diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacySqlAstTranslator.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacySqlAstTranslator.java index 84048cac6a..0aa4c60a6e 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacySqlAstTranslator.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/CockroachLegacySqlAstTranslator.java @@ -46,7 +46,7 @@ public class CockroachLegacySqlAstTranslator extends Ab else { final boolean isNegated = booleanExpressionPredicate.isNegated(); if ( isNegated ) { - appendSql( "not(" ); + appendSql( "not (" ); } booleanExpressionPredicate.getExpression().accept( this ); if ( isNegated ) { @@ -188,7 +188,7 @@ public class CockroachLegacySqlAstTranslator extends Ab @Override public void visitInArrayPredicate(InArrayPredicate inArrayPredicate) { inArrayPredicate.getTestExpression().accept( this ); - appendSql( " = ANY(" ); + appendSql( " = any(" ); inArrayPredicate.getArrayParameter().accept( this ); appendSql( ')' ); } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 2e374e9107..1db43121db 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -124,6 +124,7 @@ import static org.hibernate.cfg.AvailableSettings.USE_SCROLLABLE_RESULTSET; import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE; import static org.hibernate.cfg.AvailableSettings.USE_SQL_COMMENTS; import static org.hibernate.cfg.AvailableSettings.USE_STRUCTURED_CACHE; +import static org.hibernate.cfg.AvailableSettings.USE_SUBSELECT_FETCH; import static org.hibernate.engine.config.spi.StandardConverters.BOOLEAN; import static org.hibernate.internal.CoreLogging.messageLogger; import static org.hibernate.internal.log.DeprecationLogger.DEPRECATION_LOGGER; @@ -194,6 +195,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean delayBatchFetchLoaderCreations; private int defaultBatchFetchSize; private Integer maximumFetchDepth; + private boolean subselectFetchEnabled; private NullPrecedence defaultNullPrecedence; private boolean orderUpdatesEnabled; private boolean orderInsertsEnabled; @@ -361,6 +363,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { this.batchFetchStyle = BatchFetchStyle.interpret( configurationSettings.get( BATCH_FETCH_STYLE ) ); this.delayBatchFetchLoaderCreations = configurationService.getSetting( DELAY_ENTITY_LOADER_CREATIONS, BOOLEAN, true ); this.defaultBatchFetchSize = getInt( DEFAULT_BATCH_FETCH_SIZE, configurationSettings, -1 ); + this.subselectFetchEnabled = getBoolean( USE_SUBSELECT_FETCH, configurationSettings ); this.maximumFetchDepth = getInteger( MAX_FETCH_DEPTH, configurationSettings ); final String defaultNullPrecedence = getString( AvailableSettings.DEFAULT_NULL_ORDERING, configurationSettings, "none", "first", "last" @@ -972,6 +975,11 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { return maximumFetchDepth; } + @Override + public boolean isSubselectFetchEnabled() { + return subselectFetchEnabled; + } + @Override public NullPrecedence getDefaultNullPrecedence() { return defaultNullPrecedence; @@ -1342,6 +1350,10 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { this.maximumFetchDepth = depth; } + public void applySubselectFetchEnabled(boolean subselectFetchEnabled) { + this.subselectFetchEnabled = subselectFetchEnabled; + } + public void applyDefaultNullPrecedence(NullPrecedence nullPrecedence) { this.defaultNullPrecedence = nullPrecedence; } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index 3129e96e9d..95ba220c37 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -187,6 +187,11 @@ public class AbstractDelegatingSessionFactoryOptions implements SessionFactoryOp return delegate.getMaximumFetchDepth(); } + @Override + public boolean isSubselectFetchEnabled() { + return delegate.isSubselectFetchEnabled(); + } + @Override public NullPrecedence getDefaultNullPrecedence() { return delegate.getDefaultNullPrecedence(); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index f8c417a01c..712c8ac066 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -144,6 +144,8 @@ public interface SessionFactoryOptions extends QueryEngineOptions { Integer getMaximumFetchDepth(); + boolean isSubselectFetchEnabled(); + NullPrecedence getDefaultNullPrecedence(); boolean isOrderUpdatesEnabled(); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 63b528acc5..e156f02abd 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -1032,6 +1032,17 @@ public interface AvailableSettings { */ String DEFAULT_BATCH_FETCH_SIZE = "hibernate.default_batch_fetch_size"; + /** + * When enabled, Hibernate will use subselect fetching, when possible, to + * fetch any collection. + *

+ * By default, Hibernate only uses subselect fetching for collections + * explicitly annotated {@code @Fetch(SUBSELECT)}. + * + * @see org.hibernate.annotations.FetchMode#SUBSELECT + */ + String USE_SUBSELECT_FETCH = "hibernate.use_subselect_fetch"; + /** * When enabled, specifies that JDBC scrollable {@code ResultSet}s may be used. * This property is only necessary when there is no {@code ConnectionProvider}, diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachSqlAstTranslator.java index 532550b6cc..d90f325f1b 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/CockroachSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/CockroachSqlAstTranslator.java @@ -46,7 +46,7 @@ public class CockroachSqlAstTranslator extends Abstract else { final boolean isNegated = booleanExpressionPredicate.isNegated(); if ( isNegated ) { - appendSql( "not(" ); + appendSql( "not (" ); } booleanExpressionPredicate.getExpression().accept( this ); if ( isNegated ) { @@ -160,7 +160,7 @@ public class CockroachSqlAstTranslator extends Abstract @Override public void visitInArrayPredicate(InArrayPredicate inArrayPredicate) { inArrayPredicate.getTestExpression().accept( this ); - appendSql( " = ANY(" ); + appendSql( " = any(" ); inArrayPredicate.getArrayParameter().accept( this ); appendSql( ')' ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java index ee2ac40025..f5380444df 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/DerbySqlAstTranslator.java @@ -208,7 +208,7 @@ public class DerbySqlAstTranslator extends AbstractSqlA if ( inListPredicate.isNegated() ) { appendSql( " not" ); } - appendSql( " in(" ); + appendSql( " in (" ); renderCommaSeparated( listExpressions ); appendSql( CLOSE_PARENTHESIS ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index f893f57bb9..44b581c693 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -868,7 +868,7 @@ public class StatefulPersistenceContext implements PersistenceContext { public void addUninitializedCollection(CollectionPersister persister, PersistentCollection collection, Object id) { final CollectionEntry ce = new CollectionEntry( collection, persister, id, flushing ); addCollection( collection, ce, id ); - if ( persister.getBatchSize() > 1 ) { + if ( session.getLoadQueryInfluencers().effectiveBatchSize( persister ) > 1 ) { getBatchFetchQueue().addBatchLoadableCollection( collection, ce ); } } @@ -877,7 +877,7 @@ public class StatefulPersistenceContext implements PersistenceContext { public void addUninitializedDetachedCollection(CollectionPersister persister, PersistentCollection collection) { final CollectionEntry ce = new CollectionEntry( persister, collection.getKey() ); addCollection( collection, ce, collection.getKey() ); - if ( persister.getBatchSize() > 1 ) { + if ( session.getLoadQueryInfluencers().effectiveBatchSize( persister ) > 1 ) { getBatchFetchQueue().addBatchLoadableCollection( collection, ce ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java index 751385bd1a..5e3d701512 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java @@ -6,12 +6,6 @@ */ package org.hibernate.engine.spi; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.Collection; - import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.collection.spi.AbstractPersistentCollection; @@ -21,6 +15,12 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.pretty.MessageHelper; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; + /** * We need an entry to tell us all about the current state * of a collection with respect to its persistent state @@ -202,10 +202,10 @@ public final class CollectionEntry implements Serializable { snapshot = loadedPersister.isMutable() ? collection.getSnapshot( loadedPersister ) : null; - collection.setSnapshot(loadedKey, role, snapshot); - if ( loadedPersister.getBatchSize() > 1 ) { - ( (AbstractPersistentCollection) collection ).getSession() - .getPersistenceContextInternal() + collection.setSnapshot( loadedKey, role, snapshot ); + final SharedSessionContractImplementor session = ((AbstractPersistentCollection) collection).getSession(); + if ( session.getLoadQueryInfluencers().effectiveBatchSize( loadedPersister ) > 1 ) { + session.getPersistenceContextInternal() .getBatchFetchQueue() .removeBatchLoadableCollection( this ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java index e4ee3d92de..4bfd454be5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/LoadQueryInfluencers.java @@ -19,6 +19,7 @@ import org.hibernate.Filter; import org.hibernate.UnknownProfileException; import org.hibernate.internal.FilterImpl; import org.hibernate.loader.ast.spi.CascadingFetchProfile; +import org.hibernate.persister.collection.CollectionPersister; /** * Centralize all options which can influence the SQL query needed to load an @@ -48,6 +49,10 @@ public class LoadQueryInfluencers implements Serializable { //Lazily initialized! private HashMap enabledFilters; + private Boolean subselectFetchEnabled; + + private Integer batchSize; + private final EffectiveEntityGraph effectiveEntityGraph = new EffectiveEntityGraph(); private Boolean readOnly; @@ -57,7 +62,7 @@ public class LoadQueryInfluencers implements Serializable { } public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory) { - this(sessionFactory, null); + this( sessionFactory, null ); } public LoadQueryInfluencers(SessionFactoryImplementor sessionFactory, Boolean readOnly) { @@ -73,13 +78,13 @@ public class LoadQueryInfluencers implements Serializable { // internal fetch profile support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ public T fromInternalFetchProfile(CascadingFetchProfile profile, Supplier supplier) { - final CascadingFetchProfile previous = this.enabledCascadingFetchProfile; - this.enabledCascadingFetchProfile = profile; + final CascadingFetchProfile previous = enabledCascadingFetchProfile; + enabledCascadingFetchProfile = profile; try { return supplier.get(); } finally { - this.enabledCascadingFetchProfile = previous; + enabledCascadingFetchProfile = previous; } } @@ -250,6 +255,30 @@ public class LoadQueryInfluencers implements Serializable { this.readOnly = readOnly; } + public Integer getBatchSize() { + return batchSize; + } + + public void setBatchSize(Integer batchSize) { + this.batchSize = batchSize; + } + + public int effectiveBatchSize(CollectionPersister persister) { + return batchSize != null ? batchSize : persister.getBatchSize(); + } + + public Boolean getSubselectFetchEnabled() { + return subselectFetchEnabled; + } + + public void setSubselectFetchEnabled(Boolean subselectFetchEnabled) { + this.subselectFetchEnabled = subselectFetchEnabled; + } + + public boolean effectiveSubselectFetchEnabled(CollectionPersister persister) { + return subselectFetchEnabled != null ? subselectFetchEnabled : persister.isSubselectLoadable(); + } + private void checkMutability() { if ( sessionFactory == null ) { // that's the signal that this is the immutable, context-less diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java index 2b82d50621..d116d38248 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -149,11 +150,13 @@ public class SubselectFetch { } public void addKey(EntityKey key, LoadingEntityEntry entry) { - if ( !entry.getDescriptor().hasSubselectLoadableCollections() ) { - return; - } - if ( shouldAddSubselectFetch( entry ) ) { + final EntityPersister persister = entry.getDescriptor(); + boolean subselectsPossible = + persister.hasCollections() + && persister.getFactory().getSessionFactoryOptions().isSubselectFetchEnabled() + || persister.hasSubselectLoadableCollections(); + if ( subselectsPossible && shouldAddSubselectFetch( entry ) ) { final SubselectFetch subselectFetch = subselectFetches.computeIfAbsent( entry.getEntityInitializer().getNavigablePath(), navigablePath -> new SubselectFetch( diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java b/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java index af4f587cc2..6f678dc543 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/EvictVisitor.java @@ -15,9 +15,11 @@ import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.event.spi.EventSource; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.pretty.MessageHelper; +import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.type.CollectionType; +import static org.hibernate.pretty.MessageHelper.collectionInfoString; + /** * Evict any collections referenced by the object from the session cache. * This will NOT pick up any collections that were dereferenced, so they @@ -37,8 +39,8 @@ public class EvictVisitor extends AbstractVisitor { @Override Object processCollection(Object collection, CollectionType type) throws HibernateException { - if (collection != null) { - evictCollection(collection, type); + if ( collection != null ) { + evictCollection( collection, type ); } return null; @@ -55,34 +57,37 @@ public class EvictVisitor extends AbstractVisitor { } else if ( value == LazyPropertyInitializer.UNFETCHED_PROPERTY ) { final Object keyOfOwner = type.getKeyOfOwner( owner, session ); - collection = (PersistentCollection) type.getCollection( keyOfOwner, session, owner, Boolean.FALSE ); + collection = (PersistentCollection) type.getCollection( keyOfOwner, session, owner, false ); } else { return; //EARLY EXIT! } - if ( collection != null && collection.unsetSession(session) ) { - evictCollection(collection); + if ( collection != null && collection.unsetSession( session ) ) { + evictCollection( collection ); } } private void evictCollection(PersistentCollection collection) { - final PersistenceContext persistenceContext = getSession().getPersistenceContextInternal(); - CollectionEntry ce = persistenceContext.removeCollectionEntry( collection ); + final EventSource session = getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final CollectionEntry ce = persistenceContext.removeCollectionEntry( collection ); + final CollectionPersister persister = ce.getLoadedPersister(); + if ( LOG.isDebugEnabled() ) { LOG.debugf( "Evicting collection: %s", - MessageHelper.collectionInfoString( ce.getLoadedPersister(), - collection, - ce.getLoadedKey(), - getSession() ) ); + collectionInfoString( persister, collection, ce.getLoadedKey(), session ) + ); } - if (ce.getLoadedPersister() != null && ce.getLoadedPersister().getBatchSize() > 1) { - persistenceContext.getBatchFetchQueue().removeBatchLoadableCollection(ce); + + if ( persister != null + && session.getLoadQueryInfluencers().effectiveBatchSize(persister) > 1 ) { + persistenceContext.getBatchFetchQueue().removeBatchLoadableCollection( ce ); } - if ( ce.getLoadedPersister() != null && ce.getLoadedKey() != null ) { + if ( persister != null && ce.getLoadedKey() != null ) { //TODO: is this 100% correct? - persistenceContext.removeCollectionByKey( new CollectionKey( ce.getLoadedPersister(), ce.getLoadedKey() ) ); + persistenceContext.removeCollectionByKey( new CollectionKey( persister, ce.getLoadedKey() ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 518332e496..ea28e3594e 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -265,9 +265,9 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont @Override public Integer getConfiguredJdbcBatchSize() { final Integer sessionJdbcBatchSize = jdbcBatchSize; - return sessionJdbcBatchSize == null ? - fastSessionServices.defaultJdbcBatchSize : - sessionJdbcBatchSize; + return sessionJdbcBatchSize == null + ? fastSessionServices.defaultJdbcBatchSize + : sessionJdbcBatchSize; } protected void addSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 318e1985ec..39840fe744 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1932,6 +1932,22 @@ public class SessionImpl loadQueryInfluencers.disableFetchProfile( name ); } + public void setSubselectFetchingEnabled(boolean enabled) { + loadQueryInfluencers.setSubselectFetchEnabled( enabled ); + } + + public boolean isSubselectFetchingEnabled() { + return loadQueryInfluencers.getSubselectFetchEnabled(); + } + + public void setFetchBatchSize(int batchSize) { + loadQueryInfluencers.setBatchSize( batchSize ); + } + + public int getFetchBatchSize() { + return loadQueryInfluencers.getBatchSize(); + } + @Override public LobHelper getLobHelper() { if ( lobHelper == null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java index 25d9e6b501..969088273d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java @@ -34,7 +34,7 @@ import org.hibernate.sql.results.internal.RowTransformerStandardImpl; import org.hibernate.sql.results.spi.ListResultsConsumer; /** - * A one-time use CollectionLoader for applying a sub-select fetch + * A one-time use {@link CollectionLoader} for applying a subselect fetch. * * @author Steve Ebersole */ diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java index 73f0a3c6a9..575a61c467 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java @@ -252,7 +252,10 @@ public class MultiIdEntityLoaderStandard extends AbstractMultiIdEntityLoader< .translate( jdbcParameterBindings, QueryOptions.NONE ); final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler; - if ( getLoadable().getEntityPersister().hasSubselectLoadableCollections() ) { + final EntityPersister persister = getLoadable().getEntityPersister(); + if ( persister.hasCollections() + && session.getSessionFactory().getSessionFactoryOptions().isSubselectFetchEnabled() + || persister.hasSubselectLoadableCollections() ) { subSelectFetchableKeysHandler = SubselectFetch.createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqlAst, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java index 09454424d9..ea1fe88eb6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java @@ -20,6 +20,7 @@ import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.loader.ast.spi.MultiNaturalIdLoadOptions; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.ast.SqlAstTranslatorFactory; import org.hibernate.sql.ast.tree.expression.JdbcParameter; @@ -138,7 +139,10 @@ public class MultiNaturalIdLoadingBatcher { private List performLoad(JdbcParameterBindings jdbcParamBindings, SharedSessionContractImplementor session) { final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler; - if ( entityDescriptor.getEntityPersister().hasSubselectLoadableCollections() ) { + final EntityPersister persister = entityDescriptor.getEntityPersister(); + if ( persister.hasCollections() + && session.getSessionFactory().getSessionFactoryOptions().isSubselectFetchEnabled() + || persister.hasSubselectLoadableCollections() ) { subSelectFetchableKeysHandler = SubselectFetch.createRegistrationHandler( session.getPersistenceContext().getBatchFetchQueue(), sqlSelect, diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/FetchOptionsHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/FetchOptionsHelper.java index 421a37ada5..8c856a9121 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/FetchOptionsHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/FetchOptionsHelper.java @@ -61,9 +61,9 @@ public final class FetchOptionsHelper { } } else { - CollectionPersister persister = (CollectionPersister) type.getAssociatedJoinable( sessionFactory ); + final CollectionPersister persister = (CollectionPersister) type.getAssociatedJoinable( sessionFactory ); if ( persister instanceof AbstractCollectionPersister - && ( (AbstractCollectionPersister) persister ).isSubselectLoadable() ) { + && persister.isSubselectLoadable() ) { return FetchStyle.SUBSELECT; } else if ( persister.getBatchSize() > 0 ) { diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java index bcabe4a4ea..99f03a0dea 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/AbstractCollectionPersister.java @@ -42,7 +42,6 @@ import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.profile.Fetch; import org.hibernate.engine.profile.internal.FetchProfileAffectee; -import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; @@ -281,7 +280,8 @@ public abstract class AbstractCollectionPersister // isSet = collectionBinding.isSet(); // isSorted = collectionBinding.isSorted(); isPrimitiveArray = collectionBootDescriptor.isPrimitiveArray(); - subselectLoadable = collectionBootDescriptor.isSubselectLoadable(); + subselectLoadable = collectionBootDescriptor.isSubselectLoadable() + || factory.getSessionFactoryOptions().isSubselectFetchEnabled(); qualifiedTableName = determineTableName( table ); @@ -705,48 +705,47 @@ public abstract class AbstractCollectionPersister // if there is a user-specified loader, return that return getStandardCollectionLoader(); } + final LoadQueryInfluencers loadQueryInfluencers = session.getLoadQueryInfluencers(); - final CollectionLoader subSelectLoader = resolveSubSelectLoader( key, session ); - if ( subSelectLoader != null ) { - return subSelectLoader; + if ( loadQueryInfluencers.effectiveSubselectFetchEnabled( this ) ) { + final CollectionLoader subSelectLoader = resolveSubSelectLoader( key, session ); + if ( subSelectLoader != null ) { + return subSelectLoader; + } } - if ( ! session.getLoadQueryInfluencers().hasEnabledFilters() && ! isAffectedByEnabledFetchProfiles( session.getLoadQueryInfluencers() ) ) { + if ( !loadQueryInfluencers.hasEnabledFilters() + && !isAffectedByEnabledFetchProfiles( loadQueryInfluencers ) ) { return getStandardCollectionLoader(); } - - return createCollectionLoader( session.getLoadQueryInfluencers() ); + else { + return createCollectionLoader( loadQueryInfluencers ); + } } private CollectionLoader resolveSubSelectLoader(Object key, SharedSessionContractImplementor session) { - if ( !isSubselectLoadable() ) { - return null; - } - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - final EntityKey ownerEntityKey = session.generateEntityKey( key, getOwnerEntityPersister() ); - final SubselectFetch subselect = persistenceContext.getBatchFetchQueue().getSubselect( ownerEntityKey ); + final SubselectFetch subselect = + persistenceContext.getBatchFetchQueue() + .getSubselect( session.generateEntityKey( key, getOwnerEntityPersister() ) ); if ( subselect == null ) { return null; } + else { + // Take care of any entities that might have + // been evicted! + subselect.getResultingEntityKeys() + .removeIf( entityKey -> !persistenceContext.containsEntity( entityKey ) ); - // Take care of any entities that might have - // been evicted! - subselect.getResultingEntityKeys().removeIf( o -> !persistenceContext.containsEntity( o ) ); - - // Run a subquery loader - return createSubSelectLoader( subselect, session ); + // Run a subquery loader + return createSubSelectLoader( subselect, session ); + } } protected CollectionLoader createSubSelectLoader(SubselectFetch subselect, SharedSessionContractImplementor session) { //noinspection RedundantCast - return new CollectionLoaderSubSelectFetch( - attributeMapping, - (DomainResult) null, - subselect, - session - ); + return new CollectionLoaderSubSelectFetch( attributeMapping, (DomainResult) null, subselect, session ); } private CollectionLoader reusableCollectionLoader; @@ -758,9 +757,10 @@ public abstract class AbstractCollectionPersister } return reusableCollectionLoader; } - - // create a one-off - return generateCollectionLoader( loadQueryInfluencers ); + else { + // create a one-off + return generateCollectionLoader( loadQueryInfluencers ); + } } private boolean canUseReusableCollectionLoader(LoadQueryInfluencers loadQueryInfluencers) { @@ -769,13 +769,15 @@ public abstract class AbstractCollectionPersister } private CollectionLoader generateCollectionLoader(LoadQueryInfluencers loadQueryInfluencers) { - final int batchSize = getBatchSize(); + final int batchSize = loadQueryInfluencers.effectiveBatchSize( this ); if ( batchSize > 1 ) { return getFactory().getServiceRegistry() .getService( BatchLoaderFactory.class ) .createCollectionBatchLoader( batchSize, loadQueryInfluencers, attributeMapping, getFactory() ); } - return new CollectionLoaderSingleKey( attributeMapping, loadQueryInfluencers, getFactory() ); + else { + return new CollectionLoaderSingleKey( attributeMapping, loadQueryInfluencers, getFactory() ); + } } @Override @@ -1371,6 +1373,7 @@ public abstract class AbstractCollectionPersister return isAffectedByEnabledFilters( session.getLoadQueryInfluencers() ); } + @Override public boolean isSubselectLoadable() { return subselectLoadable; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java index 2e9b0ba914..fe0ba567f7 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/collection/CollectionPersister.java @@ -302,12 +302,19 @@ public interface CollectionPersister extends Restrictable { throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" ); } - boolean isExtraLazy(); + default boolean isExtraLazy() { + return false; + } int getSize(Object key, SharedSessionContractImplementor session); boolean indexExists(Object key, Object index, SharedSessionContractImplementor session); boolean elementExists(Object key, Object element, SharedSessionContractImplementor session); Object getElementByIndex(Object key, Object index, SharedSessionContractImplementor session, Object owner); - int getBatchSize(); + default int getBatchSize() { + return 0; + } + default boolean isSubselectLoadable() { + return false; + } /** * @return the name of the property this collection is mapped by 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 8fce367906..1924d07044 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 @@ -512,7 +512,8 @@ public abstract class AbstractEntityPersister batch = creationContext.getSessionFactoryOptions().getDefaultBatchFetchSize(); } batchSize = batch; - hasSubselectLoadableCollections = persistentClass.hasSubselectLoadableCollections(); + hasSubselectLoadableCollections = persistentClass.hasSubselectLoadableCollections() + || entityMetamodel.hasCollections() && factory.getSessionFactoryOptions().isSubselectFetchEnabled(); hasPartitionedSelectionMapping = persistentClass.hasPartitionedSelectionMapping(); hasCollectionNotReferencingPK = persistentClass.hasCollectionNotReferencingPK(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index fc113f1c16..3fcb7f29b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -6939,7 +6939,7 @@ public abstract class AbstractSqlAstTranslator implemen if ( inSubQueryPredicate.isNegated() ) { appendSql( " not" ); } - appendSql( " in" ); + appendSql( " in " ); inSubQueryPredicate.getSubQuery().accept( this ); } else if ( !supportsRowValueConstructorSyntaxInInSubQuery() ) { @@ -6957,7 +6957,7 @@ public abstract class AbstractSqlAstTranslator implemen if ( inSubQueryPredicate.isNegated() ) { appendSql( " not" ); } - appendSql( " in" ); + appendSql( " in " ); inSubQueryPredicate.getSubQuery().accept( this ); } } @@ -6966,7 +6966,7 @@ public abstract class AbstractSqlAstTranslator implemen if ( inSubQueryPredicate.isNegated() ) { appendSql( " not" ); } - appendSql( " in" ); + appendSql( " in " ); inSubQueryPredicate.getSubQuery().accept( this ); } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java index 8165a5db01..f6d30cc83d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java @@ -966,10 +966,6 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver { return null; //To change body of implemented methods use File | Settings | File Templates. } - public boolean isExtraLazy() { - return false; //To change body of implemented methods use File | Settings | File Templates. - } - public int getSize(Object key, SharedSessionContractImplementor session) { return 0; //To change body of implemented methods use File | Settings | File Templates. } @@ -986,11 +982,6 @@ public class GoofyPersisterClassProvider implements PersisterClassResolver { return null; //To change body of implemented methods use File | Settings | File Templates. } - @Override - public int getBatchSize() { - return 0; - } - @Override public String getMappedByProperty() { return null; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/fetch/depth/DepthOneBatchTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/fetch/depth/DepthOneBatchTest.java index e656b78381..39cb59378e 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/fetch/depth/DepthOneBatchTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/fetch/depth/DepthOneBatchTest.java @@ -104,7 +104,7 @@ public class DepthOneBatchTest { ); assertThat( executedQueries.get( 4 ).toLowerCase() ).isEqualTo( - "select u1_0.group_id,u1_1.user_id,a1_0.agency_id,a1_0.agency_txt,u1_1.user_name from group_user u1_0 join user_table u1_1 on u1_1.user_id=u1_0.user_id left join agency_table a1_0 on a1_0.agency_id=u1_1.agency_id where u1_0.group_id in(select g1_0.group_id from group_table g1_0 where g1_0.agency_id=?)" + "select u1_0.group_id,u1_1.user_id,a1_0.agency_id,a1_0.agency_txt,u1_1.user_name from group_user u1_0 join user_table u1_1 on u1_1.user_id=u1_0.user_id left join agency_table a1_0 on a1_0.agency_id=u1_1.agency_id where u1_0.group_id in (select g1_0.group_id from group_table g1_0 where g1_0.agency_id=?)" ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/fetch/depth/DepthOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/fetch/depth/DepthOneTest.java index 89e1e8f44e..9c13529d1a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/fetch/depth/DepthOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/mapping/fetch/depth/DepthOneTest.java @@ -100,7 +100,7 @@ public class DepthOneTest { ); assertThat( executedQueries.get( 3 ).toLowerCase() ).isEqualTo( - "select u1_0.group_id,u1_1.user_id,a1_0.agency_id,a1_0.agency_txt,u1_1.user_name from group_user u1_0 join user_table u1_1 on u1_1.user_id=u1_0.user_id left join agency_table a1_0 on a1_0.agency_id=u1_1.agency_id where u1_0.group_id in(select g1_0.group_id from group_table g1_0 where g1_0.agency_id=?)" + "select u1_0.group_id,u1_1.user_id,a1_0.agency_id,a1_0.agency_txt,u1_1.user_name from group_user u1_0 join user_table u1_1 on u1_1.user_id=u1_0.user_id left join agency_table a1_0 on a1_0.agency_id=u1_1.agency_id where u1_0.group_id in (select g1_0.group_id from group_table g1_0 where g1_0.agency_id=?)" ); assertThat( executedQueries.get( 4 ).toLowerCase() ).isEqualTo( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/CompareEntityValuedPathsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/CompareEntityValuedPathsTest.java index c543fcc953..3bc4ff62a8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/CompareEntityValuedPathsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/CompareEntityValuedPathsTest.java @@ -41,7 +41,7 @@ public class CompareEntityValuedPathsTest { "select " + "1 " + "from PERSON_TABLE p1_0 " + - "where p1_0.uk in(" + + "where p1_0.uk in (" + "select c1_0.child_uk " + "from children_uks c1_0 " + "where p1_0.uk=c1_0.owner_uk" + @@ -65,7 +65,7 @@ public class CompareEntityValuedPathsTest { "select " + "1 " + "from PERSON_TABLE p1_0 " + - "where p1_0.id in(" + + "where p1_0.id in (" + "select c1_0.children_id " + "from PERSON_TABLE_PERSON_TABLE c1_0 " + "where p1_0.id=c1_0.Person_id" + @@ -109,7 +109,7 @@ public class CompareEntityValuedPathsTest { "select " + "1 " + "from PERSON_TABLE p1_0 " + - "where p1_0.parent_id in(" + + "where p1_0.parent_id in (" + "select c1_1.id " + "from children_uks c1_0 " + "join PERSON_TABLE c1_1 on c1_1.uk=c1_0.child_uk " + @@ -135,7 +135,7 @@ public class CompareEntityValuedPathsTest { "1 " + "from PERSON_TABLE p1_0 " + "join PERSON_TABLE p2_0 on p2_0.uk=p1_0.parent_uk " + - "where p2_0.id in(" + + "where p2_0.id in (" + "select c1_0.children_id " + "from PERSON_TABLE_PERSON_TABLE c1_0 " + "where p1_0.id=c1_0.Person_id" + @@ -159,7 +159,7 @@ public class CompareEntityValuedPathsTest { "select " + "1 " + "from PERSON_TABLE p1_0 " + - "where p1_0.id in(" + + "where p1_0.id in (" + "select e1_0.id " + "from PERSON_TABLE e1_0 " + "where p1_0.id=e1_0.supervisor_id" + @@ -183,7 +183,7 @@ public class CompareEntityValuedPathsTest { "select " + "1 " + "from PERSON_TABLE p1_0 " + - "where p1_0.id in(" + + "where p1_0.id in (" + "select e1_0.id " + "from PERSON_TABLE e1_0 " + "where p1_0.uk=e1_0.supervisor_uk" + @@ -294,7 +294,7 @@ public class CompareEntityValuedPathsTest { "1 " + "from PERSON_TABLE p1_0 " + "join (children_uks c1_0 join PERSON_TABLE c1_1 on c1_1.uk=c1_0.child_uk) on p1_0.uk=c1_0.owner_uk " + - "where c1_1.id in(select c2_0.children_id from PERSON_TABLE_PERSON_TABLE c2_0 where p1_0.id=c2_0.Person_id)", + "where c1_1.id in (select c2_0.children_id from PERSON_TABLE_PERSON_TABLE c2_0 where p1_0.id=c2_0.Person_id)", statementInspector.getSqlQueries().get( 0 ) ); } @@ -315,7 +315,7 @@ public class CompareEntityValuedPathsTest { "1 " + "from PERSON_TABLE p1_0 " + "join PERSON_TABLE_PERSON_TABLE c1_0 on p1_0.id=c1_0.Person_id " + - "where c1_0.children_id in(select c2_1.id from children_uks c2_0 join PERSON_TABLE c2_1 on c2_1.uk=c2_0.child_uk where p1_0.uk=c2_0.owner_uk)", + "where c1_0.children_id in (select c2_1.id from children_uks c2_0 join PERSON_TABLE c2_1 on c2_1.uk=c2_0.child_uk where p1_0.uk=c2_0.owner_uk)", statementInspector.getSqlQueries().get( 0 ) ); } @@ -335,7 +335,7 @@ public class CompareEntityValuedPathsTest { "select " + "1 " + "from PERSON_TABLE p1_0 " + - "where p1_0.uk in(" + + "where p1_0.uk in (" + "select p2_0.parent_uk " + "from PERSON_TABLE p2_0" + ")", @@ -358,7 +358,7 @@ public class CompareEntityValuedPathsTest { "select " + "1 " + "from PERSON_TABLE p1_0 " + - "where p1_0.id in(" + + "where p1_0.id in (" + "select p2_0.parent_id " + "from PERSON_TABLE p2_0" + ")",