Relacja Many to Many w JPA, błąd StackOverflow

0

Siema, może ktoś będzie w stanie mi pomóc ogarnąć magię JPA. Mam dwie encje: Customer i YogaClass które chciałbym połączyć relacją many to many:

@Entity
@Table(name = "customer")
@Data
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String lastName;
    @OneToMany
    private Set<Pass> passes;
    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(
            name = "customer_yoga_class",
            joinColumns = { @JoinColumn(name = "customer_id", referencedColumnName="id") },
            inverseJoinColumns = { @JoinColumn(name = "yoga_class_id", referencedColumnName="id") }
    )
    @JsonIgnoreProperties(value = "attendees")
    private Set<YogaClass> yogaClasses;
}

@Entity
@Table(name = "yoga_class")
@Data
public class YogaClass {
    @Id
    @GeneratedValue
    private Long id;
    private String instructor;
    private LocalDateTime date;
    private Integer spotsLeft;
    @ManyToMany(mappedBy = "yogaClasses")
    @JsonIgnoreProperties(value = "yogaClasses")
    @Nullable
    private Set<Customer> attendees;
}

Zapisuje po jednym Customer i YogaClass w bazie danych, następnie chce przypisać jedno do drugiego w ten sposób:

public void addAttendee(String custId, String classId) {
    YogaClass classToUpdate = yogaClassRepository.findById(Long.parseLong(classId))
            .orElseThrow(EntityNotFoundException::new);
    Customer customerToAdd = customerService.findById(Long.parseLong(custId))
            .orElseThrow(EntityNotFoundException::new);

    classToUpdate.getAttendees().add(customerToAdd);
    classToUpdate.setSpotsLeft(classToUpdate.getSpotsLeft() - 1);

    customerToAdd.getYogaClasses().add(classToUpdate);

    yogaClassRepository.save(classToUpdate);
    customerService.put(customerToAdd);
}

Niestety podczas wykonywania metody addAttendee leci stackOverflowError:

java.lang.StackOverflowError: null
	at org.hibernate.collection.spi.PersistentSet.hashCode(PersistentSet.java:410) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
	at io.github.project_yoga.domain.yogaclass.YogaClass.hashCode(YogaClass.java:14) ~[classes/:na]
	at java.base/java.util.AbstractSet.hashCode(AbstractSet.java:124) ~[na:na]
	at org.hibernate.collection.spi.PersistentSet.hashCode(PersistentSet.java:410) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]

Czytałem trochę o circular reference w JPA, ale nie pomagają żadne @JsonManagedReference, @JsonBackReference czy inne rozwiazania. Próbowałem to ograć również w taki sposób: https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/ ale efekt był ten sam. Jestem w szoku że z pozoru tak prosta rzecz dostarcza tylu problemów. 😅 Ktoś ma pomysł jak to ugryźć?

2

Problemu szukałbym tutaj:

at io.github.project_yoga.domain.yogaclass.YogaClass.hashCode(YogaClass.java:14) ~[classes/:na]

hashCode - skąd się bierze? Pojawia się ta linia w kółko w stacktrace? Możliwe że implementacja z Lomboka napieprza ci w kółko po dwóch obiektach.

0

Tak, te 4 linie lecą w kółko w stacktrace.

0

Ok rozwiązane. Faktycznie był problem z metodą hashCode generowaną przez lomboka. Po pozbyciu się adnotacji @Data działa poprawnie. Dzięki @kelog!

1

Niestety Lombok to Lombok - przydatne, ale trzeba wiedzieć co jest pod spodem. Spoko :)

1

@horse from valona no własnie tu trzeba uważać - to że będą błedy SOE przy many-to-many w JPA po nadpisaniu hashcode to jedno, ale to ze hashcode lombokowy z encjami JPA to już totalna kaplica na ogół - na ogół najlepszy jest jakiś techniczny id wykorzystać do equalsa i hashcoda jeśli trzeba ;)

1

Lombokowe @EqualsAndHashCode pozwala wykluczyć niektóre pola z implementacji tych metod za pomocą parametru exclude, np @EqualsAndHashCode(exclude = {"id", "age"}).

Więcej można poczytać tutaj chociażby tutaj: https://www.baeldung.com/java-lombok-equalsandhashcode

2
Belka napisał(a):

Lombokowe @EqualsAndHashCode pozwala wykluczyć niektóre pola z implementacji tych metod za pomocą parametru exclude, np @EqualsAndHashCode(exclude = {"id", "age"}).

Więcej można poczytać tutaj chociażby tutaj: https://www.baeldung.com/java-lombok-equalsandhashcode

Zajebiste. Zawsze chciałem, żeby przy analizie problemów czytać stringi w adnotacjach.

0
jarekr000000 napisał(a):

Zajebiste. Zawsze chciałem, żeby przy analizie problemów czytać stringi w adnotacjach.

Czy ja wiem, czy to taki problem w pet projectach. Jak najbardziej możesz zagrać kartę "pielęgnowanie złych nawyków" z czym się po cześci zgodzę, but still w komercyjnym projecie pewnie i tak by musiał się dopasować do jakiegoś codestyle zespołu, w tym wytycznych dot. uzywania/nieuzywania Lomboka itd.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.