HHH-11958 Make EntityManager.find() support QueryHints.HINT_READONLY

This commit is contained in:
Nathan Xu 2019-12-21 17:10:38 -05:00 committed by Steve Ebersole
parent 49fe4f6494
commit 00e9db2b8b
20 changed files with 289 additions and 49 deletions

View File

@ -52,12 +52,19 @@ public class LoadQueryInfluencers implements Serializable {
private final EffectiveEntityGraph effectiveEntityGraph = new EffectiveEntityGraph(); private final EffectiveEntityGraph effectiveEntityGraph = new EffectiveEntityGraph();
private Boolean readOnly;
public LoadQueryInfluencers() { public LoadQueryInfluencers() {
this( null ); this( null, null );
} }
public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory) { public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory) {
this(sessionFactory, null);
}
public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory, Boolean readOnly) {
this.sessionFactory = sessionFactory; this.sessionFactory = sessionFactory;
this.readOnly = readOnly;
} }
public SessionFactoryImplementor getSessionFactory() { public SessionFactoryImplementor getSessionFactory() {
@ -282,4 +289,11 @@ public class LoadQueryInfluencers implements Serializable {
effectiveEntityGraph.applyGraph( (RootGraphImplementor<?>) loadGraph, GraphSemantic.LOAD ); effectiveEntityGraph.applyGraph( (RootGraphImplementor<?>) loadGraph, GraphSemantic.LOAD );
} }
public Boolean getReadOnly() {
return readOnly;
}
public void setReadOnly(Boolean readOnly) {
this.readOnly = readOnly;
}
} }

View File

@ -570,7 +570,8 @@ public class DefaultLoadEventListener implements LoadEventListener {
event.getEntityId(), event.getEntityId(),
event.getInstanceToLoad(), event.getInstanceToLoad(),
event.getLockOptions(), event.getLockOptions(),
event.getSession() event.getSession(),
event.getReadOnly()
); );
final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics(); final StatisticsImplementor statistics = event.getSession().getFactory().getStatistics();

View File

