Hash function delegation for uninitialized hibernate delegates causes a hashCode change

I have a problem with hashCode() , which delegates uninitialized objects using sleep mode.

My data model is as follows (the following code is heavily cropped to emphasize the problem and thus is broken, do not replicate!):

 class Compound { @FetchType.EAGER Set<Part> parts = new HashSet<Part>(); String someUniqueName; public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getSomeUniqueName() == null) ? 0 : getSomeUniqueName().hashCode()); return result; } } class Part { Compound compound; String someUniqueName; public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getCompound() == null) ? 0 : getCompound().hashCode()); result = prime * result + ((getSomeUniqueName() == null) ? 0 : getSomeUniqueName().hashCode()); return result; } } 

Note that the implementation of hashCode() fully consistent with the recommendations in the hibernation documentation .

Now, if I load an object of type Compound , it readily loads HasSet with parts. This calls hashCode() on the parts, which in turn calls hashCode() on the connection. However, the problem is that at the moment, not all values ​​that are considered to create a hash code of the connection are still available. Therefore, the hash code of the parts changes after initialization is completed, thus trading with the HashSet contract and leading to all kinds of errors that are difficult to track (for example, with the same object in parts specified twice).

So my question is: what is the simplest solution to avoid this problem (I would like to avoid writing classes for custom loading / initialization)? Am I not doing anything here?

Edit : Am I missing something? This is apparently the main problem, why can't I find anything about it?

Instead of using a database identifier to compare equality, you should use a set of properties for equals () that identify your individual objects. [...] No need to use a constant identifier, so the so-called "business key" is much better. This is a natural key, but this time there is nothing wrong with using it! ( article from hibernate )

and

It is recommended to use equals () and hashCode () using Business Key Equality. Business key equality means that the equals () method compares only the properties that make up the business key. this is the key that identifies our example in the real world (candidate’s natural key). ( hibernate documentation )

