HHH-17948 make findAll() accept FindOptions

and add missing options to MultiIdentifierLoadAccess
This commit is contained in:
Gavin King 2024-09-09 12:00:30 +02:00
parent 62e1b0470e
commit 6e2ed7f1a0
15 changed files with 291 additions and 42 deletions

View File

@ -51,6 +51,15 @@ public interface MultiIdentifierLoadAccess<T> {
*/ */
MultiIdentifierLoadAccess<T> with(CacheMode cacheMode); MultiIdentifierLoadAccess<T> with(CacheMode cacheMode);
/**
* Specify whether the entities should be loaded in read-only mode.
*
* @see Session#setDefaultReadOnly(boolean)
*
* @since 7.0
*/
MultiIdentifierLoadAccess<T> withReadOnly(boolean readOnly);
/** /**
* Override the associations fetched by default by specifying * Override the associations fetched by default by specifying
* the complete list of associations to be fetched as an * the complete list of associations to be fetched as an
@ -88,6 +97,30 @@ public interface MultiIdentifierLoadAccess<T> {
*/ */
MultiIdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic); MultiIdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic);
/**
* Customize the associations fetched by specifying a
* {@linkplain org.hibernate.annotations.FetchProfile fetch profile}
* that should be enabled during this operation.
* <p>
* This allows the {@linkplain Session#isFetchProfileEnabled(String)
* session-level fetch profiles} to be temporarily overridden.
*
* @since 7.0
*/
MultiIdentifierLoadAccess<T> enableFetchProfile(String profileName);
/**
* Customize the associations fetched by specifying a
* {@linkplain org.hibernate.annotations.FetchProfile fetch profile}
* that should be disabled during this operation.
* <p>
* This allows the {@linkplain Session#isFetchProfileEnabled(String)
* session-level fetch profiles} to be temporarily overridden.
*
* @since 7.0
*/
MultiIdentifierLoadAccess<T> disableFetchProfile(String profileName);
/** /**
* Specify a batch size, that is, how many entities should be * Specify a batch size, that is, how many entities should be
* fetched in each request to the database. * fetched in each request to the database.

View File

@ -9,6 +9,7 @@ package org.hibernate;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import jakarta.persistence.FindOption;
import org.hibernate.graph.RootGraph; import org.hibernate.graph.RootGraph;
import org.hibernate.jdbc.Work; import org.hibernate.jdbc.Work;
import org.hibernate.query.Query; import org.hibernate.query.Query;
@ -706,12 +707,13 @@ public interface Session extends SharedSessionContract, EntityManager {
* *
* @param entityType the entity type * @param entityType the entity type
* @param ids the identifiers * @param ids the identifiers
* @param options options, if any
* @return an ordered list of persistent instances, with null elements representing missing * @return an ordered list of persistent instances, with null elements representing missing
* entities * entities
* @see #byMultipleIds(Class) * @see #byMultipleIds(Class)
* @since 7.0 * @since 7.0
*/ */
<E> List<E> findAll(Class<E> entityType, List<Object> ids); <E> List<E> findAll(Class<E> entityType, List<Object> ids, FindOption... options);
/** /**
* Return the persistent instance of the given entity class with the given identifier, * Return the persistent instance of the given entity class with the given identifier,
@ -922,7 +924,7 @@ public interface Session extends SharedSessionContract, EntityManager {
* *
* @throws HibernateException If the given class does not resolve as a mapped entity * @throws HibernateException If the given class does not resolve as a mapped entity
* *
* @see #findAll(Class, List) * @see #findAll(Class, List, FindOption...)
*/ */
<T> MultiIdentifierLoadAccess<T> byMultipleIds(Class<T> entityClass); <T> MultiIdentifierLoadAccess<T> byMultipleIds(Class<T> entityClass);

View File