@ -46,23 +46,24 @@ public class LoadEvent extends AbstractEvent {
private boolean isAssociationFetch; private boolean isAssociationFetch;
private Object result; private Object result;
private PostLoadEvent postLoadEvent; private PostLoadEvent postLoadEvent;
private Boolean readOnly;
public LoadEvent(Serializable entityId, Object instanceToLoad, EventSource source) { public LoadEvent(Serializable entityId, Object instanceToLoad, EventSource source, Boolean readOnly) {
this( entityId, null, instanceToLoad, DEFAULT_LOCK_OPTIONS, false, source ); this( entityId, null, instanceToLoad, DEFAULT_LOCK_OPTIONS, false, source, readOnly );
} }
public LoadEvent(Serializable entityId, String entityClassName, LockMode lockMode, EventSource source) { public LoadEvent(Serializable entityId, String entityClassName, LockMode lockMode, EventSource source, Boolean readOnly) {
this( entityId, entityClassName, null, lockMode, false, source ); this( entityId, entityClassName, null, lockMode, false, source, readOnly );
} }
public LoadEvent(Serializable entityId, String entityClassName, LockOptions lockOptions, EventSource source) { public LoadEvent(Serializable entityId, String entityClassName, LockOptions lockOptions, EventSource source, Boolean readOnly) {
this( entityId, entityClassName, null, lockOptions, false, source ); this( entityId, entityClassName, null, lockOptions, false, source, readOnly );
} }
public LoadEvent(Serializable entityId, String entityClassName, boolean isAssociationFetch, EventSource source) { public LoadEvent(Serializable entityId, String entityClassName, boolean isAssociationFetch, EventSource source, Boolean readOnly) {
this( entityId, entityClassName, null, DEFAULT_LOCK_OPTIONS, isAssociationFetch, source ); this( entityId, entityClassName, null, DEFAULT_LOCK_OPTIONS, isAssociationFetch, source, readOnly );
} }
public boolean isAssociationFetch() { public boolean isAssociationFetch() {
return isAssociationFetch; return isAssociationFetch;
} }
@ -73,10 +74,11 @@ public class LoadEvent extends AbstractEvent {
Object instanceToLoad, Object instanceToLoad,
LockMode lockMode, LockMode lockMode,
boolean isAssociationFetch, boolean isAssociationFetch,
EventSource source) { EventSource source,
Boolean readOnly) {
this( entityId, entityClassName, instanceToLoad, this( entityId, entityClassName, instanceToLoad,
lockMode == DEFAULT_LOCK_MODE ? DEFAULT_LOCK_OPTIONS : new LockOptions().setLockMode( lockMode ), lockMode == DEFAULT_LOCK_MODE ? DEFAULT_LOCK_OPTIONS : new LockOptions().setLockMode( lockMode ),
isAssociationFetch, source ); isAssociationFetch, source, readOnly );
} }
private LoadEvent( private LoadEvent(
@ -85,7 +87,8 @@ public class LoadEvent extends AbstractEvent {
Object instanceToLoad, Object instanceToLoad,
LockOptions lockOptions, LockOptions lockOptions,
boolean isAssociationFetch, boolean isAssociationFetch,
EventSource source) { EventSource source,
Boolean readOnly) {
super( source ); super( source );
@ -106,6 +109,7 @@ public class LoadEvent extends AbstractEvent {
this.lockOptions = lockOptions; this.lockOptions = lockOptions;
this.isAssociationFetch = isAssociationFetch; this.isAssociationFetch = isAssociationFetch;
this.postLoadEvent = new PostLoadEvent( source ); this.postLoadEvent = new PostLoadEvent( source );
this.readOnly = readOnly;
} }
public Serializable getEntityId() { public Serializable getEntityId() {
@ -190,4 +194,12 @@ public class LoadEvent extends AbstractEvent {
public void setPostLoadEvent(PostLoadEvent postLoadEvent) { public void setPostLoadEvent(PostLoadEvent postLoadEvent) {
this.postLoadEvent = postLoadEvent; this.postLoadEvent = postLoadEvent;
} }
public Boolean getReadOnly() {
return readOnly;
}
public void setReadOnly(Boolean readOnly) {
this.readOnly = readOnly;
}
} }

View File

@ -928,7 +928,7 @@ public final class SessionImpl
LoadEvent event = loadEvent; LoadEvent event = loadEvent;
loadEvent = null; loadEvent = null;
if ( event == null ) { if ( event == null ) {
event = new LoadEvent( id, object, this ); event = new LoadEvent( id, object, this, getReadOnlyFromLoadQueryInfluencers() );
} }
else { else {
event.setEntityClassName( null ); event.setEntityClassName( null );
@ -1059,7 +1059,7 @@ public final class SessionImpl
*/ */
private LoadEvent recycleEventInstance(final LoadEvent event, final Serializable id, final String entityName) { private LoadEvent recycleEventInstance(final LoadEvent event, final Serializable id, final String entityName) {
if ( event == null ) { if ( event == null ) {
return new LoadEvent( id, entityName, true, this ); return new LoadEvent( id, entityName, true, this, getReadOnlyFromLoadQueryInfluencers() );
} }
else { else {
event.setEntityClassName( entityName ); event.setEntityClassName( entityName );
@ -2748,12 +2748,12 @@ public final class SessionImpl
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected T doGetReference(Serializable id) { protected T doGetReference(Serializable id) {
if ( this.lockOptions != null ) { 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 ); fireLoad( event, LoadEventListener.LOAD );
return (T) event.getResult(); 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; boolean success = false;
try { try {
fireLoad( event, LoadEventListener.LOAD ); fireLoad( event, LoadEventListener.LOAD );
@ -2784,12 +2784,12 @@ public final class SessionImpl
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected final T doLoad(Serializable id) { protected final T doLoad(Serializable id) {
if ( this.lockOptions != null ) { 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 ); fireLoad( event, LoadEventListener.GET );
return (T) event.getResult(); 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; boolean success = false;
try { try {
fireLoad( event, LoadEventListener.GET ); fireLoad( event, LoadEventListener.GET );
@ -3311,7 +3311,8 @@ public final class SessionImpl
try { try {
getLoadQueryInfluencers().getEffectiveEntityGraph().applyConfiguredGraph( properties ); getLoadQueryInfluencers().getEffectiveEntityGraph().applyConfiguredGraph( properties );
Boolean readOnly = properties == null ? null : (Boolean) properties.get( QueryHints.HINT_READONLY );
getLoadQueryInfluencers().setReadOnly( readOnly );
final IdentifierLoadAccess<T> loadAccess = byId( entityClass ); final IdentifierLoadAccess<T> loadAccess = byId( entityClass );
loadAccess.with( determineAppropriateLocalCacheMode( properties ) ); loadAccess.with( determineAppropriateLocalCacheMode( properties ) );
@ -3366,6 +3367,7 @@ public final class SessionImpl
} }
finally { finally {
getLoadQueryInfluencers().getEffectiveEntityGraph().clear(); getLoadQueryInfluencers().getEffectiveEntityGraph().clear();
getLoadQueryInfluencers().setReadOnly( null );
} }
} }
@ -3786,4 +3788,12 @@ public final class SessionImpl
( (FilterImpl) loadQueryInfluencers.getEnabledFilter( filterName ) ).afterDeserialize( getFactory() ); ( (FilterImpl) loadQueryInfluencers.getEnabledFilter( filterName ) ).afterDeserialize( getFactory() );
} }
} }
private Boolean getReadOnlyFromLoadQueryInfluencers() {
Boolean readOnly = null;
if ( loadQueryInfluencers != null ) {
readOnly = loadQueryInfluencers.getReadOnly();
}
return readOnly;
}
} }

View File

@ -2389,7 +2389,8 @@ public abstract class Loader {
final String optionalEntityName, final String optionalEntityName,
final Serializable optionalIdentifier, final Serializable optionalIdentifier,
final EntityPersister persister, final EntityPersister persister,
LockOptions lockOptions) throws HibernateException { final LockOptions lockOptions,
final Boolean readOnly) throws HibernateException {
if ( LOG.isDebugEnabled() ) { if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Loading entity: %s", MessageHelper.infoString( persister, id, identifierType, getFactory() ) ); LOG.debugf( "Loading entity: %s", MessageHelper.infoString( persister, id, identifierType, getFactory() ) );
} }
@ -2403,6 +2404,9 @@ public abstract class Loader {
qp.setOptionalEntityName( optionalEntityName ); qp.setOptionalEntityName( optionalEntityName );
qp.setOptionalId( optionalIdentifier ); qp.setOptionalId( optionalIdentifier );
qp.setLockOptions( lockOptions ); qp.setLockOptions( lockOptions );
if ( readOnly != null ) {
qp.setReadOnly( readOnly );
}
result = doQueryAndInitializeNonLazyCollections( session, qp, false ); result = doQueryAndInitializeNonLazyCollections( session, qp, false );
} }
catch (SQLException sqle) { catch (SQLException sqle) {
@ -2477,6 +2481,22 @@ public abstract class Loader {
final Serializable optionalId, final Serializable optionalId,
final EntityPersister persister, final EntityPersister persister,
LockOptions lockOptions) throws HibernateException { 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() ) { if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, getFactory() ) ); LOG.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, getFactory() ) );
} }
@ -2492,6 +2512,9 @@ public abstract class Loader {
qp.setOptionalEntityName( optionalEntityName ); qp.setOptionalEntityName( optionalEntityName );
qp.setOptionalId( optionalId ); qp.setOptionalId( optionalId );
qp.setLockOptions( lockOptions ); qp.setLockOptions( lockOptions );
if ( readOnly != null ) {
qp.setReadOnly( readOnly );
}
result = doQueryAndInitializeNonLazyCollections( session, qp, false ); result = doQueryAndInitializeNonLazyCollections( session, qp, false );
} }
catch (SQLException sqle) { catch (SQLException sqle) {

View File

@ -8,8 +8,6 @@ package org.hibernate.loader.entity;
import java.io.Serializable; import java.io.Serializable;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List; import java.util.List;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
@ -46,12 +44,23 @@ public abstract class AbstractEntityLoader
@Override @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) { public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) {
// this form is deprecated! // 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 @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { 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( protected Object load(
@ -59,7 +68,8 @@ public abstract class AbstractEntityLoader
Object id, Object id,
Object optionalObject, Object optionalObject,
Serializable optionalId, Serializable optionalId,
LockOptions lockOptions) { LockOptions lockOptions,
Boolean readOnly) {
List list = loadEntity( List list = loadEntity(
session, session,
@ -69,7 +79,8 @@ public abstract class AbstractEntityLoader
entityName, entityName,
optionalId, optionalId,
persister, persister,
lockOptions lockOptions,
readOnly
); );
if ( list.size()==1 ) { if ( list.size()==1 ) {

View File

@ -11,6 +11,7 @@ import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.engine.internal.BatchFetchQueueHelper; import org.hibernate.engine.internal.BatchFetchQueueHelper;
import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.QueryParameters;
@ -50,11 +51,27 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader {
return load( id, optionalObject, session, LockOptions.NONE ); 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( protected QueryParameters buildQueryParameters(
Serializable id, Serializable id,
Serializable[] ids, Serializable[] ids,
Object optionalObject, Object optionalObject,
LockOptions lockOptions) { LockOptions lockOptions,
Boolean readOnly) {
Type[] types = new Type[ids.length]; Type[] types = new Type[ids.length];
Arrays.fill( types, persister().getIdentifierType() ); Arrays.fill( types, persister().getIdentifierType() );
@ -65,6 +82,9 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader {
qp.setOptionalEntityName( persister().getEntityName() ); qp.setOptionalEntityName( persister().getEntityName() );
qp.setOptionalId( id ); qp.setOptionalId( id );
qp.setLockOptions( lockOptions ); qp.setLockOptions( lockOptions );
if ( readOnly != null ) {
qp.setReadOnly( readOnly );
}
return qp; return qp;
} }
@ -88,12 +108,13 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader {
SharedSessionContractImplementor session, SharedSessionContractImplementor session,
Serializable[] ids, Serializable[] ids,
Object optionalObject, Object optionalObject,
LockOptions lockOptions) { LockOptions lockOptions,
Boolean readOnly) {
if ( log.isDebugEnabled() ) { if ( log.isDebugEnabled() ) {
log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, session.getFactory() ) ); 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 { try {
final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false ); final List results = loaderToUse.doQueryAndInitializeNonLazyCollections( session, qp, false );

View File

@ -105,7 +105,8 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil
id, id,
persister.getMappedClass().getName(), persister.getMappedClass().getName(),
lockOptions, lockOptions,
(EventSource) session (EventSource) session,
null
); );
Object managedEntity = null; Object managedEntity = null;
@ -231,7 +232,8 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil
id, id,
persister.getMappedClass().getName(), persister.getMappedClass().getName(),
lockOptions, lockOptions,
(EventSource) session (EventSource) session,
null
); );
Object managedEntity = null; Object managedEntity = null;
@ -397,6 +399,16 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil
Object optionalObject, Object optionalObject,
SharedSessionContractImplementor session, SharedSessionContractImplementor session,
LockOptions lockOptions) { 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() final Serializable[] batch = session.getPersistenceContextInternal()
.getBatchFetchQueue() .getBatchFetchQueue()
.getEntityBatch( persister(), id, maxBatchSize, persister().getEntityMode() ); .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() ) ); 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 ); List results = dynamicLoader.doEntityBatchFetch( session, qp, idsToLoad );
// The EntityKey for any entity that is not found will remain in the batch. // The EntityKey for any entity that is not found will remain in the batch.

View File

@ -141,7 +141,11 @@ public class EntityLoader extends AbstractEntityLoader {
} }
public Object loadByUniqueKey(SharedSessionContractImplementor session, Object key) { 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 @Override

View File

@ -81,6 +81,11 @@ public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuild
@Override @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { 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() final Serializable[] batch = session.getPersistenceContextInternal()
.getBatchFetchQueue() .getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
@ -99,7 +104,8 @@ public class LegacyBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuild
persister().getEntityName(), persister().getEntityName(),
id, id,
persister(), persister(),
lockOptions lockOptions,
readOnly
); );
// The EntityKey for any entity that is not found will remain in the batch. // 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 // Explicitly remove the EntityKeys for entities that were not found to

View File

@ -91,6 +91,11 @@ class PaddedBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
@Override @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { 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() final Serializable[] batch = session.getPersistenceContextInternal()
.getBatchFetchQueue() .getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
@ -123,7 +128,7 @@ class PaddedBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuilder {
idsToLoad[i] = id; idsToLoad[i] = id;
} }
return doBatchLoad( id, loaders[indexToUse], session, idsToLoad, optionalObject, lockOptions ); return doBatchLoad( id, loaders[indexToUse], session, idsToLoad, optionalObject, lockOptions, readOnly );
} }
} }

