0

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?

Gabriel
  • 233
  • 1
  • 3
  • 11

1 Answers1

1

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

Ahmed Nabil
  • 457
  • 1
  • 9
  • 1: Thank you very much, I didn't know about the article, which was very good but a little bit complicated. But I haven't really even started to think about the problems that would start to occur when for example changing attributes of an entity. I am now using ```return getClass().hashCode();``` because that seems to be the easiest way to avoid these errors. But I still don't fully understand the natural key, as the attributes I had to comment out were natural keys. But I can't use them because of recursion. Or is it as simple as: natural keys I can't use? – Gabriel Jul 04 '23 at 12:22
  • 2: Because in my class "Klasse.java" if I comment everything out that generates the recursion I am left with "Id" and "name" as options to use in my hashCode() method. But "Id" as you said isn't very good and "name" can't be a natural key because it doesn't have to be unique. The set of students (Set) is a natural key but I can't use it because that causes a recursion error. So now I am using ```return getClass().hashCode();``` and it seems to work but I am not sure if this is a good solution to my specific problem? – Gabriel Jul 04 '23 at 12:25
  • tbh I don't really understand your code, but If you are sure that set of students is the natural key then you should add equals and hashCode in Student class to break the cycle, I would advise you to take some time with a pen and paper and try to apply equals and hashcode for all entities in a way that applies your business logic and avoids cycles, hope that helps – Ahmed Nabil Jul 04 '23 at 12:31
  • 1
    Thank you very much for your answer, I was arguing before, because I didn't want to implement it and have a look at my whole db again. But now that I wrote it down I could implement the methods for every class and there is no recursion anymore. I also learned about NK and a flaw in my system which I now fixed (I had no NK for my users, for login) – Gabriel Jul 06 '23 at 19:43
  • again, happy to help anytime :) – Ahmed Nabil Jul 06 '23 at 20:26