@ -262,7 +262,7 @@ public interface StatelessSession extends SharedSessionContract {
* @param entityClass The class of the entity to retrieve * @param entityClass The class of the entity to retrieve
* @param ids The ids of the entities to retrieve * @param ids The ids of the entities to retrieve
* @return an ordered list of detached entity instances, with * @return an ordered list of detached entity instances, with
* null elements representing missing entities * null elements representing missing entities
* @since 7.0 * @since 7.0
*/ */
<T> List<T> getAll(Class<T> entityClass, List<Object> ids); <T> List<T> getAll(Class<T> entityClass, List<Object> ids);

View File

@ -954,8 +954,8 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
} }
@Override @Override
public <E> List<E> findAll(Class<E> entityType, List<Object> ids) { public <E> List<E> findAll(Class<E> entityType, List<Object> ids, FindOption... options) {
return delegate.findAll( entityType, ids ); return delegate.findAll( entityType, ids, options );
} }
@Override @Override

View File

@ -264,8 +264,8 @@ public class SessionLazyDelegator implements Session {
} }
@Override @Override
public <E> List<E> findAll(Class<E> entityType, List<Object> ids) { public <E> List<E> findAll(Class<E> entityType, List<Object> ids, FindOption... options) {
return this.lazySession.get().findAll( entityType, ids ); return this.lazySession.get().findAll( entityType, ids, options );
} }
@Override @Override

View File