View File

@ -29,6 +29,10 @@ public interface UniqueEntityLoader {
@Deprecated @Deprecated
Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) throws HibernateException; 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 <tt>optionalObject</tt> is supplied (non-<tt>null</tt>, * Load an entity instance by id. If <tt>optionalObject</tt> is supplied (non-<tt>null</tt>,
* the entity state is loaded into that object instance instead of instantiating a new one. * the entity state is loaded into that object instance instead of instantiating a new one.
@ -47,4 +51,13 @@ public interface UniqueEntityLoader {
Object optionalObject, Object optionalObject,
SharedSessionContractImplementor session, SharedSessionContractImplementor session,
LockOptions lockOptions); LockOptions lockOptions);
default Object load(
Serializable id,
Object optionalObject,
SharedSessionContractImplementor session,
LockOptions lockOptions,
Boolean readOnly) {
return load( id, optionalObject, session, lockOptions );
}
} }

View File

@ -139,8 +139,20 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan
final String optionalEntityName, final String optionalEntityName,
final Serializable optionalId, final Serializable optionalId,
final EntityPersister persister, 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() ) { if ( log.isDebugEnabled() ) {
log.debugf( "Batch loading entity: %s", MessageHelper.infoString( persister, ids, getFactory() ) ); 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.setPositionalParameterTypes( types );
qp.setPositionalParameterValues( ids ); qp.setPositionalParameterValues( ids );
qp.setLockOptions( lockOptions ); qp.setLockOptions( lockOptions );
if ( readOnly != null ) {
qp.setReadOnly( readOnly );
}
result = executeLoad( result = executeLoad(
session, session,
qp, qp,
@ -178,11 +192,21 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan
@Override @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) throws HibernateException { 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 @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { 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; final Object result;
try { try {
@ -193,7 +217,9 @@ public abstract class AbstractLoadPlanBasedEntityLoader extends AbstractLoadPlan
qp.setOptionalEntityName( entityPersister.getEntityName() ); qp.setOptionalEntityName( entityPersister.getEntityName() );
qp.setOptionalId( id ); qp.setOptionalId( id );
qp.setLockOptions( lockOptions ); qp.setLockOptions( lockOptions );
if ( readOnly != null ) {
qp.setReadOnly( readOnly );
}
final List results = executeLoad( final List results = executeLoad(
session, session,
qp, qp,

View File

@ -49,7 +49,7 @@ public abstract class BatchingEntityLoader implements UniqueEntityLoader {
@Override @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) { 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( protected QueryParameters buildQueryParameters(

View File

@ -94,6 +94,11 @@ public class LegacyBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoa
@Override @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { 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() final Serializable[] batch = session.getPersistenceContextInternal()
.getBatchFetchQueue() .getBatchFetchQueue()
.getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() ); .getEntityBatch( persister(), id, batchSizes[0], persister().getEntityMode() );
@ -112,7 +117,8 @@ public class LegacyBatchingEntityLoaderBuilder extends AbstractBatchingEntityLoa
persister().getEntityName(), persister().getEntityName(),
id, id,
persister(), persister(),
lockOptions lockOptions,
readOnly
); );
// The EntityKey for any entity that is not found will remain in the batch. // 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 // Explicitly remove the EntityKeys for entities that were not found to

View File

@ -4370,13 +4370,22 @@ public abstract class AbstractEntityPersister
*/ */
public Object load(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) public Object load(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session)
throws HibernateException { 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() ) { if ( LOG.isTraceEnabled() ) {
LOG.tracev( "Fetching entity: {0}", MessageHelper.infoString( this, id, getFactory() ) ); LOG.tracev( "Fetching entity: {0}", MessageHelper.infoString( this, id, getFactory() ) );
} }
final UniqueEntityLoader loader = getAppropriateLoader( lockOptions, session ); final UniqueEntityLoader loader = getAppropriateLoader( lockOptions, session );
return loader.load( id, optionalObject, session, lockOptions ); return loader.load( id, optionalObject, session, lockOptions, readOnly );
} }
@Override @Override

View File

@ -376,12 +376,22 @@ public interface EntityPersister extends EntityDefinition {
Object load(Serializable id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session) Object load(Serializable id, Object optionalObject, LockMode lockMode, SharedSessionContractImplementor session)
throws HibernateException; 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. * Load an instance of the persistent class.
*/ */
Object load(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) Object load(Serializable id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session)
throws HibernateException; 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. * 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 * Does this class have a natural id cache
*/ */
boolean hasNaturalIdCache(); boolean hasNaturalIdCache();
/** /**
* Get the NaturalId cache (optional operation) * Get the NaturalId cache (optional operation)
*/ */

View File

@ -48,15 +48,25 @@ public final class NamedQueryLoader implements UniqueEntityLoader {
@Override @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session, LockOptions lockOptions) { 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 ) { if ( lockOptions != null ) {
LOG.debug( "Ignoring lock-options passed to named query loader" ); LOG.debug( "Ignoring lock-options passed to named query loader" );
} }
return load( id, optionalObject, session ); return load( id, optionalObject, session, readOnly );
} }
@Override @Override
public Object load(Serializable id, Object optionalObject, SharedSessionContractImplementor session) { 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 // 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. // 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.setOptionalEntityName( persister.getEntityName() );
query.setOptionalObject( optionalObject ); query.setOptionalObject( optionalObject );
query.setFlushMode( FlushMode.MANUAL ); query.setFlushMode( FlushMode.MANUAL );
if ( readOnly != null ) {
query.setReadOnly( readOnly );
}
query.list(); query.list();
// now look up the object we are really interested in! // now look up the object we are really interested in!

View File

@ -30,6 +30,7 @@ import org.hibernate.Session;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.HibernateEntityManagerFactory; import org.hibernate.jpa.HibernateEntityManagerFactory;
import org.hibernate.jpa.QueryHints;
import org.hibernate.stat.Statistics; import org.hibernate.stat.Statistics;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.junit.Test; 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<String, Object> 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 { private static class MyObject {
public int value; public int value;
} }

View File

@ -264,7 +264,7 @@ public class PersisterClassProviderTest {
public boolean hasNaturalIdentifier() { public boolean hasNaturalIdentifier() {
return false; return false;
} }
@Override @Override
public int[] getNaturalIdentifierProperties() { public int[] getNaturalIdentifierProperties() {
return new int[0]; return new int[0];
@ -280,7 +280,7 @@ public class PersisterClassProviderTest {
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
return null; return null;
} }
@Override @Override
public boolean hasNaturalIdCache() { public boolean hasNaturalIdCache() {
return false; return false;