Edit: This is a stack trace at boot (in case this helps). At this point, the someUniqueName attribute is zero, and therefore hashCode is not computed correctly.

 Compound.getSomeUniqueName() line: 263 Compound.hashCode() line: 286 Part.hashCode() line: 123 HashMap<K,V>.put(K, V) line: 372 HashSet<E>.add(E) line: 200 HashSet<E>(AbstractCollection<E>).addAll(Collection<? extends E>) line: 305 PersistentSet.endRead() line: 352 CollectionLoadContext.endLoadingCollection(LoadingCollectionEntry, CollectionPersister) line: 261 CollectionLoadContext.endLoadingCollections(CollectionPersister, List) line: 246 CollectionLoadContext.endLoadingCollections(CollectionPersister) line: 219 EntityLoader(Loader).endCollectionLoad(Object, SessionImplementor, CollectionPersister) line: 1005 EntityLoader(Loader).initializeEntitiesAndCollections(List, Object, SessionImplementor, boolean) line: 993 EntityLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean) line: 857 EntityLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 274 EntityLoader(Loader).loadEntity(SessionImplementor, Object, Type, Object, String, Serializable, EntityPersister, LockOptions) line: 2037 EntityLoader(AbstractEntityLoader).load(SessionImplementor, Object, Object, Serializable, LockOptions) line: 86 EntityLoader(AbstractEntityLoader).load(Serializable, Object, SessionImplementor, LockOptions) line: 76 SingleTableEntityPersister(AbstractEntityPersister).load(Serializable, Object, LockOptions, SessionImplementor) line: 3293 DefaultLoadEventListener.loadFromDatasource(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 496 DefaultLoadEventListener.doLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 477 DefaultLoadEventListener.load(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 227 DefaultLoadEventListener.proxyOrLoad(LoadEvent, EntityPersister, EntityKey, LoadEventListener$LoadType) line: 269 DefaultLoadEventListener.onLoad(LoadEvent, LoadEventListener$LoadType) line: 152 SessionImpl.fireLoad(LoadEvent, LoadEventListener$LoadType) line: 1090 SessionImpl.internalLoad(String, Serializable, boolean, boolean) line: 1038 ManyToOneType(EntityType).resolveIdentifier(Serializable, SessionImplementor) line: 630 ManyToOneType(EntityType).resolve(Object, SessionImplementor, Object) line: 438 TwoPhaseLoad.initializeEntity(Object, boolean, SessionImplementor, PreLoadEvent, PostLoadEvent) line: 139 QueryLoader(Loader).initializeEntitiesAndCollections(List, Object, SessionImplementor, boolean) line: 982 QueryLoader(Loader).doQuery(SessionImplementor, QueryParameters, boolean) line: 857 QueryLoader(Loader).doQueryAndInitializeNonLazyCollections(SessionImplementor, QueryParameters, boolean) line: 274 QueryLoader(Loader).doList(SessionImplementor, QueryParameters) line: 2542 QueryLoader(Loader).listIgnoreQueryCache(SessionImplementor, QueryParameters) line: 2276 QueryLoader(Loader).list(SessionImplementor, QueryParameters, Set, Type[]) line: 2271 QueryLoader.list(SessionImplementor, QueryParameters) line: 459 QueryTranslatorImpl.list(SessionImplementor, QueryParameters) line: 365 HQLQueryPlan.performList(QueryParameters, SessionImplementor) line: 196 SessionImpl.list(String, QueryParameters) line: 1268 QueryImpl.list() line: 102 <my code where the query is executed> 
+6
source share
7 answers

You have a perfect legal precedent, and it really should work. However, you would have the same problem in regular Java if you installed the "parts" of the Compound object before setting "someUniqueName".

So, if you can convince hibernate to set the someUniqueName property in front of the "parts" property. Have you only experimented with reordering them in the java class? Or rename "parts" to "zparts"? Hibernate docs says ordering is not guaranteed. I would make a mistake in sleep mode to enforce this order ...

Another solution that could be simpler:

 class Part { public int hashCode() { //don't include getCompound().hashCode() return getSomeUniqueName() == null ? 0 : getSomeUniqueName().hashCode(); } public boolean equals(Object o) { if (this == o) return true; if (!o instanceof Part) return false; Part part = (Part) o; if (getCompound() != null ? !getCompound().equals(part.getCompound()) : part.getCompound()!= null) return false; if (getSomeUniqueName()!= null ? !getSomeUniqueName().equals(part.getSomeUniqueName()) : part.getSomeUniqueName()!= null) return false; return true; } } 

In Compound.equals (), make sure it also starts with

 public boolean equals(Object o) { if (this == o) return true; 

This should avoid the problem that you are currently facing.

Each property in the hashCode () method must be in the equals () method, but not necessarily the other way around.

+2
source

From your question, I realized that all of your model properties that have ever participated in the hashCode() method were not loaded by default. In this case, if you want all your properties to load, you can follow their methods.

  • Calling the getter methods in hashCode() your model class as it initializes / loads all the model properties.
  • Using sesstion.get() instead of the session.load() method, since it will not create a proxy server and will load all the properties of your model.
  • By setting lazy="false" for all your properties when matching.

Hope this can solve your problem!

+2
source

I read in one comment the question that you are allowing Eclipse to generate equals and hashCode methods.

Have you done this for all objects ( Part and Compound )? I ask because in this case these methods usually directly access the properties of the object (i.e., without calling getter methods). They look as follows.

 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((prop == null) ? 0 : prop.hashCode()); return result; } 

When using Hibernate, this often leads to problems similar to the ones you describe, since uninitialized properties have default values ​​( null for objects, 0 for int s, etc.) while the corresponding get method is called , which forces the proxy server hibernate access the database and load the values ​​necessary to calculate the correct values ​​for the methods.

You can easily spot the problem if you run the debugger and check the properties on the first call to hashCode() .

If this happens, the easiest way to fix this is to change the methods to use the get methods, for example here:

 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((getProp() == null) ? 0 : getProp().hashCode()); return result; } 

Another point worth considering: the equals method created by Eclipse performs this check on getClass() != obj.getClass() , which is not suitable for Hibernate objects distributed by Hibernate proxies. I would replace this with instanceof checking.

+2
source

So why not use a list, not a set? I know this would be a workaround rather than the right solution, but you would not have to play with hash codes at all.

+2
source

One of the possibilities I was thinking about followed the advice given in this article . They basically suggest not using hibernation (or rather a database) to generate identifiers, but using the UUID library to generate their own identifiers, and then using these identifiers for equals() and hashCode() . Besides the problems mentioned in this article, it has some serious flaws for my current implementation: it will break my existing code! Each time I created a Part element, I would first need to query if it exists in the database and retrieve it, if so. In my current implementation, I just create Parts as I like and just add them to Compound . If Compound already has this part, everything works automatically ...

0
source

I found a related question here . The main idea of ​​the solution is that the whole problem disappears as soon as you are not willing to retrieve the details. Then the connection is already fully initialized when loading parts. However, this creates a completely different problem when working out of sessions with individual objects ...

0
source

I'm not quite sure, but you can check if the object returned by Hibernate is the object you are looking for (.class), instanceof would not be a good candidate in this case, since the proxy is a subclass, Hibernate will return the proxy if not all participants will be allowed, and therefore hashcode / equals will be interrupted for sure.

Cheers, Eugene

0
source

Source: https://habr.com/ru/post/904800/


All Articles