@ -6,13 +6,18 @@
*/ */
package org.hibernate.internal; package org.hibernate.internal;
import java.util.Collections; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.CacheMode; import org.hibernate.CacheMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.MultiIdentifierLoadAccess; import org.hibernate.MultiIdentifierLoadAccess;
import org.hibernate.UnknownProfileException;
import org.hibernate.engine.spi.EffectiveEntityGraph;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.GraphSemantic;
import org.hibernate.graph.RootGraph; import org.hibernate.graph.RootGraph;
import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.graph.spi.RootGraphImplementor;
@ -20,6 +25,8 @@ import org.hibernate.loader.ast.internal.LoaderHelper;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.MultiIdLoadOptions;
import static java.util.Collections.emptyList;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@ -29,6 +36,7 @@ class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>,
private LockOptions lockOptions; private LockOptions lockOptions;
private CacheMode cacheMode; private CacheMode cacheMode;
private Boolean readOnly;
private RootGraphImplementor<T> rootGraph; private RootGraphImplementor<T> rootGraph;
private GraphSemantic graphSemantic; private GraphSemantic graphSemantic;
@ -38,6 +46,9 @@ class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>,
private boolean returnOfDeletedEntitiesEnabled; private boolean returnOfDeletedEntitiesEnabled;
private boolean orderedReturnEnabled = true; private boolean orderedReturnEnabled = true;
private Set<String> enabledFetchProfiles;
private Set<String> disabledFetchProfiles;
public MultiIdentifierLoadAccessImpl(SessionImpl session, EntityPersister entityPersister) { public MultiIdentifierLoadAccessImpl(SessionImpl session, EntityPersister entityPersister) {
this.session = session; this.session = session;
this.entityPersister = entityPersister; this.entityPersister = entityPersister;
@ -60,6 +71,12 @@ class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>,
return this; return this;
} }
@Override
public MultiIdentifierLoadAccess<T> withReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
}
@Override @Override
public MultiIdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic) { public MultiIdentifierLoadAccess<T> with(RootGraph<T> graph, GraphSemantic semantic) {
this.rootGraph = (RootGraphImplementor<T>) graph; this.rootGraph = (RootGraphImplementor<T>) graph;
@ -121,6 +138,13 @@ class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>,
return this; return this;
} }
@Override
public Boolean getReadOnly(SessionImplementor session) {
return readOnly != null
? readOnly
: session.getLoadQueryInfluencers().getReadOnly();
}
@Override @Override
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )
public <K> List<T> multiLoad(K... ids) { public <K> List<T> multiLoad(K... ids) {
@ -128,7 +152,7 @@ class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>,
} }
public List<T> perform(Supplier<List<T>> executor) { public List<T> perform(Supplier<List<T>> executor) {
CacheMode sessionCacheMode = session.getCacheMode(); final CacheMode sessionCacheMode = session.getCacheMode();
boolean cacheModeChanged = false; boolean cacheModeChanged = false;
if ( cacheMode != null ) { if ( cacheMode != null ) {
// naive check for now... // naive check for now...
@ -140,20 +164,17 @@ class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>,
} }
try { try {
if ( graphSemantic != null ) { final LoadQueryInfluencers influencers = session.getLoadQueryInfluencers();
if ( rootGraph == null ) { final HashSet<String> fetchProfiles =
throw new IllegalArgumentException( "Graph semantic specified, but no RootGraph was supplied" ); influencers.adjustFetchProfiles( disabledFetchProfiles, enabledFetchProfiles );
} final EffectiveEntityGraph effectiveEntityGraph =
session.getLoadQueryInfluencers().getEffectiveEntityGraph().applyGraph( rootGraph, graphSemantic ); influencers.applyEntityGraph( rootGraph, graphSemantic );
}
try { try {
return executor.get(); return executor.get();
} }
finally { finally {
if ( graphSemantic != null ) { effectiveEntityGraph.clear();
session.getLoadQueryInfluencers().getEffectiveEntityGraph().clear(); influencers.setEnabledFetchProfileNames( fetchProfiles );
}
} }
} }
finally { finally {
@ -168,12 +189,41 @@ class MultiIdentifierLoadAccessImpl<T> implements MultiIdentifierLoadAccess<T>,
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )
public <K> List<T> multiLoad(List<K> ids) { public <K> List<T> multiLoad(List<K> ids) {
if ( ids.isEmpty() ) { if ( ids.isEmpty() ) {
return Collections.emptyList(); return emptyList();
} }
return perform( () -> (List<T>) entityPersister.multiLoad( else {
ids.toArray( LoaderHelper.createTypedArray( ids.get( 0 ).getClass(), ids.size() ) ), return perform( () -> (List<T>) entityPersister.multiLoad(
session, ids.toArray( LoaderHelper.createTypedArray( ids.get( 0 ).getClass(), ids.size() ) ),
this session,
) ); this
) );
}
}
@Override
public MultiIdentifierLoadAccess<T> enableFetchProfile(String profileName) {
if ( !session.getFactory().containsFetchProfileDefinition( profileName ) ) {
throw new UnknownProfileException( profileName );
}
if ( enabledFetchProfiles == null ) {
enabledFetchProfiles = new HashSet<>();
}
enabledFetchProfiles.add( profileName );
if ( disabledFetchProfiles != null ) {
disabledFetchProfiles.remove( profileName );
}
return this;
}
@Override
public MultiIdentifierLoadAccess<T> disableFetchProfile(String profileName) {
if ( disabledFetchProfiles == null ) {
disabledFetchProfiles = new HashSet<>();
}
disabledFetchProfiles.add( profileName );
if ( enabledFetchProfiles != null ) {
enabledFetchProfiles.remove( profileName );
}
return this;
} }
} }

View File

@ -944,22 +944,64 @@ public class SessionImpl
@Override @Deprecated @Override @Deprecated
public Object load(String entityName, Object id) throws HibernateException { public Object load(String entityName, Object id) throws HibernateException {
return this.byId( entityName ).getReference( id ); return byId( entityName ).getReference( id );
}
private <T> MultiIdentifierLoadAccess<T> multiloadAccessWithOptions(Class<T> entityClass, FindOption[] options) {
final MultiIdentifierLoadAccess<T> loadAccess = byMultipleIds( entityClass );
CacheStoreMode storeMode = getCacheStoreMode();
CacheRetrieveMode retrieveMode = getCacheRetrieveMode();
LockOptions lockOptions = copySessionLockOptions();
for ( FindOption option : options ) {
if ( option instanceof CacheStoreMode cacheStoreMode ) {
storeMode = cacheStoreMode;
}
else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) {
retrieveMode = cacheRetrieveMode;
}
else if ( option instanceof CacheMode cacheMode ) {
storeMode = cacheMode.getJpaStoreMode();
retrieveMode = cacheMode.getJpaRetrieveMode();
}
else if ( option instanceof LockModeType lockModeType ) {
lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) );
}
else if ( option instanceof LockMode lockMode ) {
lockOptions.setLockMode( lockMode );
}
else if ( option instanceof LockOptions lockOpts ) {
lockOptions = lockOpts;
}
else if ( option instanceof PessimisticLockScope pessimisticLockScope ) {
lockOptions.setLockScope( pessimisticLockScope );
}
else if ( option instanceof Timeout timeout ) {
lockOptions.setTimeOut( timeout.milliseconds() );
}
else if ( option instanceof EnabledFetchProfile enabledFetchProfile ) {
loadAccess.enableFetchProfile( enabledFetchProfile.profileName() );
}
else if ( option instanceof ReadOnlyMode ) {
loadAccess.withReadOnly( option == ReadOnlyMode.READ_ONLY );
}
}
loadAccess.with( lockOptions ).with( interpretCacheMode( storeMode, retrieveMode ) );
return loadAccess;
} }
@Override @Override
public <E> List<E> findAll(Class<E> entityType, List<Object> ids) { public <E> List<E> findAll(Class<E> entityType, List<Object> ids, FindOption... options) {
return this.byMultipleIds( entityType ).multiLoad( ids ); return multiloadAccessWithOptions( entityType, options ).multiLoad( ids );
} }
@Override @Override
public <T> T get(Class<T> entityClass, Object id) throws HibernateException { public <T> T get(Class<T> entityClass, Object id) throws HibernateException {
return this.byId( entityClass ).load( id ); return byId( entityClass ).load( id );
} }
@Override @Override
public Object get(String entityName, Object id) throws HibernateException { public Object get(String entityName, Object id) throws HibernateException {
return this.byId( entityName ).load( id ); return byId( entityName ).load( id );
} }
/** /**
@ -2360,6 +2402,10 @@ public class SessionImpl
else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) { else if ( option instanceof CacheRetrieveMode cacheRetrieveMode ) {
retrieveMode = cacheRetrieveMode; retrieveMode = cacheRetrieveMode;
} }
else if ( option instanceof CacheMode cacheMode ) {
storeMode = cacheMode.getJpaStoreMode();
retrieveMode = cacheMode.getJpaRetrieveMode();
}
else if ( option instanceof LockModeType lockModeType ) { else if ( option instanceof LockModeType lockModeType ) {
lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) ); lockOptions.setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) );
} }

View File

@ -9,17 +9,27 @@ package org.hibernate.loader.ast.internal;
import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityHolder;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.exec.internal.BaseExecutionContext; import org.hibernate.sql.exec.internal.BaseExecutionContext;
class ExecutionContextWithSubselectFetchHandler extends BaseExecutionContext { class ExecutionContextWithSubselectFetchHandler extends BaseExecutionContext {
private final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler; private final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler;
private final boolean readOnly;
public ExecutionContextWithSubselectFetchHandler( public ExecutionContextWithSubselectFetchHandler(
SharedSessionContractImplementor session, SharedSessionContractImplementor session,
SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler) { SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler) {
this( session, subSelectFetchableKeysHandler, false );
}
public ExecutionContextWithSubselectFetchHandler(
SharedSessionContractImplementor session,
SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler,
boolean readOnly) {
super( session ); super( session );
this.subSelectFetchableKeysHandler = subSelectFetchableKeysHandler; this.subSelectFetchableKeysHandler = subSelectFetchableKeysHandler;
this.readOnly = readOnly;
} }
@Override @Override
@ -29,4 +39,8 @@ class ExecutionContextWithSubselectFetchHandler extends BaseExecutionContext {
} }
} }
@Override
public QueryOptions getQueryOptions() {
return readOnly ? QueryOptions.READ_ONLY : super.getQueryOptions();
}
} }

View File

@ -43,6 +43,7 @@ import org.hibernate.sql.results.spi.ManagedResultConsumer;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import static java.lang.Boolean.TRUE;
import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty; import static org.hibernate.internal.util.collections.CollectionHelper.isEmpty;
/** /**
@ -193,7 +194,9 @@ public class MultiIdEntityLoaderArrayParam<E> extends AbstractMultiIdEntityLoade
session.getJdbcServices().getJdbcSelectExecutor().executeQuery( session.getJdbcServices().getJdbcSelectExecutor().executeQuery(
jdbcSelectOperation, jdbcSelectOperation,
jdbcParameterBindings, jdbcParameterBindings,
new ExecutionContextWithSubselectFetchHandler( session, subSelectFetchableKeysHandler ), new ExecutionContextWithSubselectFetchHandler( session,
subSelectFetchableKeysHandler,
TRUE.equals( loadOptions.getReadOnly(session) ) ),
RowTransformerStandardImpl.instance(), RowTransformerStandardImpl.instance(),
null, null,
idsToLoadFromDatabase.size(), idsToLoadFromDatabase.size(),

View File

@ -40,6 +40,8 @@ import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static java.lang.Boolean.TRUE;
/** /**
* Standard MultiIdEntityLoader * Standard MultiIdEntityLoader
* *
@ -157,7 +159,7 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
if ( idsInBatch.size() >= maxBatchSize ) { if ( idsInBatch.size() >= maxBatchSize ) {
// we've hit the allotted max-batch-size, perform an "intermediate load" // we've hit the allotted max-batch-size, perform an "intermediate load"
loadEntitiesById( idsInBatch, lockOptions, session ); loadEntitiesById( idsInBatch, lockOptions, loadOptions, session );
idsInBatch.clear(); idsInBatch.clear();
} }
@ -169,7 +171,7 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
if ( !idsInBatch.isEmpty() ) { if ( !idsInBatch.isEmpty() ) {
// we still have ids to load from the processing above since the last max-batch-size trigger, // we still have ids to load from the processing above since the last max-batch-size trigger,
// perform a load for them // perform a load for them
loadEntitiesById( idsInBatch, lockOptions, session ); loadEntitiesById( idsInBatch, lockOptions, loadOptions, session );
} }
// for each result where we set the EntityKey earlier, replace them // for each result where we set the EntityKey earlier, replace them
@ -197,7 +199,8 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
private List<T> loadEntitiesById( private List<T> loadEntitiesById(
List<Object> idsInBatch, List<Object> idsInBatch,
LockOptions lockOptions, LockOptions lockOptions,
SharedSessionContractImplementor session) { MultiIdLoadOptions loadOptions,
EventSource session) {
assert idsInBatch != null; assert idsInBatch != null;
assert ! idsInBatch.isEmpty(); assert ! idsInBatch.isEmpty();
@ -265,7 +268,9 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
return session.getJdbcServices().getJdbcSelectExecutor().list( return session.getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect, jdbcSelect,
jdbcParameterBindings, jdbcParameterBindings,
new ExecutionContextWithSubselectFetchHandler( session, subSelectFetchableKeysHandler ), new ExecutionContextWithSubselectFetchHandler( session,
subSelectFetchableKeysHandler,
TRUE.equals( loadOptions.getReadOnly(session) ) ),
RowTransformerStandardImpl.instance(), RowTransformerStandardImpl.instance(),
null, null,
ListResultsConsumer.UniqueSemantic.FILTER, ListResultsConsumer.UniqueSemantic.FILTER,
@ -403,7 +408,7 @@ public class MultiIdEntityLoaderStandard<T> extends AbstractMultiIdEntityLoader<
System.arraycopy( ids, idPosition, idsInBatch, 0, batchSize ); System.arraycopy( ids, idPosition, idsInBatch, 0, batchSize );
result.addAll( result.addAll(
loadEntitiesById( Arrays.asList( idsInBatch ), lockOptions, session ) loadEntitiesById( Arrays.asList( idsInBatch ), lockOptions, loadOptions, session )
); );
numberOfIdsLeft = numberOfIdsLeft - batchSize; numberOfIdsLeft = numberOfIdsLeft - batchSize;

View File

@ -6,6 +6,8 @@
*/ */
package org.hibernate.loader.ast.spi; package org.hibernate.loader.ast.spi;
import org.hibernate.engine.spi.SessionImplementor;
/** /**
* Encapsulation of the options for loading multiple entities by id * Encapsulation of the options for loading multiple entities by id
*/ */
@ -25,4 +27,9 @@ public interface MultiIdLoadOptions extends MultiLoadOptions {
* @return the session factory cache is checked first * @return the session factory cache is checked first
*/ */
boolean isSecondLevelCacheCheckingEnabled(); boolean isSecondLevelCacheCheckingEnabled();
/**
* Should the entities be loaded in read-only mode?
*/
Boolean getReadOnly(SessionImplementor session);
} }

