more efficient loading by @NaturalId

For entities with a single @NaturalId property only.
Uses a unique key EntityLoader instead of two selects.
This commit is contained in:
Gavin King 2021-02-23 15:05:29 +01:00 committed by Steve Ebersole
parent e61eff2913
commit 725083b767
4 changed files with 88 additions and 24 deletions

View File

@ -7,11 +7,12 @@
package org.hibernate.event.internal; package org.hibernate.event.internal;
import java.io.Serializable; import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.cache.spi.access.NaturalIdDataAccess; import org.hibernate.LockOptions;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.WrongClassException;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.ResolveNaturalIdEvent; import org.hibernate.event.spi.ResolveNaturalIdEvent;
@ -19,6 +20,7 @@ import org.hibernate.event.spi.ResolveNaturalIdEventListener;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.UniqueKeyLoadable;
import org.hibernate.pretty.MessageHelper; import org.hibernate.pretty.MessageHelper;
import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.stat.spi.StatisticsImplementor;
@ -96,7 +98,8 @@ public class DefaultResolveNaturalIdEventListener
* @return The entity from the cache, or null. * @return The entity from the cache, or null.
*/ */
protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) { protected Serializable resolveFromCache(final ResolveNaturalIdEvent event) {
return event.getSession().getPersistenceContextInternal().getNaturalIdHelper().findCachedNaturalIdResolution( return event.getSession().getPersistenceContextInternal().getNaturalIdHelper()
.findCachedNaturalIdResolution(
event.getEntityPersister(), event.getEntityPersister(),
event.getOrderedNaturalIdValues() event.getOrderedNaturalIdValues()
); );
@ -120,29 +123,48 @@ public class DefaultResolveNaturalIdEventListener
startTime = System.nanoTime(); startTime = System.nanoTime();
} }
final Serializable pk = event.getEntityPersister().loadEntityIdByNaturalId( Object[] naturalIdValues = event.getOrderedNaturalIdValues();
event.getOrderedNaturalIdValues(),
event.getLockOptions(), final Serializable pk;
session EntityPersister persister = event.getEntityPersister();
LockOptions lockOptions = event.getLockOptions();
if ( persister instanceof UniqueKeyLoadable
&& naturalIdValues.length==1 ) {
UniqueKeyLoadable rootPersister = (UniqueKeyLoadable)
persister.getFactory().getMetamodel().entityPersister( persister.getRootEntityName() );
Map.Entry<String, Object> e = event.getNaturalIdValues().entrySet().iterator().next();
Object entity = rootPersister.loadByUniqueKey( e.getKey(), e.getValue(), lockOptions, session );
if ( entity == null ) {
pk = null;
}
else {
if ( !persister.isInstance(entity) ) {
throw new WrongClassException(
"loaded object was of wrong class " + entity.getClass(),
e.getKey(),
persister.getEntityName()
); );
}
pk = persister.getIdentifier( entity, session );
}
}
else {
pk = persister.loadEntityIdByNaturalId( naturalIdValues, lockOptions, session );
}
if ( stats ) { if ( stats ) {
final long endTime = System.nanoTime(); final long endTime = System.nanoTime();
final long milliseconds = TimeUnit.MILLISECONDS.convert( endTime - startTime, TimeUnit.NANOSECONDS ); final long milliseconds = TimeUnit.MILLISECONDS.convert( endTime - startTime, TimeUnit.NANOSECONDS );
statistics.naturalIdQueryExecuted( statistics.naturalIdQueryExecuted(
event.getEntityPersister().getRootEntityName(), persister.getRootEntityName(),
milliseconds milliseconds
); );
} }
//PK can be null if the entity doesn't exist //PK can be null if the entity doesn't exist
if (pk != null) { if (pk != null) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); session.getPersistenceContextInternal().getNaturalIdHelper()
persistenceContext.getNaturalIdHelper().cacheNaturalIdCrossReferenceFromLoad( .cacheNaturalIdCrossReferenceFromLoad( persister, pk, naturalIdValues );
event.getEntityPersister(),
pk,
event.getOrderedNaturalIdValues()
);
} }
return pk; return pk;

View File

@ -2481,6 +2481,23 @@ public abstract class AbstractEntityPersister
return getAppropriateUniqueKeyLoader( propertyName, session ).loadByUniqueKey( session, uniqueKey ); return getAppropriateUniqueKeyLoader( propertyName, session ).loadByUniqueKey( session, uniqueKey );
} }
public Object loadByUniqueKey(
String propertyName,
Object uniqueKey,
LockOptions lockOptions,
SharedSessionContractImplementor session) throws HibernateException {
//TODO: cache this
return new EntityLoader(
this,
propertyMapping.toColumns( propertyName ),
propertyMapping.toType( propertyName ),
1,
lockOptions,
getFactory(),
session.getLoadQueryInfluencers()
).loadByUniqueKey( session, uniqueKey );
}
private EntityLoader getAppropriateUniqueKeyLoader(String propertyName, SharedSessionContractImplementor session) { private EntityLoader getAppropriateUniqueKeyLoader(String propertyName, SharedSessionContractImplementor session) {
final boolean useStaticLoader = !session.getLoadQueryInfluencers().hasEnabledFilters() final boolean useStaticLoader = !session.getLoadQueryInfluencers().hasEnabledFilters()
&& !session.getLoadQueryInfluencers().hasEnabledFetchProfiles() && !session.getLoadQueryInfluencers().hasEnabledFetchProfiles()

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.persister.entity; package org.hibernate.persister.entity;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
/** /**
@ -16,7 +17,19 @@ public interface UniqueKeyLoadable extends Loadable {
* Load an instance of the persistent class, by a unique key other * Load an instance of the persistent class, by a unique key other
* than the primary key. * than the primary key.
*/ */
Object loadByUniqueKey(String propertyName, Object uniqueKey, SharedSessionContractImplementor session); Object loadByUniqueKey(
String propertyName,
Object uniqueKey,
SharedSessionContractImplementor session);
/**
* Load an instance of the persistent class, by a natural id.
*/
Object loadByUniqueKey(
String propertyName,
Object uniqueKey,
LockOptions lockOptions,
SharedSessionContractImplementor session);
/** /**
* Get the property number of the unique key property * Get the property number of the unique key property

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.test.naturalid.inheritance.cache; package org.hibernate.test.naturalid.inheritance.cache;
import org.hibernate.WrongClassException;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Configuration;
@ -14,7 +15,7 @@ import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.fail;
public class InheritedNaturalIdNoCacheTest extends BaseCoreFunctionalTestCase { public class InheritedNaturalIdNoCacheTest extends BaseCoreFunctionalTestCase {
@ -57,10 +58,21 @@ public class InheritedNaturalIdNoCacheTest extends BaseCoreFunctionalTestCase {
}); });
doInHibernate( this::sessionFactory, session -> { doInHibernate( this::sessionFactory, session -> {
ExtendedEntity user = session.byNaturalId( ExtendedEntity.class ) try {
session.byNaturalId( ExtendedEntity.class )
.using( "uid", "base" ) .using( "uid", "base" )
.load(); .load();
assertNull( user ); fail( "Expecting WrongClassException" );
}
catch (WrongClassException expected) {
// expected outcome
}
catch (Exception other) {
throw new AssertionError(
"Unexpected exception type : " + other.getClass().getName(),
other
);
}
}); });
} }
} }