Search code examples
javaspringspring-bootrecursionspring-data

Spring Boot recursion in code because of bidirectional relationships


I have a pretty weird error I have been sitting on for a few days. After learning about the importance of hashCode() and equals() and remembering I tried to implement these methods, for which I first used Intellij's generate feature and then, at least for the hashCode() method, did some things myself. This all is for the following unit test:

@Test
@DisplayName("Should return correct Stundenplan Eintrag given Lehrer [deutschLehrer]")
void findByLehrer() {
    List<StundenplanEintrag> stundenplanEintrag = stundenplanEintragRepository.findByLehrer(deutschLehrer);
    assertEquals(stundenplanEintrag.get(0).hashCode(), stundenplanEintragExpected.hashCode());
}

StundenplanEintrag.java:

@Override
public int hashCode() {
    int prime = 31;
    int result = 1;
    result = prime * result + Id;
    result = prime * result + stunde;
    result = prime * result + (klasse != null ? klasse.hashCode() : 0);
    result = prime * result + (fach != null ? fach.hashCode() : 0);
    result = prime * result + (lehrer != null ? lehrer.hashCode() : 0);
    result = prime * result + (tag != null ? tag.hashCode() : 0);
    return result;
}

Klasse.java:

@Override
public int hashCode() {
    int prime = 31;
    int result = 1;
    result = prime * result + Id;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    result = prime * result + (schueler != null && !schueler.isEmpty() ? schueler.hashCode() : 0);
    // result = prime * result + (stundenplanEintrag != null && !stundenplanEintrag.isEmpty() ? stundenplanEintrag.hashCode() : 0);
    result = prime * result + (vertretungsplanEintrag != null && !vertretungsplanEintrag.isEmpty() ? vertretungsplanEintrag.hashCode() : 0);
    return result;
}

Here the recursion occurs (this also is valid for the other classes Fach, Lehrer and Tag) because every class has a bidirectional relationship to StundenplanEintrag.java. So the recursion happens like this: StundenplanEintrag.java:hashCode -> Klasse.java:hashCode -> StundenplanEintrag.java:hashCode .... The only thing I found about this problem was this stackoverflow post: Always get infinite recursion when saving OneToMany relation (already used @JsonBackReference and @JsonManagedReference) (And the other once are for infinite recursion in JSON responses, which is managable by annotations). Since I am implementing the methods on my own I can't use a annoation to do that for me. And simply excluding EVERYTHING that makes a recursion possible I also tried, which works and the test passes:

enter image description here

Still, my hashCode() methods now look like this and I can't use any value that is in any relationship:

@Override
public int hashCode() {
    int prime = 31;
    int result = 1;
    result = prime * result + Id;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    // result = prime * result + (schueler != null && !schueler.isEmpty() ? schueler.hashCode() : 0);
    // result = prime * result + (stundenplanEintrag != null && !stundenplanEintrag.isEmpty() ? stundenplanEintrag.hashCode() : 0);
    // result = prime * result + (vertretungsplanEintrag != null && !vertretungsplanEintrag.isEmpty() ? vertretungsplanEintrag.hashCode() : 0);
    return result;
}

But I really want unique hashCodes for which I want to use every attribute and not exclude most of them. Also, this applies to the toString() method, and if I want to print info I want to print info, I can't just exclude everything besides to integers, or atleast that wouldn't be very useful info. I know the Id basically is unique, but I just want to know if there maybe is a cleaner solution for this instead of avoiding the problem through just deleting the code?


Solution

  • from hibernate docs

    It is recommended that you implement equals() and hashCode() using Business key equality. Business key equality means that the equals() method compares only the properties that form the business key. It is a key that would identify our instance in the real world (a natural candidate key): Immutable or unique properties are usually good candidates for a business key.

    so you would have to identify your business requirements and check if your entity has attributes that qualify to be natural key. otherwise you would use the primary key which has some pitfalls if entity isn't persisted or if you detach and reattach entities you can read more here

    also I recommend reading this blogpost by Vlad Mihalcea