Note that there are some explanatory texts on larger screens.

plurals
  1. PODelegating hash-function to uninitialized delegates in hibernate causes changing hashCode
    primarykey
    data
    text
    <p>I have a problem with <code>hashCode()</code> that delegates to uninitialized objects using hibernate.</p> <p>My data-model looks as follows (the following code is highly pruned to stress the problem and thus broken, do not replicate!):</p> <pre><code>class Compound { @FetchType.EAGER Set&lt;Part&gt; parts = new HashSet&lt;Part&gt;(); 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; } } </code></pre> <p>Please note that the implementation of <code>hashCode()</code> thoroughly follows the advice as given <a href="http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/persistent-classes.html#persistent-classes-equalshashcode" rel="nofollow">in the hibernate documentation</a>. </p> <p>Now if I load an object of type <code>Compound</code>, it eagerly loads the <code>HasSet</code> with the parts. This calls the <code>hashCode()</code> on the parts, which in turn calls the <code>hashCode()</code> on the compound. However the problem is that at this point, not all values that are considered for creating the hashCode of the compound are yet available. Therefore, the hashCode of the parts <em>changes</em> after initialization is complete, thus braking the contract of the <code>HashSet</code> and leading to all kinds of difficult-to-track-down errors (like e.g. having the same object in the parts set twice).</p> <p>So my question is: What is the simplest solution to avoid this problem (I'd like to avoid writing classes for custom loading/initialization)? Do I do anything wrong here entirely?</p> <p><strong>Edit</strong>: Am I missing something here? This seems to be a basic problem, why don't I find anything about it anywhere?</p> <blockquote> <p>Instead of using the database identifier for the equality comparison, you should use a set of properties for equals() that identify your individual objects. [...] No need to use the persistent identifier, the so called "business key" is much better. It's a natural key, but this time there is nothing wrong in using it! (<a href="http://community.jboss.org/wiki/EqualsAndHashCode" rel="nofollow">article from hibernate</a>)</p> </blockquote> <p>And</p> <blockquote> <p>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). (<a href="http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/persistent-classes.html#persistent-classes-equalshashcode" rel="nofollow">hibernate documentation</a>)</p> </blockquote> <p><strong>Edit:</strong> This is the stack trace when the loading happens (in case this helps). At that point in time, the attribute <code>someUniqueName</code> is null and thus the hashCode is calculated wrongly. </p> <pre><code>Compound.getSomeUniqueName() line: 263 Compound.hashCode() line: 286 Part.hashCode() line: 123 HashMap&lt;K,V&gt;.put(K, V) line: 372 HashSet&lt;E&gt;.add(E) line: 200 HashSet&lt;E&gt;(AbstractCollection&lt;E&gt;).addAll(Collection&lt;? extends E&gt;) 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 &lt;my code where the query is executed&gt; </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. CO*(not an answer to your question)*... *"Please note that the implementation of hashCode thoroughly follows the advice as given in the hibernate documentation"* [sic]. The very article you link to composes the two hash codes by multiplying by a prime number before adding, which is much more common than just summing. I'd recommend you *really* thoroughly follow that advice and multiply your *compound* by a prime number before doing the addition. Either that or do XOR the two hashes. But simply adding them typically augments the risks of collisions.
      singulars
    2. COThanks for the recommendation. The actual code does so. However this does not exactly help to illustrate the problem, which is why I removed it. As the note above explains: "*the following code is highly pruned to stress the problem*".
      singulars
    3. COIt is not the most ideal solution, but it might solve your problem. For the various hashing collections, the first call is to hashCode *as an optimization*. Then equals() is called if the hash codes are equal. You could have hashCode() always return 1, assuming your equals() method does not suffer from the same issue (e.g., an uninitialized value of compound.someUniqueValue). What does the equals() method look like?
      singulars
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload