HHH-16695 make fetch profiles actually work for natural id loading

This commit is contained in:
Gavin King 2023-07-09 12:04:56 +02:00
parent 3dfa70a781
commit 87a320615c
11 changed files with 254 additions and 112 deletions

View File

@ -110,7 +110,7 @@ public interface NaturalIdLoadAccess<T> {
* Determines if cached natural id cross-references are synchronized
* before query execution with unflushed modifications made in memory
* to {@linkplain org.hibernate.annotations.NaturalId#mutable mutable}
* natural ids .
* natural ids.
* <p>
* By default, every cached cross-reference is updated to reflect any
* modification made in memory.

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.loader.ast.internal;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer;
@ -49,17 +48,15 @@ import org.hibernate.sql.exec.spi.JdbcOperationQuerySelect;
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
import org.hibernate.sql.exec.spi.JdbcParametersList;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.FetchableContainer;
import org.hibernate.sql.results.graph.*;
import org.hibernate.sql.results.graph.internal.ImmutableFetchList;
import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.stat.spi.StatisticsImplementor;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
/**
* Base support for NaturalIdLoader implementations
* Base support for {@link NaturalIdLoader} implementations
*/
public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
@ -91,35 +88,82 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
@Override
public T load(Object naturalIdValue, NaturalIdLoadOptions options, SharedSessionContractImplementor session) {
return selectByNaturalId(
final SessionFactoryImplementor sessionFactory = session.getFactory();
final LockOptions lockOptions = options.getLockOptions() == null ? LockOptions.NONE : options.getLockOptions();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
getLoadable(),
// null here means to select everything
null,
true,
emptyList(), // we're going to add the restrictions ourselves
null,
1,
session.getLoadQueryInfluencers(),
lockOptions,
JdbcParametersList.newBuilder()::add,
sessionFactory
);
// we have to add the restrictions ourselves manually because we want special null handling
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( naturalIdMapping.getJdbcTypeCount() );
applyNaturalIdRestriction(
naturalIdMapping().normalizeInput( naturalIdValue ),
options,
(tableGroup, creationState) -> entityDescriptor.createDomainResult(
new NavigablePath( entityDescriptor().getRootPathName() ),
tableGroup,
null,
creationState
sqlSelect.getQuerySpec().getFromClause().getRoots().get(0),
sqlSelect.getQuerySpec()::applyPredicate,
jdbcParamBindings::addBinding,
new LoaderSqlAstCreationState(
sqlSelect.getQuerySpec(),
new SqlAliasBaseManager(),
new SimpleFromClauseAccessImpl(),
lockOptions,
AbstractNaturalIdLoader::visitFetches,
true,
new LoadQueryInfluencers( sessionFactory ),
sessionFactory
),
AbstractNaturalIdLoader::visitFetches,
(statsEnabled) -> {
// entityDescriptor().getPreLoadListener().startingLoad( entityDescriptor, naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
return statsEnabled ? System.nanoTime() : -1;
},
(result,startToken) -> {
// entityDescriptor().getPostLoadListener().completedLoad( result, entityDescriptor(), naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
if ( startToken > 0 ) {
session.getFactory().getStatistics().naturalIdQueryExecuted(
entityDescriptor().getEntityPersister().getRootEntityName(),
System.nanoTime() - startToken
);
// // todo (6.0) : need a "load-by-natural-id" stat
// // e.g.,
// // final Object identifier = entityDescriptor().getIdentifierMapping().getIdentifier( result, session );
// // session.getFactory().getStatistics().entityLoadedByNaturalId( entityDescriptor(), identifier );
}
},
session
);
final QueryOptions queryOptions = new SimpleQueryOptions( lockOptions, false );
final JdbcOperationQuerySelect jdbcSelect =
sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory()
.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, queryOptions );
final long startToken = sessionFactory.getStatistics().isStatisticsEnabled() ? System.nanoTime() : -1;
//noinspection unchecked
final List<T> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
jdbcParamBindings,
new NaturalIdLoaderWithOptionsExecutionContext( session, queryOptions ),
row -> (T) row[0],
ListResultsConsumer.UniqueSemantic.FILTER
);
if ( results.size() > 1 ) {
throw new HibernateException(
String.format(
"Loading by natural-id returned more that one row : %s",
entityDescriptor.getEntityName()
)
);
}
final T result = results.isEmpty() ? null : results.get(0);
// entityDescriptor().getPostLoadListener().completedLoad( result, entityDescriptor(), naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
if ( startToken > 0 ) {
session.getFactory().getStatistics().naturalIdQueryExecuted(
entityDescriptor().getEntityPersister().getRootEntityName(),
System.nanoTime() - startToken
);
// // todo (6.0) : need a "load-by-natural-id" stat, e.g.,
// // final Object identifier = entityDescriptor().getIdentifierMapping().getIdentifier( result, session );
// // session.getFactory().getStatistics().entityLoadedByNaturalId( entityDescriptor(), identifier );
}
return result;
}
/**
@ -134,17 +178,8 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
BiConsumer<Object,Long> statementCompletionHandler,
SharedSessionContractImplementor session) {
final SessionFactoryImplementor sessionFactory = session.getFactory();
final JdbcServices jdbcServices = sessionFactory.getJdbcServices();
final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment();
final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory();
final LockOptions lockOptions;
if ( options.getLockOptions() != null ) {
lockOptions = options.getLockOptions();
}
else {
lockOptions = LockOptions.NONE;
}
final LockOptions lockOptions = options.getLockOptions() != null ? options.getLockOptions() : LockOptions.NONE;
final NavigablePath entityPath = new NavigablePath( entityDescriptor.getRootPathName() );
final QuerySpec rootQuerySpec = new QuerySpec( true );
@ -174,7 +209,7 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
final DomainResult<?> domainResult = domainResultProducer.apply( rootTableGroup, sqlAstCreationState );
final SelectStatement sqlSelect = new SelectStatement( rootQuerySpec, Collections.singletonList( domainResult ) );
final SelectStatement sqlSelect = new SelectStatement( rootQuerySpec, singletonList( domainResult ) );
final JdbcParameterBindings jdbcParamBindings = new JdbcParameterBindingsImpl( naturalIdMapping.getJdbcTypeCount() );
@ -188,11 +223,12 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
);
final QueryOptions queryOptions = new SimpleQueryOptions( lockOptions, false );
final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, queryOptions );
final JdbcOperationQuerySelect jdbcSelect =
sessionFactory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory()
.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, queryOptions );
final StatisticsImplementor statistics = sessionFactory.getStatistics();
final Long startToken = statementStartHandler.apply( statistics.isStatisticsEnabled() );
final Long startToken = statementStartHandler.apply( sessionFactory.getStatistics().isStatisticsEnabled() );
//noinspection unchecked
final List<L> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
@ -212,16 +248,8 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
);
}
final L result;
if ( results.isEmpty() ) {
result = null;
}
else {
result = results.get( 0 );
}
final L result = results.isEmpty() ? null : results.get(0);
statementCompletionHandler.accept( result, startToken );
return result;
}
@ -246,7 +274,11 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
SelectableMapping selectableMapping,
SqlExpressionResolver sqlExpressionResolver,
@SuppressWarnings("unused") SessionFactoryImplementor sessionFactory) {
final TableReference tableReference = rootTableGroup.getTableReference( rootTableGroup.getNavigablePath(), selectableMapping.getContainingTableExpression() );
final TableReference tableReference =
rootTableGroup.getTableReference(
rootTableGroup.getNavigablePath(),
selectableMapping.getContainingTableExpression()
);
if ( tableReference == null ) {
throw new IllegalStateException(
String.format(
@ -300,7 +332,7 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
JdbcParametersList.Builder jdbcParametersBuilder = JdbcParametersList.newBuilder();
final SelectStatement sqlSelect = LoaderSelectBuilder.createSelect(
entityDescriptor(),
Collections.singletonList( naturalIdMapping() ),
singletonList( naturalIdMapping() ),
entityDescriptor().getIdentifierMapping(),
null,
1,
@ -324,8 +356,9 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
session
);
assert offset == jdbcParameters.size();
final JdbcOperationQuerySelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, QueryOptions.NONE );
final JdbcOperationQuerySelect jdbcSelect =
sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory, sqlSelect )
.translate( jdbcParamBindings, QueryOptions.NONE );
final List<Object> results = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(
jdbcSelect,
@ -339,21 +372,20 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
ListResultsConsumer.UniqueSemantic.FILTER
);
if ( results.isEmpty() ) {
return null;
switch ( results.size() ) {
case 0:
return null;
case 1:
return results.get( 0 );
default:
throw new HibernateException(
String.format(
"Resolving id to natural-id returned more that one row : %s #%s",
entityDescriptor().getEntityName(),
id
)
);
}
if ( results.size() > 1 ) {
throw new HibernateException(
String.format(
"Resolving id to natural-id returned more that one row : %s #%s",
entityDescriptor().getEntityName(),
id
)
);
}
return results.get( 0 );
}
private static ImmutableFetchList visitFetches(

View File

@ -282,6 +282,36 @@ public class LoaderSelectBuilder {
return process.generateSelect();
}
// TODO: this method is probably unnecessary if we make
// determineWhetherToForceIdSelection() a bit smarter
static SelectStatement createSelect(
Loadable loadable,
List<ModelPart> partsToSelect,
boolean forceIdentifierSelection,
List<ModelPart> restrictedParts,
DomainResult<?> cachedDomainResult,
int numberOfKeysToLoad,
LoadQueryInfluencers loadQueryInfluencers,
LockOptions lockOptions,
Consumer<JdbcParameter> jdbcParameterConsumer,
SessionFactoryImplementor sessionFactory) {
final LoaderSelectBuilder process = new LoaderSelectBuilder(
sessionFactory,
loadable,
partsToSelect,
restrictedParts,
cachedDomainResult,
numberOfKeysToLoad,
loadQueryInfluencers,
lockOptions,
determineGraphTraversalState( loadQueryInfluencers, sessionFactory ),
forceIdentifierSelection,
jdbcParameterConsumer
);
return process.generateSelect();
}
/**
* Create an SQL AST select-statement used for subselect-based CollectionLoader
*

View File

@ -8,7 +8,6 @@ package org.hibernate.loader.ast.internal;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.LockMode;

View File

@ -37,13 +37,13 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
// see org.hibernate.persister.entity.AbstractEntityPersister#createLoaders
// we should preload a few - maybe LockMode.NONE and LockMode.READ
final LockOptions lockOptions = LockOptions.NONE;
final LoadQueryInfluencers queryInfluencers = new LoadQueryInfluencers( sessionFactory );
final LoadQueryInfluencers influencers = new LoadQueryInfluencers( sessionFactory );
final SingleIdLoadPlan<T> plan = createLoadPlan(
lockOptions,
queryInfluencers,
influencers,
sessionFactory
);
if ( isLoadPlanReusable( lockOptions, queryInfluencers ) ) {
if ( isLoadPlanReusable( lockOptions, influencers ) ) {
selectByLockMode.put( lockOptions.getLockMode(), plan );
}
}
@ -125,7 +125,7 @@ public class SingleIdEntityLoaderStandardImpl<T> extends SingleIdEntityLoaderSup
}
}
else {
return createLoadPlan(lockOptions, loadQueryInfluencers, sessionFactory);
return createLoadPlan( lockOptions, loadQueryInfluencers, sessionFactory );
}
}

View File

@ -20,7 +20,6 @@ import org.hibernate.query.internal.SimpleQueryOptions;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.spi.QueryOptionsAdapter;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.BaseExecutionContext;
import org.hibernate.sql.exec.internal.CallbackImpl;
@ -34,14 +33,14 @@ import org.hibernate.sql.results.spi.ListResultsConsumer;
import org.hibernate.sql.results.spi.RowTransformer;
/**
* todo (6.0) : this can generically define a load-by-uk as well. only the SQL AST and `restrictivePart` vary and they are passed as ctor args
*
* Describes a plan for loading an entity by identifier.
*
* @implNote Made up of (1) a SQL AST for the SQL SELECT and (2) the `ModelPart` used as the restriction
*
* @author Steve Ebersole
*/
// todo (6.0) : this can generically define a load-by-uk as well.
// only the SQL AST and `restrictivePart` vary and they are passed as constructor args
public class SingleIdLoadPlan<T> implements SingleEntityLoadPlan {
private final EntityMappingType entityMappingType;
private final ModelPart restrictivePart;

View File

@ -6,7 +6,6 @@
*/
package org.hibernate.loader.ast.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -24,7 +23,6 @@ import org.hibernate.metamodel.mapping.SingularAttributeMapping;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.exec.internal.BaseExecutionContext;
import org.hibernate.sql.exec.internal.CallbackImpl;

View File

@ -8,7 +8,6 @@ package org.hibernate.loader.internal;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hibernate.HibernateException;
@ -98,18 +97,18 @@ public abstract class BaseNaturalIdLoadAccessImpl<T> implements NaturalIdLoadOpt
protected void synchronizationEnabled(boolean synchronizationEnabled) {
this.synchronizationEnabled = synchronizationEnabled;
}
protected final Object resolveNaturalId(Map<String, Object> naturalIdParameters) {
performAnyNeededCrossReferenceSynchronizations();
final Object resolvedId = entityPersister()
.getNaturalIdLoader()
.resolveNaturalIdToId( naturalIdParameters, context.getSession() );
return resolvedId == INVALID_NATURAL_ID_REFERENCE
? null
: resolvedId;
}
//
// protected final Object resolveNaturalId(Map<String, Object> naturalIdParameters) {
// performAnyNeededCrossReferenceSynchronizations();
//
// final Object resolvedId = entityPersister()
// .getNaturalIdLoader()
// .resolveNaturalIdToId( naturalIdParameters, context.getSession() );
//
// return resolvedId == INVALID_NATURAL_ID_REFERENCE
// ? null
// : resolvedId;
// }
protected void performAnyNeededCrossReferenceSynchronizations() {
if ( !synchronizationEnabled ) {

View File

@ -232,7 +232,7 @@ public class DeferredResultSetAccess extends AbstractResultSetAccess {
.getEventListenerManager();
long executeStartNanos = 0;
if ( this.sqlStatementLogger.getLogSlowQuery() > 0 ) {
if ( sqlStatementLogger.getLogSlowQuery() > 0 ) {
executeStartNanos = System.nanoTime();
}
try {

View File

@ -97,9 +97,9 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
private static class Results<R> {
private final List<R> results = new ArrayList<>();
private final JavaType resultJavaType;
private final JavaType<R> resultJavaType;
public Results(JavaType resultJavaType) {
public Results(JavaType<R> resultJavaType) {
this.resultJavaType = resultJavaType;
}
@ -127,7 +127,7 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
private final IdentityHashMap<R, Object> added = new IdentityHashMap<>();
public EntityResult(JavaType resultJavaType) {
public EntityResult(JavaType<R> resultJavaType) {
super( resultJavaType );
}
@ -162,25 +162,27 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
typeConfiguration
);
final boolean isEnityResultType = domainResultJavaType instanceof EntityJavaType;
final boolean isEntityResultType = domainResultJavaType instanceof EntityJavaType;
final Results<R> results;
if ( ( uniqueSemantic == UniqueSemantic.ALLOW || uniqueSemantic == UniqueSemantic.FILTER ) && isEnityResultType ) {
if ( isEntityResultType
&& ( uniqueSemantic == UniqueSemantic.ALLOW
|| uniqueSemantic == UniqueSemantic.FILTER ) ) {
results = new EntityResult<>( domainResultJavaType );
}
else {
results = new Results<>( domainResultJavaType );
}
if ( this.uniqueSemantic == UniqueSemantic.FILTER
|| this.uniqueSemantic == UniqueSemantic.ASSERT && rowProcessingState.hasCollectionInitializers()
|| this.uniqueSemantic == UniqueSemantic.ALLOW && isEnityResultType ) {
if ( uniqueSemantic == UniqueSemantic.FILTER
|| uniqueSemantic == UniqueSemantic.ASSERT && rowProcessingState.hasCollectionInitializers()
|| uniqueSemantic == UniqueSemantic.ALLOW && isEntityResultType ) {
while ( rowProcessingState.next() ) {
results.addUnique( rowReader.readRow( rowProcessingState, processingOptions ) );
rowProcessingState.finishRowProcessing();
}
}
else if ( this.uniqueSemantic == UniqueSemantic.ASSERT ) {
else if ( uniqueSemantic == UniqueSemantic.ASSERT ) {
while ( rowProcessingState.next() ) {
if ( !results.addUnique( rowReader.readRow( rowProcessingState, processingOptions ) ) ) {
throw new HibernateException(
@ -210,7 +212,8 @@ public class ListResultsConsumer<R> implements ResultsConsumer<List<R>, R> {
}
//noinspection unchecked
final ResultListTransformer<R> resultListTransformer = (ResultListTransformer<R>) queryOptions.getResultListTransformer();
final ResultListTransformer<R> resultListTransformer =
(ResultListTransformer<R>) queryOptions.getResultListTransformer();
if ( resultListTransformer != null ) {
return resultListTransformer.transformList( results.getResults() );
}

View File

@ -7,6 +7,7 @@ import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.FetchProfileOverride;
import org.hibernate.annotations.NaturalId;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@ -60,7 +61,7 @@ public class NewFetchTest {
s.persist(e);
});
F f = scope.fromSession( s -> s.find(F.class, 1));
F f = scope.fromSession( s -> s.find(F.class, 1) );
assertFalse( isInitialized( f.g ) );
assertFalse( isInitialized( f.es ) );
F ff = scope.fromSession( s -> {
@ -70,7 +71,7 @@ public class NewFetchTest {
assertTrue( isInitialized( ff.g ) );
assertTrue( isInitialized( ff.es ) );
E e = scope.fromSession( s -> s.find(E.class, 1));
E e = scope.fromSession( s -> s.find(E.class, 1) );
assertFalse( isInitialized( e.f ) );
E ee = scope.fromSession( s -> {
s.enableFetchProfile(OLD_PROFILE);
@ -79,6 +80,85 @@ public class NewFetchTest {
assertTrue( isInitialized( ee.f ) );
}
@Test void testById(SessionFactoryScope scope) {
scope.inTransaction( s-> {
G g = new G();
F f = new F();
E e = new E();
f.g = g;
e.f = f;
s.persist(g);
s.persist(f);
s.persist(e);
});
F f = scope.fromSession( s -> s.byId(F.class).load(1) );
assertFalse( isInitialized( f.g ) );
assertFalse( isInitialized( f.es ) );
F ff = scope.fromSession( s -> s.byId(F.class).enableFetchProfile(NEW_PROFILE).load(1) );
assertTrue( isInitialized( ff.g ) );
assertTrue( isInitialized( ff.es ) );
E e = scope.fromSession( s -> s.byId(E.class).load(1) );
assertFalse( isInitialized( e.f ) );
E ee = scope.fromSession( s -> s.byId(E.class).enableFetchProfile(OLD_PROFILE).load(1) );
assertTrue( isInitialized( ee.f ) );
}
@Test void testBySimpleNaturalId(SessionFactoryScope scope) {
scope.inTransaction( s-> {
G g = new G();
F f = new F();
E e = new E();
f.g = g;
e.f = f;
e.s = "1";
f.s = "1";
s.persist(g);
s.persist(f);
s.persist(e);
});
F f = scope.fromSession( s -> s.bySimpleNaturalId(F.class).load("1") );
assertFalse( isInitialized( f.g ) );
assertFalse( isInitialized( f.es ) );
F ff = scope.fromSession( s -> s.bySimpleNaturalId(F.class).enableFetchProfile(NEW_PROFILE).load("1") );
assertTrue( isInitialized( ff.g ) );
assertTrue( isInitialized( ff.es ) );
E e = scope.fromSession( s -> s.bySimpleNaturalId(E.class).load("1") );
assertFalse( isInitialized( e.f ) );
E ee = scope.fromSession( s -> s.bySimpleNaturalId(E.class).enableFetchProfile(OLD_PROFILE).load("1") );
assertTrue( isInitialized( ee.f ) );
}
@Test void testByNaturalId(SessionFactoryScope scope) {
scope.inTransaction( s-> {
G g = new G();
F f = new F();
E e = new E();
f.g = g;
e.f = f;
e.s = "2";
f.s = "2";
s.persist(g);
s.persist(f);
s.persist(e);
});
F f = scope.fromSession( s -> s.byNaturalId(F.class).using("s", "2").load() );
assertFalse( isInitialized( f.g ) );
assertFalse( isInitialized( f.es ) );
F ff = scope.fromSession( s -> s.byNaturalId(F.class).using("s", "2").enableFetchProfile(NEW_PROFILE).load() );
assertTrue( isInitialized( ff.g ) );
assertTrue( isInitialized( ff.es ) );
E e = scope.fromSession( s -> s.byNaturalId(E.class).using("s", "2").load() );
assertFalse( isInitialized( e.f ) );
E ee = scope.fromSession( s -> s.byNaturalId(E.class).using("s", "2").enableFetchProfile(OLD_PROFILE).load() );
assertTrue( isInitialized( ee.f ) );
}
@Test void testQuery(SessionFactoryScope scope) {
scope.inTransaction( s-> {
G g = new G();
@ -268,6 +348,7 @@ public class NewFetchTest {
Long id;
@ManyToOne(fetch = LAZY)
F f;
@NaturalId String s;
}
@Entity(name = "F")
static class F {
@ -284,6 +365,7 @@ public class NewFetchTest {
@FetchProfileOverride(mode = SELECT, fetch = EAGER, profile = EAGER_SELECT_PROFILE)
@FetchProfileOverride(mode = JOIN, profile = JOIN_PROFILE)
Set<E> es;
@NaturalId String s;
}
@Entity(name = "G")
static class G {