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>