Hibernate maintains a cache for id<->naturalId correspondances (pkToNaturalIdMap and naturalIdToPkMap maps in org.hibernate.engine.internal.NaturalIdResolutionsImpl.EntityResolutions#cache). This cache is updated after creations and loads (at least, I did not test other use-cases) .
In the maps, the natural id is encapsulated in a ResolutionImpl instance and the hashcode is computed in the constructor :
{code:java}final int prime = 31; int hashCodeCalculation = 1; hashCodeCalculation = prime * hashCodeCalculation + entityDescriptor.hashCode(); hashCodeCalculation = prime * hashCodeCalculation + entityDescriptor.getNaturalIdMapping().calculateHashCode( naturalIdValue, persistenceContext.getSession() );{code}
For entities with a simple natural id, entityDescriptor.getNaturalIdMapping() is an instance of SimpleNaturalIdMapping with this calculateHashCode method :
{code:java}@Override public int calculateHashCode(Object value) { //noinspection unchecked return value == null ? 0 : ( (JavaType<Object>) getJavaType() ).extractHashCode( value ); }{code}
For entities with a compound natural id, entityDescriptor.getNaturalIdMapping() is an instance of CompoundNaturalIdMapping with this calculateHashCode method :
{code:java}@Override public int calculateHashCode(Object value) { return 0; }{code}
As a consequence, for a given entity with a compound natural id, the hashcodes of all ResolutionImpl objects used as keys in naturalIdToPkMap are the same, which creates collisions and consumes a lot of cpu cycles.
The calculateHashCode method in CompoundNaturalIdMapping should be something like
{code:java}@Override public int calculateHashCode(Object value) { return Arrays.hashCode((Object[]) value); }{code}
to align with the areEqual method.
The attached test case creates 20000 objects in database then re-reads these objects. It is done for two entity classes : EntityWithSimpleNaturalId and EntityWithCompoundNaturalId.
Logs before correction modification :
{code:java}16:33:31,782 INFO CompoundNaturalIdCacheTest:64 - Starting creations 16:33:53,745 INFO CompoundNaturalIdCacheTest:74 - Persisted 20000 EntityWithCompoundNaturalId objects, duration=21957ms 16:33:53,906 INFO CompoundNaturalIdCacheTest:83 - Persisted 20000 EntityWithSimpleNaturalId objects, duration=160ms 16:33:55,140 INFO CompoundNaturalIdCacheTest:115 - Loading at most 20000 instances of class org.hibernate.orm.test.mapping.naturalid.compound.CompoundNaturalIdCacheTest$EntityWithCompoundNaturalId 16:34:12,915 INFO CompoundNaturalIdCacheTest:123 - Loaded 20000 instances of class org.hibernate.orm.test.mapping.naturalid.compound.CompoundNaturalIdCacheTest$EntityWithCompoundNaturalId, duration=17777ms 16:34:12,921 INFO CompoundNaturalIdCacheTest:115 - Loading at most 20000 instances of class org.hibernate.orm.test.mapping.naturalid.compound.CompoundNaturalIdCacheTest$EntityWithSimpleNaturalId 16:34:13,021 INFO CompoundNaturalIdCacheTest:123 - Loaded 20000 instances of class org.hibernate.orm.test.mapping.naturalid.compound.CompoundNaturalIdCacheTest$EntityWithSimpleNaturalId, duration=99ms{code}
Logs after correction modification :
{code:java}16:35:37,950 INFO CompoundNaturalIdCacheTest:64 - Starting creations 16:35:38,247 INFO CompoundNaturalIdCacheTest:74 - Persisted 20000 EntityWithCompoundNaturalId objects, duration=292ms 16:35:38,368 INFO CompoundNaturalIdCacheTest:83 - Persisted 20000 EntityWithSimpleNaturalId objects, duration=119ms 16:35:39,679 INFO CompoundNaturalIdCacheTest:115 - Loading at most 20000 instances of class org.hibernate.orm.test.mapping.naturalid.compound.CompoundNaturalIdCacheTest$EntityWithCompoundNaturalId 16:35:40,096 INFO CompoundNaturalIdCacheTest:123 - Loaded 20000 instances of class org.hibernate.orm.test.mapping.naturalid.compound.CompoundNaturalIdCacheTest$EntityWithCompoundNaturalId, duration=418ms 16:35:40,103 INFO CompoundNaturalIdCacheTest:115 - Loading at most 20000 instances of class org.hibernate.orm.test.mapping.naturalid.compound.CompoundNaturalIdCacheTest$EntityWithSimpleNaturalId 16:35:40,232 INFO CompoundNaturalIdCacheTest:123 - Loaded 20000 instances of class org.hibernate.orm.test.mapping.naturalid.compound.CompoundNaturalIdCacheTest$EntityWithSimpleNaturalId, duration=129ms{code} |
|