HHH-16968 StackOverflowError when using NaturalIdLoadAccess and bi-directional association
This commit is contained in:
parent
5d6766698b
commit
81fe7688c4
|
@ -6,7 +6,6 @@
|
||||||
*/
|
*/
|
||||||
package org.hibernate.loader.ast.internal;
|
package org.hibernate.loader.ast.internal;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -51,14 +50,12 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinding;
|
||||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||||
import org.hibernate.sql.exec.spi.JdbcParametersList;
|
import org.hibernate.sql.exec.spi.JdbcParametersList;
|
||||||
import org.hibernate.sql.results.graph.DomainResult;
|
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.internal.ImmutableFetchList;
|
import org.hibernate.sql.results.graph.internal.ImmutableFetchList;
|
||||||
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
||||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base support for NaturalIdLoader implementations
|
* Base support for NaturalIdLoader implementations
|
||||||
*/
|
*/
|
||||||
|
@ -92,35 +89,82 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T load(Object naturalIdValue, NaturalIdLoadOptions options, SharedSessionContractImplementor session) {
|
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 ),
|
naturalIdMapping().normalizeInput( naturalIdValue ),
|
||||||
options,
|
sqlSelect.getQuerySpec().getFromClause().getRoots().get(0),
|
||||||
(tableGroup, creationState) -> entityDescriptor.createDomainResult(
|
sqlSelect.getQuerySpec()::applyPredicate,
|
||||||
new NavigablePath( entityDescriptor().getRootPathName() ),
|
jdbcParamBindings::addBinding,
|
||||||
tableGroup,
|
new LoaderSqlAstCreationState(
|
||||||
null,
|
sqlSelect.getQuerySpec(),
|
||||||
creationState
|
new SqlAliasBaseManager(),
|
||||||
|
new SimpleFromClauseAccessImpl(),
|
||||||
|
lockOptions,
|
||||||
|
(fetchParent, creationState) -> ImmutableFetchList.EMPTY,
|
||||||
|
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
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -272,7 +316,7 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
|
||||||
null,
|
null,
|
||||||
creationState
|
creationState
|
||||||
),
|
),
|
||||||
AbstractNaturalIdLoader::visitFetches,
|
(fetchParent, creationState) -> ImmutableFetchList.EMPTY,
|
||||||
(statsEnabled) -> {
|
(statsEnabled) -> {
|
||||||
// entityDescriptor().getPreLoadListener().startingLoad( entityDescriptor, naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
|
// entityDescriptor().getPreLoadListener().startingLoad( entityDescriptor, naturalIdValue, KeyType.NATURAL_ID, LoadSource.DATABASE );
|
||||||
return statsEnabled ? System.nanoTime() : -1L;
|
return statsEnabled ? System.nanoTime() : -1L;
|
||||||
|
@ -357,28 +401,6 @@ public abstract class AbstractNaturalIdLoader<T> implements NaturalIdLoader<T> {
|
||||||
return results.get( 0 );
|
return results.get( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableFetchList visitFetches(
|
|
||||||
FetchParent fetchParent,
|
|
||||||
LoaderSqlAstCreationState creationState) {
|
|
||||||
final FetchableContainer fetchableContainer = fetchParent.getReferencedMappingContainer();
|
|
||||||
final int size = fetchableContainer.getNumberOfFetchables();
|
|
||||||
final ImmutableFetchList.Builder fetches = new ImmutableFetchList.Builder( fetchableContainer );
|
|
||||||
for ( int i = 0; i < size; i++ ) {
|
|
||||||
final Fetchable fetchable = fetchableContainer.getFetchable( i );
|
|
||||||
final NavigablePath navigablePath = fetchParent.resolveNavigablePath( fetchable );
|
|
||||||
final Fetch fetch = fetchParent.generateFetchableFetch(
|
|
||||||
fetchable,
|
|
||||||
navigablePath,
|
|
||||||
fetchable.getMappedFetchOptions().getTiming(),
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
creationState
|
|
||||||
);
|
|
||||||
fetches.add( fetch );
|
|
||||||
}
|
|
||||||
return fetches.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NaturalIdLoaderWithOptionsExecutionContext extends BaseExecutionContext {
|
private static class NaturalIdLoaderWithOptionsExecutionContext extends BaseExecutionContext {
|
||||||
private final Callback callback;
|
private final Callback callback;
|
||||||
private final QueryOptions queryOptions;
|
private final QueryOptions queryOptions;
|
||||||
|
|
|
@ -281,6 +281,36 @@ public class LoaderSelectBuilder {
|
||||||
return process.generateSelect();
|
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 ),
|
||||||
|
forceIdentifierSelection,
|
||||||
|
jdbcParameterConsumer
|
||||||
|
);
|
||||||
|
|
||||||
|
return process.generateSelect();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an SQL AST select-statement used for subselect-based CollectionLoader
|
* Create an SQL AST select-statement used for subselect-based CollectionLoader
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue