diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java index ae49005e7f..620cef6886 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderDynamicBatch.java @@ -21,6 +21,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryOptionsAdapter; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; @@ -55,7 +56,7 @@ public SingleIdEntityLoaderDynamicBatch( } @Override - public T load(Object pkValue, LockOptions lockOptions, SharedSessionContractImplementor session) { + public T load(Object pkValue, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { final Object[] batchIds = session.getPersistenceContextInternal() .getBatchFetchQueue() .getBatchLoadableEntityIds( getLoadable(), pkValue, maxBatchSize ); @@ -64,7 +65,7 @@ public T load(Object pkValue, LockOptions lockOptions, SharedSessionContractImpl if ( numberOfIds <= 1 ) { initializeSingleIdLoaderIfNeeded( session ); - final T result = singleIdLoader.load( pkValue, lockOptions, session ); + final T result = singleIdLoader.load( pkValue, lockOptions, readOnly, session ); if ( result == null ) { // There was no entity with the specified ID. Make sure the EntityKey does not remain // in the batch to avoid including it in future batches that get executed. @@ -147,7 +148,12 @@ public SharedSessionContractImplementor getSession() { @Override public QueryOptions getQueryOptions() { - return QueryOptions.NONE; + return new QueryOptionsAdapter() { + @Override + public Boolean isReadOnly() { + return readOnly; + } + }; } @Override @@ -181,9 +187,10 @@ public T load( Object pkValue, Object entityInstance, LockOptions lockOptions, + Boolean readOnly, SharedSessionContractImplementor session) { initializeSingleIdLoaderIfNeeded( session ); - return singleIdLoader.load( pkValue, entityInstance, lockOptions, session ); + return singleIdLoader.load( pkValue, entityInstance, lockOptions, readOnly, session ); } private void initializeSingleIdLoaderIfNeeded(SharedSessionContractImplementor session) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderProvidedQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderProvidedQueryImpl.java index 3dde4b38a0..5142b1963c 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderProvidedQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderProvidedQueryImpl.java @@ -57,7 +57,7 @@ public EntityMappingType getLoadable() { } @Override - public T load(Object pkValue, LockOptions lockOptions, SharedSessionContractImplementor session) { + public T load(Object pkValue, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { //noinspection unchecked final QueryImplementor query = namedQueryMemento.toQuery( session, @@ -74,11 +74,12 @@ public T load( Object pkValue, Object entityInstance, LockOptions lockOptions, + Boolean readOnly, SharedSessionContractImplementor session) { if ( entityInstance != null ) { throw new UnsupportedOperationException( ); } - return load( pkValue, lockOptions, session ); + return load( pkValue, lockOptions, readOnly, session ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderStandardImpl.java index 7865ac5f23..8aa7ea11b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdEntityLoaderStandardImpl.java @@ -52,14 +52,14 @@ public void prepare() { } @Override - public T load(Object key, LockOptions lockOptions, SharedSessionContractImplementor session) { + public T load(Object key, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) { final SingleIdLoadPlan loadPlan = resolveLoadPlan( lockOptions, session.getLoadQueryInfluencers(), session.getFactory() ); - return loadPlan.load( key, lockOptions, null, session ); + return loadPlan.load( key, lockOptions, readOnly, session ); } @Override @@ -67,6 +67,7 @@ public T load( Object key, Object entityInstance, LockOptions lockOptions, + Boolean readOnly, SharedSessionContractImplementor session) { final SingleIdLoadPlan loadPlan = resolveLoadPlan( lockOptions, @@ -74,7 +75,7 @@ public T load( session.getFactory() ); - return loadPlan.load( key, lockOptions, entityInstance, session ); + return loadPlan.load( key, lockOptions, entityInstance, readOnly, session ); } @Internal diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java index 2ea2d131a2..159cde0926 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdLoadPlan.java @@ -18,6 +18,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryOptionsAdapter; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; @@ -73,14 +74,23 @@ public SelectStatement getSqlAst() { T load( Object restrictedValue, LockOptions lockOptions, + Boolean readOnly, SharedSessionContractImplementor session) { - return load( restrictedValue, lockOptions, null, session ); + return load( restrictedValue, lockOptions, null, readOnly, session ); + } + + T load( + Object restrictedValue, + LockOptions lockOptions, + SharedSessionContractImplementor session) { + return load( restrictedValue, lockOptions, null, null, session ); } T load( Object restrictedValue, LockOptions lockOptions, Object entityInstance, + Boolean readOnly, SharedSessionContractImplementor session) { final SessionFactoryImplementor sessionFactory = session.getFactory(); final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); @@ -145,7 +155,12 @@ public Object getEntityId() { @Override public QueryOptions getQueryOptions() { - return QueryOptions.NONE; + return new QueryOptionsAdapter() { + @Override + public Boolean isReadOnly() { + return readOnly; + } + }; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java index 78f07ceead..004984b079 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java @@ -22,6 +22,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryOptionsAdapter; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; @@ -57,6 +58,7 @@ public EntityMappingType getLoadable() { public T load( Object ukValue, LockOptions lockOptions, + Boolean readOnly, SharedSessionContractImplementor session) { final SessionFactoryImplementor sessionFactory = session.getFactory(); @@ -117,7 +119,12 @@ public SharedSessionContractImplementor getSession() { @Override public QueryOptions getQueryOptions() { - return QueryOptions.NONE; + return new QueryOptionsAdapter() { + @Override + public Boolean isReadOnly() { + return readOnly; + } + }; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/CollectionLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/CollectionLoader.java index 9c85ac1c95..8421838831 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/CollectionLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/CollectionLoader.java @@ -23,4 +23,7 @@ public interface CollectionLoader extends Loader { * Load a collection by its key (not necessarily the same as its owner's PK). */ PersistentCollection load(Object key, SharedSessionContractImplementor session); + + //TODO support 'readOnly' collection loading + } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleEntityLoader.java index 0a25c46eb2..45cc0e29e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleEntityLoader.java @@ -22,5 +22,9 @@ public interface SingleEntityLoader extends Loader { /** * Load an entity by a primary or unique key value. */ - T load(Object key, LockOptions lockOptions, SharedSessionContractImplementor session); + T load(Object key, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session); + + default T load(Object key, LockOptions lockOptions, SharedSessionContractImplementor session) { + return load( key, lockOptions, session ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleIdEntityLoader.java index c907aa1b35..334194a2b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleIdEntityLoader.java @@ -19,14 +19,18 @@ public interface SingleIdEntityLoader extends SingleEntityLoader { * Load by primary key value */ @Override - T load(Object pkValue, LockOptions lockOptions, SharedSessionContractImplementor session); + T load(Object pkValue, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session); /** * Load by primary key value, populating the passed entity instance. Used to initialize an uninitialized * bytecode-proxy or {@link org.hibernate.event.spi.LoadEvent} handling. * The passed instance is the enhanced proxy or the entity to be loaded. */ - T load(Object pkValue, Object entityInstance, LockOptions lockOptions, SharedSessionContractImplementor session); + T load(Object pkValue, Object entityInstance, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session); + + default T load(Object pkValue, Object entityInstance, LockOptions lockOptions, SharedSessionContractImplementor session) { + return load( pkValue, entityInstance, lockOptions, null, session ); + } /** * Load database snapshot by primary key value diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleUniqueKeyEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleUniqueKeyEntityLoader.java index 50bbc20510..41237e43af 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleUniqueKeyEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/SingleUniqueKeyEntityLoader.java @@ -19,7 +19,7 @@ public interface SingleUniqueKeyEntityLoader extends SingleEntityLoader { * Load by unique key value */ @Override - T load(Object ukValue, LockOptions lockOptions, SharedSessionContractImplementor session); + T load(Object ukValue, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session); /** * Resolve the matching id diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 7e6bafc60f..8b31eeb1cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -2617,7 +2617,15 @@ public Object loadByUniqueKey( String propertyName, Object uniqueKey, SharedSessionContractImplementor session) throws HibernateException { - return getUniqueKeyLoader( propertyName ).load( uniqueKey, LockOptions.READ, session ); + return loadByUniqueKey( propertyName, uniqueKey, null, session ); + } + + public Object loadByUniqueKey( + String propertyName, + Object uniqueKey, + Boolean readOnly, + SharedSessionContractImplementor session) throws HibernateException { + return getUniqueKeyLoader( propertyName ).load( uniqueKey, LockOptions.READ, readOnly, session ); } private Map> uniqueKeyLoadersNew; @@ -4459,25 +4467,25 @@ public Object load(Object id, Object optionalObject, LockMode lockMode, SharedSe */ public Object load(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session) throws HibernateException { - return doLoad( id, optionalObject, lockOptions, session, null ); + return doLoad( id, optionalObject, lockOptions, null, session ); } public Object load(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) throws HibernateException { - return doLoad( id, optionalObject, lockOptions, session, readOnly ); + return doLoad( id, optionalObject, lockOptions, readOnly, session ); } - private Object doLoad(Object id, Object optionalObject, LockOptions lockOptions, SharedSessionContractImplementor session, Boolean readOnly) + private Object doLoad(Object id, Object optionalObject, LockOptions lockOptions, Boolean readOnly, SharedSessionContractImplementor session) throws HibernateException { if ( LOG.isTraceEnabled() ) { LOG.tracev( "Fetching entity: {0}", MessageHelper.infoString( this, id, getFactory() ) ); } if ( optionalObject == null ) { - return singleIdEntityLoader.load( id, lockOptions, session ); + return singleIdEntityLoader.load( id, lockOptions, readOnly, session ); } else { - return singleIdEntityLoader.load( id, optionalObject, lockOptions, session ); + return singleIdEntityLoader.load( id, optionalObject, lockOptions, readOnly, session ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ScrollableResultsIterator.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ScrollableResultsIterator.java index dedabe93f3..7b4e9f22e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ScrollableResultsIterator.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ScrollableResultsIterator.java @@ -16,10 +16,10 @@ * @since 5.2 */ @Incubating -public class ScrollableResultsIterator implements CloseableIterator { - private final ScrollableResultsImplementor scrollableResults; +public class ScrollableResultsIterator implements CloseableIterator { + private final ScrollableResultsImplementor scrollableResults; - public ScrollableResultsIterator(ScrollableResultsImplementor scrollableResults) { + public ScrollableResultsIterator(ScrollableResultsImplementor scrollableResults) { this.scrollableResults = scrollableResults; } @@ -34,7 +34,6 @@ public boolean hasNext() { } @Override - @SuppressWarnings("unchecked") public T next() { return (T) scrollableResults.get(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryOptions.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryOptions.java index 1f55605f2c..b17f58d8ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryOptions.java @@ -7,7 +7,6 @@ package org.hibernate.query.spi; import java.sql.Statement; -import java.util.Collections; import java.util.List; import javax.persistence.CacheRetrieveMode; import javax.persistence.CacheStoreMode; @@ -184,85 +183,6 @@ default boolean hasLimit() { /** * Singleton access */ - QueryOptions NONE = new QueryOptions() { - @Override - public Limit getLimit() { - return Limit.NONE; - } - - @Override - public Integer getFetchSize() { - return null; - } - - @Override - public String getComment() { - return null; - } - - @Override - public LockOptions getLockOptions() { - return LockOptions.NONE; - } - - @Override - public List getDatabaseHints() { - return Collections.emptyList(); - } - - @Override - public Integer getTimeout() { - return null; - } - - @Override - public FlushMode getFlushMode() { - return null; - } - - @Override - public Boolean isReadOnly() { - return null; - } - - @Override - public CacheRetrieveMode getCacheRetrieveMode() { - return CacheRetrieveMode.BYPASS; - } - - @Override - public CacheStoreMode getCacheStoreMode() { - return CacheStoreMode.BYPASS; - } - - @Override - public CacheMode getCacheMode() { - return CacheMode.IGNORE; - } - - @Override - public Boolean isResultCachingEnabled() { - return null; - } - - @Override - public String getResultCacheRegionName() { - return null; - } - - @Override - public AppliedGraph getAppliedGraph() { - return null; - } - - @Override - public TupleTransformer getTupleTransformer() { - return null; - } - - @Override - public ResultListTransformer getResultListTransformer() { - return null; - } + QueryOptions NONE = new QueryOptionsAdapter() { }; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/spi/QueryOptionsAdapter.java b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryOptionsAdapter.java new file mode 100644 index 0000000000..421fecfd16 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/spi/QueryOptionsAdapter.java @@ -0,0 +1,103 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.query.spi; + +import java.util.Collections; +import java.util.List; +import javax.persistence.CacheRetrieveMode; +import javax.persistence.CacheStoreMode; + +import org.hibernate.CacheMode; +import org.hibernate.FlushMode; +import org.hibernate.LockOptions; +import org.hibernate.graph.spi.AppliedGraph; +import org.hibernate.query.Limit; +import org.hibernate.query.ResultListTransformer; +import org.hibernate.query.TupleTransformer; + +public abstract class QueryOptionsAdapter implements QueryOptions { + + @Override + public Limit getLimit() { + return Limit.NONE; + } + + @Override + public Integer getFetchSize() { + return null; + } + + @Override + public String getComment() { + return null; + } + + @Override + public LockOptions getLockOptions() { + return LockOptions.NONE; + } + + @Override + public List getDatabaseHints() { + return Collections.emptyList(); + } + + @Override + public Integer getTimeout() { + return null; + } + + @Override + public FlushMode getFlushMode() { + return null; + } + + @Override + public Boolean isReadOnly() { + return null; + } + + @Override + public CacheRetrieveMode getCacheRetrieveMode() { + return CacheRetrieveMode.BYPASS; + } + + @Override + public CacheStoreMode getCacheStoreMode() { + return CacheStoreMode.BYPASS; + } + + @Override + public CacheMode getCacheMode() { + return CacheMode.IGNORE; + } + + @Override + public Boolean isResultCachingEnabled() { + return null; + } + + @Override + public String getResultCacheRegionName() { + return null; + } + + @Override + public AppliedGraph getAppliedGraph() { + return null; + } + + @Override + public TupleTransformer getTupleTransformer() { + return null; + } + + @Override + public ResultListTransformer getResultListTransformer() { + return null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java index 4f106e518c..0378a6cf4b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectExecutorStandardImpl.java @@ -19,6 +19,7 @@ import org.hibernate.ScrollMode; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsCache; +import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.loader.ast.spi.AfterLoadAction; import org.hibernate.query.internal.ScrollableResultsIterator; import org.hibernate.query.spi.ScrollableResultsImplementor; @@ -78,7 +79,7 @@ public List list( .getJdbcCoordinator() .getStatementPreparer() .prepareStatement( sql ), - ListResultsConsumer.instance(uniqueFilter) + ListResultsConsumer.instance( uniqueFilter ) ); } @@ -130,6 +131,37 @@ private T executeQuery( RowTransformer rowTransformer, Function statementCreator, ResultsConsumer resultsConsumer) { + final PersistenceContext persistenceContext = executionContext.getSession().getPersistenceContext(); + boolean defaultReadOnlyOrig = persistenceContext.isDefaultReadOnly(); + Boolean readOnly = executionContext.getQueryOptions().isReadOnly(); + if ( readOnly != null ) { + // The read-only/modifiable mode for the query was explicitly set. + // Temporarily set the default read-only/modifiable setting to the query's setting. + persistenceContext.setDefaultReadOnly( readOnly ); + } + try { + return doExecuteQuery( + jdbcSelect, + jdbcParameterBindings, + executionContext, + rowTransformer, + statementCreator, + resultsConsumer + ); + } + finally { + if ( readOnly != null ) { + persistenceContext.setDefaultReadOnly( defaultReadOnlyOrig ); + } + } + } + private T doExecuteQuery( + JdbcSelect jdbcSelect, + JdbcParameterBindings jdbcParameterBindings, + ExecutionContext executionContext, + RowTransformer rowTransformer, + Function statementCreator, + ResultsConsumer resultsConsumer) { final JdbcValues jdbcValues = resolveJdbcValuesSource( jdbcSelect, @@ -203,7 +235,6 @@ public boolean shouldReturnProxies() { afterLoadAction.afterLoad( executionContext.getSession(), null, null ); } - return result; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/ReadonlyHintTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/ReadonlyHintTest.java new file mode 100644 index 0000000000..0f0b4574bc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/ReadonlyHintTest.java @@ -0,0 +1,80 @@ +package org.hibernate.orm.test.loading; + +import java.util.Collections; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.jpa.QueryHints; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +@DomainModel( + annotatedClasses = { + ReadonlyHintTest.SimpleEntity.class + } +) +@SessionFactory +@TestForIssue( jiraKey = "HHH-11958" ) +public class ReadonlyHintTest { + + private static final String ORIGINAL_NAME = "original"; + private static final String CHANGED_NAME = "changed"; + + @BeforeEach + void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + SimpleEntity entity = new SimpleEntity(); + entity.id = 1L; + entity.name = ORIGINAL_NAME; + session.persist( entity ); + } ); + } + + @Test + void testWithReadOnlyHint(SessionFactoryScope scope) { + scope.inTransaction( session -> { + SimpleEntity fetchedEntity = session.find( SimpleEntity.class, 1L, Collections.singletonMap( QueryHints.HINT_READONLY, true ) ); + fetchedEntity.name = CHANGED_NAME; + } ); + + scope.inTransaction( session -> { + SimpleEntity fetchedEntity = session.find( SimpleEntity.class, 1L ); + assertThat(fetchedEntity.name, is( ORIGINAL_NAME ) ); + } ); + } + + @Test + void testWithoutReadOnlyHint(SessionFactoryScope scope) { + scope.inTransaction( session -> { + SimpleEntity fetchedEntity = session.find( SimpleEntity.class, 1L ); + fetchedEntity.name = CHANGED_NAME; + } ); + + scope.inTransaction( session -> { + SimpleEntity fetchedEntity = session.find( SimpleEntity.class, 1L ); + assertThat(fetchedEntity.name, is( CHANGED_NAME ) ); + } ); + } + + @AfterEach + void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createQuery( "delete from SimpleEntity" ).executeUpdate() ); + } + + @Entity(name = "SimpleEntity") + public static class SimpleEntity { + @Id + private Long id; + + private String name; + } +}