HHH-11958 Make EntityManager.find() support QueryHints.HINT_READONLY
This commit is contained in:
parent
49fe4f6494
commit
00e9db2b8b
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
*/
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue