From my previous question: Hibernate: Cannot fetch data back to Map<>, I was getting NullPointerException after I tried to fetch data back. I though the reason was the primary key (when added to Map as put(K,V)
, the primary key was null, but after JPA persist, it created the primary key and thus changed the HashMap()). I had this equals
and hashCode
:
User.java:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(id, user.id) && Objects.equals(username, user.username) && Objects.equals(about, user.about) && Objects.equals(friendships, user.friendships) && Objects.equals(posts, user.posts);
}
@Override
public int hashCode() {
return Objects.hash(id, username, about, friendships, posts);
}
-> I used all fields in the calculation of hash. That made the NullPointerException BUT not because of id
(primary key), but because of collections involved in the hash (friends
and posts
). So I changed both functions to use only database equality:
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (id == null) return false;
if (!(o instanceof User)) return false;
User user = (User) o;
return this.id.equals(user.getId());
}
@Override
public int hashCode() {
return id == null ? System.identityHashCode(this) :
id.hashCode();
So now only the id
field is involved in the hash. Now, it didn't give me NullPointerException for fetched data. I used this code to test it:
(from User.java):
public void addFriend(User friend){
Friendship friendship = new Friendship();
friendship.setOwner(this);
friendship.setFriend(friend);
this.friendships.put(friend, friendship);
}
DemoApplication.java:
@Bean
public CommandLineRunner dataLoader(UserRepository userRepo, FriendshipRepository friendshipRepo){
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
User f1 = new User("friend1");
User f2 = new User("friend2");
User u1 = new User("user1");
System.out.println(f1);
System.out.println(f1.hashCode());
u1.addFriend(f1);
u1.addFriend(f2);
userRepo.save(u1);
User fetchedUser = userRepo.findByUsername("user1");
System.out.println(fetchedUser.getFriendships().get(f1).getFriend());
System.out.println(fetchedUser.getFriendships().get(f1).getFriend().hashCode());
}
};
}
You can see I am
put
ing thef1
User into friendship ofuser1
(owner of the friendship). The time when thef1.getId() == null
- saving the
user1
. The time when thef1
id gets assign its primary key value by Hibernate (because the friendship relation isCascade.All
so including the persisting) - Fetching the
f1
User back byget
ing it from the Map, which does the look-up with thehashCode
, which is now broken, because thef1.getId() != null
.
But even then, I got the right element. The output:
User{id=null, username='friend1', about='null', friendships={}, posts=[]}
-935581894
...
User{id=3, username='friend1', about='null', friendships={}, posts=[]}
3
As you can see: the id
is null, then 3
and the hashCode is -935581894
, then 3
... So how is possible I was able to get the right element?
CodePudding user response:
Not all Map implementation use the hashCode (for example a TreeMap implementation do not use it, and rather uses a Comparator to sort entries into a tree).
So i would first check that hibernate is not replacing the field :
private Map<User, Friendship> friendships = new HashMap<>();
with its own implementation of Map.
Then, even if hibernate keeps the HashMap, and the hashcode of the object changed, you might be lucky and both old and new hashcodes gives the same bucket of the hashmap.
As the object is the same (the hibernate session garantees that), the equals used to find the object in the bucket will work. (if the bucket has more than 8 elements, instead of the bucket being a linked list, it will be a b-tree ordered on hashcode, in that case it won't find your entry, but the map seems to have only 2-3 elements so it can't be the case).
CodePudding user response:
Now I understood your question. Looking at the Map documentation we read the following:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
It looks like there is no definitive answer for this and as @Thierry already said it seems that you just got lucky. The key takeaway is "do not use mutable objects as Map keys".