View File

@ -108,7 +108,7 @@ public class IdentifierLoadAccessImpl<T> implements IdentifierLoadAccess<T>, Jav
final HashSet<String> fetchProfiles = final HashSet<String> fetchProfiles =
influencers.adjustFetchProfiles( disabledFetchProfiles, enabledFetchProfiles ); influencers.adjustFetchProfiles( disabledFetchProfiles, enabledFetchProfiles );
final EffectiveEntityGraph effectiveEntityGraph = final EffectiveEntityGraph effectiveEntityGraph =
session.getLoadQueryInfluencers().applyEntityGraph( rootGraph, graphSemantic); influencers.applyEntityGraph( rootGraph, graphSemantic);
try { try {
return executor.get(); return executor.get();
} }

View File

@ -0,0 +1,80 @@
package org.hibernate.orm.test.loading.multiLoad;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import org.hibernate.EnabledFetchProfile;
import org.hibernate.Hibernate;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.FetchProfileOverride;
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.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SessionFactory
@DomainModel(annotatedClasses = {FindAllFetchProfileTest.Record.class, FindAllFetchProfileTest.Owner.class})
public class FindAllFetchProfileTest {
@Test void test(SessionFactoryScope scope) {
scope.inTransaction(s-> {
Owner gavin = new Owner("gavin");
s.persist(gavin);
s.persist(new Record(123L,gavin,"hello earth"));
s.persist(new Record(456L,gavin,"hello mars"));
});
scope.inTransaction(s-> {
List<Record> all = s.findAll(Record.class, List.of(456L, 123L, 2L));
assertEquals("hello mars",all.get(0).message);
assertEquals("hello earth",all.get(1).message);
assertNull(all.get(2));
assertFalse(Hibernate.isInitialized(all.get(0).owner));
assertFalse(Hibernate.isInitialized(all.get(1).owner));
});
scope.inTransaction(s-> {
List<Record> all = s.findAll(Record.class, List.of(456L, 123L),
new EnabledFetchProfile("withOwner"));
assertEquals("hello mars",all.get(0).message);
assertEquals("hello earth",all.get(1).message);
assertTrue(Hibernate.isInitialized(all.get(0).owner));
assertTrue(Hibernate.isInitialized(all.get(1).owner));
});
}
@Entity
@FetchProfile(name = "withOwner")
static class Record {
@Id Long id;
String message;
@FetchProfileOverride(profile = "withOwner")
@ManyToOne(fetch = FetchType.LAZY)
Owner owner;
Record(Long id, Owner owner, String message) {
this.id = id;
this.owner = owner;
this.message = message;
}
Record() {
}
}
@Entity
static class Owner {
@Id String name;
Owner(String name) {
this.name = name;
}
Owner() {
}
}
}

View File

@ -9,8 +9,10 @@ import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.hibernate.ReadOnlyMode.READ_ONLY;
import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SessionFactory @SessionFactory
@DomainModel(annotatedClasses = FindAllTest.Record.class) @DomainModel(annotatedClasses = FindAllTest.Record.class)
@ -26,6 +28,13 @@ public class FindAllTest {
assertEquals("hello earth",all.get(1).message); assertEquals("hello earth",all.get(1).message);
assertNull(all.get(2)); assertNull(all.get(2));
}); });
scope.inTransaction(s-> {
List<Record> all = s.findAll(Record.class, List.of(456L, 123L), READ_ONLY);
assertEquals("hello mars",all.get(0).message);
assertEquals("hello earth",all.get(1).message);
assertTrue(s.isReadOnly(all.get(0)));
assertTrue(s.isReadOnly(all.get(1)));
});
} }
@Entity @Entity
static class Record { static class Record {

View File

@ -9,8 +9,8 @@ import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
@SessionFactory @SessionFactory
@DomainModel(annotatedClasses = GetAllTest.Record.class) @DomainModel(annotatedClasses = GetAllTest.Record.class)