From 00e9db2b8bc396ce5013903bf4b4611fbb3e451d Mon Sep 17 00:00:00 2001 From: Nathan Xu Date: Sat, 21 Dec 2019 17:10:38 -0500 Subject: [PATCH] HHH-11958 Make EntityManager.find() support QueryHints.HINT_READONLY --- .../engine/spi/LoadQueryInfluencers.java | 16 ++++++- .../internal/DefaultLoadEventListener.java | 3 +- .../org/hibernate/event/spi/LoadEvent.java | 36 ++++++++++----- .../org/hibernate/internal/SessionImpl.java | 24 +++++++--- .../java/org/hibernate/loader/Loader.java | 25 ++++++++++- .../loader/entity/AbstractEntityLoader.java | 23 +++++++--- .../loader/entity/BatchingEntityLoader.java | 27 ++++++++++-- .../DynamicBatchingEntityLoaderBuilder.java | 18 ++++++-- .../hibernate/loader/entity/EntityLoader.java | 6 ++- .../LegacyBatchingEntityLoaderBuilder.java | 8 +++- .../PaddedBatchingEntityLoaderBuilder.java | 7 ++- .../loader/entity/UniqueEntityLoader.java | 13 ++++++ .../AbstractLoadPlanBasedEntityLoader.java | 34 ++++++++++++-- .../entity/plan/BatchingEntityLoader.java | 2 +- .../LegacyBatchingEntityLoaderBuilder.java | 8 +++- .../entity/AbstractEntityPersister.java | 11 ++++- .../persister/entity/EntityPersister.java | 12 ++++- .../persister/entity/NamedQueryLoader.java | 17 ++++++- .../hibernate/jpa/test/EntityManagerTest.java | 44 +++++++++++++++++++ .../PersisterClassProviderTest.java | 4 +- 20 files changed, 289 insertions(+), 49 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java index a644f35588..44ee0e534f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java @@ -52,12 +52,19 @@ public class LoadQueryInfluencers implements Serializable { private final EffectiveEntityGraph effectiveEntityGraph = new EffectiveEntityGraph(); + private Boolean readOnly; + public LoadQueryInfluencers() { - this( null ); + this( null, null ); } public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory) { + this(sessionFactory, null); + } + + public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory, Boolean readOnly) { this.sessionFactory = sessionFactory; + this.readOnly = readOnly; } public SessionFactoryImplementor getSessionFactory() { @@ -282,4 +289,11 @@ public class LoadQueryInfluencers implements Serializable { effectiveEntityGraph.applyGraph( (RootGraphImplementor) loadGraph, GraphSemantic.LOAD ); } + public Boolean getReadOnly() { + return readOnly; + } + + public void setReadOnly(Boolean readOnly) { + this.readOnly = readOnly; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java index a5d7d53b17..48af0592cf 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultLoadEventListener.java @@ -570,7 +570,8 @@ public class DefaultLoadEventListener implements LoadEventListener { event.getEntityId(), event.getInstanceToLoad(), event.getLockOptions(), - event.getSession() + event.getSession(), + event.getReadOnly() ); final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics(); diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java index bd33e60824..b011a919ac 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/LoadEvent.java @@ -46,23 +46,24 @@ public class LoadEvent extends AbstractEvent { private boolean isAssociationFetch; private Object result; private PostLoadEvent postLoadEvent; + private Boolean readOnly; - public LoadEvent(Serializable entityId, Object instanceToLoad, EventSource source) { - this( entityId, null, instanceToLoad, DEFAULT_LOCK_OPTIONS, false, source ); + public LoadEvent(Serializable entityId, Object instanceToLoad, EventSource source, Boolean readOnly) { + this( entityId, null, instanceToLoad, DEFAULT_LOCK_OPTIONS, false, source, readOnly ); } - public LoadEvent(Serializable entityId, String entityClassName, LockMode lockMode, EventSource source) { - this( entityId, entityClassName, null, lockMode, false, source ); + public LoadEvent(Serializable entityId, String entityClassName, LockMode lockMode, EventSource source, Boolean readOnly) { + this( entityId, entityClassName, null, lockMode, false, source, readOnly ); } - public LoadEvent(Serializable entityId, String entityClassName, LockOptions lockOptions, EventSource source) { - this( entityId, entityClassName, null, lockOptions, false, source ); + public LoadEvent(Serializable entityId, String entityClassName, LockOptions lockOptions, EventSource source, Boolean readOnly) { + this( entityId, entityClassName, null, lockOptions, false, source, readOnly ); } - public LoadEvent(Serializable entityId, String entityClassName, boolean isAssociationFetch, EventSource source) { - this( entityId, entityClassName, null, DEFAULT_LOCK_OPTIONS, isAssociationFetch, source ); + public LoadEvent(Serializable entityId, String entityClassName, boolean isAssociationFetch, EventSource source, Boolean readOnly) { + this( entityId, entityClassName, null, DEFAULT_LOCK_OPTIONS, isAssociationFetch, source, readOnly ); } - + public boolean isAssociationFetch() { return isAssociationFetch; } @@ -73,10 +74,11 @@ public class LoadEvent extends AbstractEvent { Object instanceToLoad, LockMode lockMode, boolean isAssociationFetch, - EventSource source) { + EventSource source, + Boolean readOnly) { this( entityId, entityClassName, instanceToLoad, lockMode == DEFAULT_LOCK_MODE ? DEFAULT_LOCK_OPTIONS : new LockOptions().setLockMode( lockMode ), - isAssociationFetch, source ); + isAssociationFetch, source, readOnly ); } private LoadEvent( @@ -85,7 +87,8 @@ public class LoadEvent extends AbstractEvent { Object instanceToLoad, LockOptions lockOptions, boolean isAssociationFetch, - EventSource source) { + EventSource source, + Boolean readOnly) { super( source ); @@ -106,6 +109,7 @@ public class LoadEvent extends AbstractEvent { this.lockOptions = lockOptions; this.isAssociationFetch = isAssociationFetch; this.postLoadEvent = new PostLoadEvent( source ); + this.readOnly = readOnly; } public Serializable getEntityId() { @@ -190,4 +194,12 @@ public class LoadEvent extends AbstractEvent { public void setPostLoadEvent(PostLoadEvent postLoadEvent) { this.postLoadEvent = postLoadEvent; } + + public Boolean getReadOnly() { + return readOnly; + } + + public void setReadOnly(Boolean readOnly) { + this.readOnly = readOnly; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index ebd4613804..0ce662253d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -928,7 +928,7 @@ public final class SessionImpl LoadEvent event = loadEvent; loadEvent = null; if ( event == null ) { - event = new LoadEvent( id, object, this ); + event = new LoadEvent( id, object, this, getReadOnlyFromLoadQueryInfluencers() ); } else { event.setEntityClassName( null ); @@ -1059,7 +1059,7 @@ public final class SessionImpl */ private LoadEvent recycleEventInstance(final LoadEvent event, final Serializable id, final String entityName) { if ( event == null ) { - return new LoadEvent( id, entityName, true, this ); + return new LoadEvent( id, entityName, true, this, getReadOnlyFromLoadQueryInfluencers() ); } else { event.setEntityClassName( entityName ); @@ -2748,12 +2748,12 @@ public final class SessionImpl @SuppressWarnings("unchecked") protected T doGetReference(Serializable id) { if ( this.lockOptions != null ) { - LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this ); + LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this, getReadOnlyFromLoadQueryInfluencers() ); fireLoad( event, LoadEventListener.LOAD ); return (T) event.getResult(); } - LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, SessionImpl.this ); + LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, SessionImpl.this, getReadOnlyFromLoadQueryInfluencers() ); boolean success = false; try { fireLoad( event, LoadEventListener.LOAD ); @@ -2784,12 +2784,12 @@ public final class SessionImpl @SuppressWarnings("unchecked") protected final T doLoad(Serializable id) { if ( this.lockOptions != null ) { - LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this ); + LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), lockOptions, SessionImpl.this, getReadOnlyFromLoadQueryInfluencers() ); fireLoad( event, LoadEventListener.GET ); return (T) event.getResult(); } - LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, SessionImpl.this ); + LoadEvent event = new LoadEvent( id, entityPersister.getEntityName(), false, SessionImpl.this, getReadOnlyFromLoadQueryInfluencers() ); boolean success = false; try { fireLoad( event, LoadEventListener.GET ); @@ -3311,7 +3311,8 @@ public final class SessionImpl try { getLoadQueryInfluencers().getEffectiveEntityGraph().applyConfiguredGraph( properties ); - + Boolean readOnly = properties == null ? null : (Boolean) properties.get( QueryHints.HINT_READONLY ); + getLoadQueryInfluencers().setReadOnly( readOnly ); final IdentifierLoadAccess loadAccess = byId( entityClass ); loadAccess.with( determineAppropriateLocalCacheMode( properties ) ); @@ -3366,6 +3367,7 @@ public final class SessionImpl } finally { getLoadQueryInfluencers().getEffectiveEntityGraph().clear(); + getLoadQueryInfluencers().setReadOnly( null ); } } @@ -3786,4 +3788,12 @@ public final class SessionImpl ( (FilterImpl) loadQueryInfluencers.getEnabledFilter( filterName ) ).afterDeserialize( getFactory() ); } } + + private Boolean getReadOnlyFromLoadQueryInfluencers() { + Boolean readOnly = null; + if ( loadQueryInfluencers != null ) { + readOnly = loadQueryInfluencers.getReadOnly(); + } + return readOnly; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index e568529c3b..90db371a9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -2389,7 +2389,8 @@ public abstract class Loader { final String optionalEntityName, final Serializable optionalIdentifier, final EntityPersister persister, - LockOptions lockOptions) throws HibernateException { + final LockOptions lockOptions, + final Boolean readOnly) throws HibernateException { if ( LOG.isDebugEnabled() ) { LOG.debugf( "Loading entity: %s", MessageHelper.infoString( persister, id, identifierType, getFactory() ) ); } @@ -2403,6 +2404,9 @@ public abstract class Loader { qp.setOptionalEntityName( optionalEntityName ); qp.setOptionalId( optionalIdentifier ); qp.setLockOptions( lockOptions ); + if ( readOnly != null ) { + qp.setReadOnly( readOnly ); + } result = doQueryAndInitializeNonLazyCollections( session, qp, false ); } catch (SQLException sqle) { @@ -2477,6 +2481,22 @@ public abstract class Loader { final Serializable optionalId, final EntityPersister persister, LockOptions lockOptions) throws HibernateException { + return loadEntityBatch( session, ids, idType, optionalObject, optionalEntityName, optionalId, persister, lockOptions, null ); + } + + /** + * Called by wrappers that batch load entities + */ + public final List loadEntityBatch( + final SharedSessionContractImplementor session, + final Serializable[] ids, + final Type idType, + final Object optionalObject, + final String optionalEntityName, + final Serializable optionalId, + final EntityPersister persister, + final LockOptions lockOptions, + final Boolean readOnly) throws HibernateException { if ( LOG.isDebugEnabled() ) { LOG.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, getFactory() ) ); } @@ -2492,6 +2512,9 @@ public abstract class Loader { qp.setOptionalEntityName( optionalEntityName ); qp.setOptionalId( optionalId ); qp.setLockOptions( lockOptions ); + if ( readOnly != null ) { + qp.setReadOnly( readOnly ); + } result = doQueryAndInitializeNonLazyCollections( session, qp, false ); } catch (SQLException sqle) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java index 85519412d8..37bf52308f 100755 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/AbstractEntityLoader.java @@ -8,8 +8,6 @@ package org.hibernate.loader.entity; import java.io.Serializable; import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collections; import java.util.List; import org.hibernate.HibernateException; @@ -46,12 +44,23 @@ public abstract class AbstractEntityLoader @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) { // this form is deprecated! - return load( id, optionalObject, session, LockOptions.NONE ); + return load( id, optionalObject, session, LockOptions.NONE, null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, Boolean readOnly) { + // this form is deprecated! + return load( id, optionalObject, session, LockOptions.NONE, readOnly ); } @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { - return load( session, id, optionalObject, id, lockOptions ); + return load( id, optionalObject, session, lockOptions, null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { + return load( session, id, optionalObject, id, lockOptions, readOnly ); } protected Object load( @@ -59,7 +68,8 @@ public abstract class AbstractEntityLoader Object id, Object optionalObject, Serializable optionalId, - LockOptions lockOptions) { + LockOptions lockOptions, + Boolean readOnly) { List list = loadEntity( session, @@ -69,7 +79,8 @@ public abstract class AbstractEntityLoader entityName, optionalId, persister, - lockOptions + lockOptions, + readOnly ); if ( list.size()==1 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java index fd4573d85a..bb53b14ea6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/BatchingEntityLoader.java @@ -11,6 +11,7 @@ import java.sql.SQLException; import java.util.Arrays; import java.util.List; +import org.hibernate.HibernateException; import org.hibernate.LockOptions; import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.spi.QueryParameters; @@ -50,11 +51,27 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader { return load( id, optionalObject, session, LockOptions.NONE ); } + @Override + public Object load( + Serializable id, + Object optionalObject, + SharedSessionContractImplementor session, + LockOptions lockOptions, + Boolean readOnly) { + return load( id, optionalObject, session, lockOptions, readOnly ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, Boolean readOnly) { + return load( id, optionalObject, session, LockOptions.NONE, readOnly ); + } + protected QueryParameters buildQueryParameters( Serializable id, Serializable[] ids, Object optionalObject, - LockOptions lockOptions) { + LockOptions lockOptions, + Boolean readOnly) { Type[] types = new Type[ids.length]; Arrays.fill( types, persister().getIdentifierType() ); @@ -65,6 +82,9 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader { qp.setOptionalEntityName( persister().getEntityName() ); qp.setOptionalId( id ); qp.setLockOptions( lockOptions ); + if ( readOnly != null ) { + qp.setReadOnly( readOnly ); + } return qp; } @@ -88,12 +108,13 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader { SharedSessionContractImplementor session, Serializable[] ids, Object optionalObject, - LockOptions lockOptions) { + LockOptions lockOptions, + Boolean readOnly) { if ( log.isDebugEnabled() ) { log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, session.getFactory() ) ); } - QueryParameters qp = buildQueryParameters( id, ids, optionalObject, lockOptions ); + QueryParameters qp = buildQueryParameters( id, ids, optionalObject, lockOptions, readOnly ); try { final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java index 7f1c91df2e..9ca9fe373b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java @@ -105,7 +105,8 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil id, persister.getMappedClass().getName(), lockOptions, - (EventSource) session + (EventSource) session, + null ); Object managedEntity = null; @@ -231,7 +232,8 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil id, persister.getMappedClass().getName(), lockOptions, - (EventSource) session + (EventSource) session, + null ); Object managedEntity = null; @@ -397,6 +399,16 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { + return load (id, optionalObject, session, lockOptions, null ); + } + + @Override + public Object load( + Serializable id, + Object optionalObject, + SharedSessionContractImplementor session, + LockOptions lockOptions, + Boolean readOnly) { final Serializable[] batch = session.getPersistenceContextInternal() .getBatchFetchQueue() .getEntityBatch( persister(), id, maxBatchSize, persister().getEntityMode() ); @@ -419,7 +431,7 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister(), idsToLoad, session.getFactory() ) ); } - QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions ); + QueryParameters qp = buildQueryParameters( id, idsToLoad, optionalObject, lockOptions, readOnly ); List results = dynamicLoader.doEntityBatchFetch( session, qp, idsToLoad ); // The EntityKey for any entity that is not found will remain in the batch. diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityLoader.java index aadba8dc41..4039bfe3b4 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/EntityLoader.java @@ -141,7 +141,11 @@ public class EntityLoader extends AbstractEntityLoader { } public Object loadByUniqueKey(SharedSessionContractImplementor session, Object key) { - return load( session, key, null, null, LockOptions.NONE ); + return loadByUniqueKey( session, key, null ); + } + + public Object loadByUniqueKey(SharedSessionContractImplementor session, Object key, Boolean readOnly) { + return load( session, key, null, null, LockOptions.NONE, readOnly ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java index fb6865e2fb..35f8038d03 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/LegacyBatchingEntityLoaderBuilder.java @@ -81,6 +81,11 @@ public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuild @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { + return load( id, optionalObject, session, lockOptions, null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { final Serializable[] batch = session.getPersistenceContextInternal() .getBatchFetchQueue() .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); @@ -99,7 +104,8 @@ public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuild persister().getEntityName(), id, persister(), - lockOptions + lockOptions, + readOnly ); // The EntityKey for any entity that is not found will remain in the batch. // Explicitly remove the EntityKeys for entities that were not found to diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java index 019b79f362..9c605d9fd0 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/PaddedBatchingEntityLoaderBuilder.java @@ -91,6 +91,11 @@ class PaddedBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder { @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { + return load( id, optionalObject, session, lockOptions, null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { final Serializable[] batch = session.getPersistenceContextInternal() .getBatchFetchQueue() .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); @@ -123,7 +128,7 @@ class PaddedBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder { idsToLoad[i] = id; } - return doBatchLoad( id, loaders[indexToUse], session, idsToLoad, optionalObject, lockOptions ); + return doBatchLoad( id, loaders[indexToUse], session, idsToLoad, optionalObject, lockOptions, readOnly ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java index 42683930c9..9763703486 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/UniqueEntityLoader.java @@ -29,6 +29,10 @@ public interface UniqueEntityLoader { @Deprecated Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) throws HibernateException; + default Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, Boolean readOnly) throws HibernateException { + return load( id, optionalObject, session ); + } + /** * Load an entity instance by id. If optionalObject is supplied (non-null, * the entity state is loaded into that object instance instead of instantiating a new one. @@ -47,4 +51,13 @@ public interface UniqueEntityLoader { Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions); + + default Object load( + Serializable id, + Object optionalObject, + SharedSessionContractImplementor session, + LockOptions lockOptions, + Boolean readOnly) { + return load( id, optionalObject, session, lockOptions ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java index 4944779dca..099e3b348d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/AbstractLoadPlanBasedEntityLoader.java @@ -139,8 +139,20 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan final String optionalEntityName, final Serializable optionalId, final EntityPersister persister, - LockOptions lockOptions) throws HibernateException { + final LockOptions lockOptions) throws HibernateException { + return loadEntityBatch( session, ids, idType, optionalObject, optionalEntityName, optionalId, persister, lockOptions, null ); + } + public final List loadEntityBatch( + final SharedSessionContractImplementor session, + final Serializable[] ids, + final Type idType, + final Object optionalObject, + final String optionalEntityName, + final Serializable optionalId, + final EntityPersister persister, + final LockOptions lockOptions, + final Boolean readOnly) throws HibernateException { if ( log.isDebugEnabled() ) { log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, getFactory() ) ); } @@ -153,7 +165,9 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan qp.setPositionalParameterTypes( types ); qp.setPositionalParameterValues( ids ); qp.setLockOptions( lockOptions ); - + if ( readOnly != null ) { + qp.setReadOnly( readOnly ); + } result = executeLoad( session, qp, @@ -178,11 +192,21 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) throws HibernateException { - return load( id, optionalObject, session, LockOptions.NONE ); + return load( id, optionalObject, session, (Boolean) null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, Boolean readOnly) throws HibernateException { + return load( id, optionalObject, session, LockOptions.NONE, readOnly ); } @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { + return load( id, optionalObject, session, lockOptions, null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { final Object result; try { @@ -193,7 +217,9 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan qp.setOptionalEntityName( entityPersister.getEntityName() ); qp.setOptionalId( id ); qp.setLockOptions( lockOptions ); - + if ( readOnly != null ) { + qp.setReadOnly( readOnly ); + } final List results = executeLoad( session, qp, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/BatchingEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/BatchingEntityLoader.java index 2aaa1842b7..b115d5735e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/BatchingEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/BatchingEntityLoader.java @@ -49,7 +49,7 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader { @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) { - return load( id, optionalObject, session, LockOptions.NONE ); + return load( id, optionalObject, session, LockOptions.NONE, null ); } protected QueryParameters buildQueryParameters( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java index cbe923f7ea..b80cb6f576 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/entity/plan/LegacyBatchingEntityLoaderBuilder.java @@ -94,6 +94,11 @@ public class LegacyBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoa @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { + return load( id, optionalObject, session, lockOptions, null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { final Serializable[] batch = session.getPersistenceContextInternal() .getBatchFetchQueue() .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); @@ -112,7 +117,8 @@ public class LegacyBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoa persister().getEntityName(), id, persister(), - lockOptions + lockOptions, + readOnly ); // The EntityKey for any entity that is not found will remain in the batch. // Explicitly remove the EntityKeys for entities that were not found to diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index dd45839ad5..ce1953c3dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -4370,13 +4370,22 @@ public abstract class AbstractEntityPersister */ public Object load(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) throws HibernateException { + return doLoad( id, optionalObject, lockOptions, session, null ); + } + public Object load(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) + throws HibernateException { + return doLoad( id, optionalObject, lockOptions, session, readOnly ); + } + + private Object doLoad(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) + throws HibernateException { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Fetching entity: {0}", MessageHelper.infoString( this, id, getFactory() ) ); } final UniqueEntityLoader loader = getAppropriateLoader( lockOptions, session ); - return loader.load( id, optionalObject, session, lockOptions ); + return loader.load( id, optionalObject, session, lockOptions, readOnly ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index 9e45d5410b..e214bb8c4f 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -376,12 +376,22 @@ public interface EntityPersister extends EntityDefinition { Object load(Serializable id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session) throws HibernateException; + default Object load(Serializable id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session, Boolean readOnly) + throws HibernateException { + return load( id, optionalObject, lockMode, session ); + } + /** * Load an instance of the persistent class. */ Object load(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) throws HibernateException; + default Object load(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) + throws HibernateException { + return load( id, optionalObject, lockOptions, session ); + } + /** * Performs a load of multiple entities (of this type) by identifier simultaneously. * @@ -546,7 +556,7 @@ public interface EntityPersister extends EntityDefinition { * Does this class have a natural id cache */ boolean hasNaturalIdCache(); - + /** * Get the NaturalId cache (optional operation) */ diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/NamedQueryLoader.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/NamedQueryLoader.java index ead92404b4..a8c69f40cc 100755 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/NamedQueryLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/NamedQueryLoader.java @@ -48,15 +48,25 @@ public final class NamedQueryLoader implements UniqueEntityLoader { @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { + return load( id, optionalObject, session, (Boolean) null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions, Boolean readOnly) { if ( lockOptions != null ) { LOG.debug( "Ignoring lock-options passed to named query loader" ); } - return load( id, optionalObject, session ); + return load( id, optionalObject, session, readOnly ); } @Override public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) { - LOG.debugf( "Loading entity: %s using named query: %s", persister.getEntityName(), queryName ); + return load( id, optionalObject, session, (Boolean) null ); + } + + @Override + public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, Boolean readOnly) { + LOG.debugf("Loading entity: %s using named query: %s", persister.getEntityName(), queryName); // IMPL NOTE: essentially we perform the named query (which loads the entity into the PC), and then // do an internal lookup of the entity from the PC. @@ -73,6 +83,9 @@ public final class NamedQueryLoader implements UniqueEntityLoader { query.setOptionalEntityName( persister.getEntityName() ); query.setOptionalObject( optionalObject ); query.setFlushMode( FlushMode.MANUAL ); + if ( readOnly != null ) { + query.setReadOnly( readOnly ); + } query.list(); // now look up the object we are really interested in! diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerTest.java index 13d0bebd86..6bc16d9565 100755 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/EntityManagerTest.java @@ -30,6 +30,7 @@ import org.hibernate.Session; import org.hibernate.cfg.Environment; import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.HibernateEntityManagerFactory; +import org.hibernate.jpa.QueryHints; import org.hibernate.stat.Statistics; import org.hibernate.testing.TestForIssue; import org.junit.Test; @@ -488,6 +489,49 @@ public class EntityManagerTest extends BaseEntityManagerFunctionalTestCase { } } + @Test + @TestForIssue( jiraKey = "HHH-11958" ) + public void testReadonlyHibernateQueryHint() { + + EntityManager em = getOrCreateEntityManager(); + em.getTransaction().begin(); + Wallet w = new Wallet(); + w.setBrand("Lacoste"); + w.setModel("Minimic"); + w.setSerial("0324"); + em.persist(w); + try { + em.getTransaction().commit(); + } finally { + em.close(); + } + + em = getOrCreateEntityManager(); + Map hints = new HashMap<>(); + hints.put(QueryHints.HINT_READONLY, true); + + em.getTransaction().begin(); + + Wallet fetchedWallet = em.find(Wallet.class, w.getSerial(), hints); + fetchedWallet.setBrand("Givenchy"); + + try { + em.getTransaction().commit(); + } finally { + em.close(); + } + + em = getOrCreateEntityManager(); + em.getTransaction().begin(); + fetchedWallet = em.find(Wallet.class, w.getSerial()); + try { + em.getTransaction().commit(); + assertEquals("Lacoste", fetchedWallet.getBrand()); + } finally { + em.close(); + } + } + private static class MyObject { public int value; } diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/PersisterClassProviderTest.java index 98bd45c81f..c9324eeb88 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/ejb3configuration/PersisterClassProviderTest.java @@ -264,7 +264,7 @@ public class PersisterClassProviderTest { public boolean hasNaturalIdentifier() { return false; } - + @Override public int[] getNaturalIdentifierProperties() { return new int[0]; @@ -280,7 +280,7 @@ public class PersisterClassProviderTest { SharedSessionContractImplementor session) { return null; } - + @Override public boolean hasNaturalIdCache() { return false;