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 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;
}
}

View File

@ -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();

View File

@ -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;
}
}

View File

@ -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<T> 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;
}
}

View File

@ -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) {

View File

@ -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 ) {

View File

@ -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 );

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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 );
}
}

View File

@ -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 <tt>optionalObject</tt> is supplied (non-<tt>null</tt>,
* 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 );
}
}

View File

@ -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,

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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)
*/

View File

@ -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!

View File

@ -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<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 {
public int value;
}

View File

@ -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;