diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractBagSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractBagSemantics.java index 55e929ee48..840a3791be 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractBagSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractBagSemantics.java @@ -11,14 +11,30 @@ import java.util.Collection; import java.util.Iterator; import java.util.function.Consumer; +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.CollectionSemantics; +import org.hibernate.engine.FetchTiming; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.internal.domain.collection.BagInitializerProducer; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.FetchParent; /** * @author Steve Ebersole */ public abstract class AbstractBagSemantics> implements CollectionSemantics { + @Override + public Class getCollectionJavaType() { + //noinspection unchecked + return (Class) Collection.class; + } + @Override @SuppressWarnings("unchecked") public B instantiateRaw( @@ -47,4 +63,40 @@ public abstract class AbstractBagSemantics> implements C rawCollection.forEach( action ); } } + + @Override + public CollectionInitializerProducer createInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + String resultVariable, + LockMode lockMode, + DomainResultCreationState creationState) { + final TableGroup tableGroup = creationState.getSqlAstCreationState() + .getFromClauseAccess() + .getTableGroup( navigablePath ); + return new BagInitializerProducer( + attributeMapping, + selected, + attributeMapping.getIdentifierDescriptor() == null ? null : attributeMapping.getIdentifierDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.ID.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ), + attributeMapping.getElementDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractMapSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractMapSemantics.java index a82b11832b..c4899f8280 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractMapSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractMapSemantics.java @@ -12,12 +12,28 @@ import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.MapSemantics; +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.internal.domain.collection.MapInitializerProducer; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.FetchParent; /** * @author Steve Ebersole */ public abstract class AbstractMapSemantics> implements MapSemantics { + @Override + public Class getCollectionJavaType() { + //noinspection unchecked + return (Class) Map.class; + } + @Override public Iterator getKeyIterator(M rawMap) { if ( rawMap == null ) { @@ -60,4 +76,40 @@ public abstract class AbstractMapSemantics> implements MapSem rawMap.values().forEach( action ); } } + + @Override + public CollectionInitializerProducer createInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + String resultVariable, + LockMode lockMode, + DomainResultCreationState creationState) { + final TableGroup tableGroup = creationState.getSqlAstCreationState() + .getFromClauseAccess() + .getTableGroup( navigablePath ); + return new MapInitializerProducer( + attributeMapping, + selected, + attributeMapping.getIndexDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.INDEX.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ), + attributeMapping.getElementDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index e80c9bebfc..2cebf531d5 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -615,6 +615,10 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers this.initialized = true; } + protected boolean isInitializing() { + return initializing; + } + protected final void setDirectlyAccessible(boolean directlyAccessible) { this.directlyAccessible = directlyAccessible; } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractSetSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractSetSemantics.java index d7bcb2874d..85b6e7e9c4 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractSetSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractSetSemantics.java @@ -10,12 +10,27 @@ import java.util.Iterator; import java.util.Set; import java.util.function.Consumer; +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.CollectionSemantics; +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.internal.domain.collection.SetInitializerProducer; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.FetchParent; /** * @author Steve Ebersole */ public abstract class AbstractSetSemantics> implements CollectionSemantics { + @Override + public Class getCollectionJavaType() { + //noinspection unchecked + return (Class) Set.class; + } + @Override public Iterator getElementIterator(Set rawCollection) { if ( rawCollection == null ) { @@ -31,4 +46,28 @@ public abstract class AbstractSetSemantics> implements Collecti rawCollection.forEach( action ); } } + + @Override + public CollectionInitializerProducer createInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + String resultVariable, + LockMode lockMode, + DomainResultCreationState creationState) { + return new SetInitializerProducer( + attributeMapping, + selected, + attributeMapping.getElementDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ) + ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java index 07ff6a2b37..14e58537a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentArrayHolder.java @@ -336,4 +336,14 @@ public class PersistentArrayHolder extends AbstractPersistentCollection { public boolean entryExists(Object entry, int i) { return entry != null; } + + public void load(int index, Object element) { + assert isInitializing(); + + for ( int i = tempList.size(); i <= index; ++i ) { + tempList.add( i, null ); + } + + tempList.set( index, element ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java index 00543aba55..8b820d77f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentBag.java @@ -661,6 +661,11 @@ public class PersistentBag extends AbstractPersistentCollection implements List return super.hashCode(); } + public void load(Object element) { + assert isInitializing(); + bag.add( element ); + } + final class Clear implements DelayedOperation { @Override public void operate() { diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java index fd6e8c7e64..7ae06b991f 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentIdentifierBag.java @@ -544,4 +544,12 @@ public class PersistentIdentifierBag extends AbstractPersistentCollection implem //TODO: if we are using identity columns, fetch the identifier } + public void load(Object identifier, Object element) { + assert isInitializing(); + Object old = identifiers.put( values.size(), identifier ); + if ( old == null ) { + //maintain correct duplication if loaded in a cartesian product + values.add( element ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java index 2ef6f631c6..6dcf1c6bbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentList.java @@ -129,6 +129,15 @@ public class PersistentList extends AbstractPersistentCollection implements List this.list = (List) persister.getCollectionType().instantiate( anticipatedSize ); } + public void load(int index, Object element) { + assert isInitializing(); + // todo (6.0) : we need to account for base - but it is not exposed from collection descriptor nor attribute + for ( int i = list.size(); i <= index; ++i ) { + list.add( i, null ); + } + list.set( index, element ); + } + @Override public boolean isWrapper(Object collection) { return list==collection; diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java index 4a8135a8b7..9ff7cee211 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java @@ -145,6 +145,11 @@ public class PersistentMap extends AbstractPersistentCollection implements Map { this.map = (Map) persister.getCollectionType().instantiate( anticipatedSize ); } + public void load(Object key, Object value) { + assert isInitializing(); + map.put( key, value ); + } + @Override public int size() { return readSize() ? getCachedSize() : map.size(); diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java index 033f3d8c40..0cf9a5d204 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java @@ -144,6 +144,12 @@ public class PersistentSet extends AbstractPersistentCollection implements java. this.set = (Set) persister.getCollectionType().instantiate( anticipatedSize ); } + @SuppressWarnings("unchecked") + public void load(Object element) { + assert isInitializing(); + tempList.add( element ); + } + @Override @SuppressWarnings("unchecked") public void initializeFromCache(CollectionPersister persister, Object disassembled, Object owner) diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardArraySemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardArraySemantics.java index 4699b07c1c..7c7fe50ba6 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardArraySemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardArraySemantics.java @@ -10,11 +10,21 @@ import java.util.Arrays; import java.util.Iterator; import java.util.function.Consumer; +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.internal.domain.collection.ArrayInitializerProducer; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.FetchParent; /** * CollectionSemantics implementation for arrays @@ -35,6 +45,11 @@ public class StandardArraySemantics implements CollectionSemantics { return CollectionClassification.ARRAY; } + @Override + public Class getCollectionJavaType() { + return Object[].class; + } + @Override public Object[] instantiateRaw( int anticipatedSize, @@ -78,6 +93,41 @@ public class StandardArraySemantics implements CollectionSemantics { for ( Object element : array ) { action.accept( element ); } + } + @Override + public CollectionInitializerProducer createInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + String resultVariable, + LockMode lockMode, + DomainResultCreationState creationState) { + final TableGroup tableGroup = creationState.getSqlAstCreationState() + .getFromClauseAccess() + .getTableGroup( navigablePath ); + return new ArrayInitializerProducer( + attributeMapping, + selected, + attributeMapping.getIndexDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.INDEX.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ), + attributeMapping.getElementDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ) + ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardListSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardListSemantics.java index 21622bbafb..903afbddd4 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardListSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardListSemantics.java @@ -10,12 +10,22 @@ import java.util.Iterator; import java.util.List; import java.util.function.Consumer; +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.internal.domain.collection.ListInitializerProducer; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.FetchParent; /** * Hibernate's standard CollectionSemantics for Lists @@ -36,6 +46,11 @@ public class StandardListSemantics implements CollectionSemantics { return CollectionClassification.LIST; } + @Override + public Class getCollectionJavaType() { + return List.class; + } + @Override public List instantiateRaw( int anticipatedSize, @@ -54,6 +69,42 @@ public class StandardListSemantics implements CollectionSemantics { rawCollection.forEach( action ); } + @Override + public CollectionInitializerProducer createInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + String resultVariable, + LockMode lockMode, + DomainResultCreationState creationState) { + final TableGroup tableGroup = creationState.getSqlAstCreationState() + .getFromClauseAccess() + .getTableGroup( navigablePath ); + return new ListInitializerProducer( + attributeMapping, + selected, + attributeMapping.getIndexDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.INDEX.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ), + attributeMapping.getElementDescriptor().generateFetch( + fetchParent, + navigablePath.append( CollectionPart.Nature.ELEMENT.getName() ), + FetchTiming.IMMEDIATE, + selected, + lockMode, + null, + creationState + ) + ); + } + @Override public PersistentCollection instantiateWrapper( Object key, diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardSortedMapSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardSortedMapSemantics.java index 5e78995f36..e12383f85e 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardSortedMapSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardSortedMapSemantics.java @@ -31,6 +31,12 @@ public class StandardSortedMapSemantics extends AbstractMapSemantics> getCollectionJavaType() { + //noinspection unchecked + return (Class) SortedMap.class; + } + @Override public TreeMap instantiateRaw( int anticipatedSize, diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardSortedSetSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardSortedSetSemantics.java index 546d635aa9..33f8d4c56d 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardSortedSetSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/StandardSortedSetSemantics.java @@ -32,6 +32,12 @@ public class StandardSortedSetSemantics extends AbstractSetSemantics> getCollectionJavaType() { + //noinspection unchecked + return (Class) SortedSet.class; + } + @Override public SortedSet instantiateRaw( int anticipatedSize, diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionInitializerProducer.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionInitializerProducer.java new file mode 100644 index 0000000000..e78c9ef3f5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionInitializerProducer.java @@ -0,0 +1,39 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.collection.spi; + +import java.util.function.Consumer; + +import org.hibernate.LockMode; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.AssemblerCreationState; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.Initializer; + +/** + * Functional contract to create a CollectionInitializer + * + * @author Steve Ebersole + */ +@FunctionalInterface +public interface CollectionInitializerProducer { + /** + * todo (6.0) : clean this contract up! + */ + CollectionInitializer produceInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + Consumer initializerConsumer, + AssemblerCreationState creationState); +} diff --git a/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionSemantics.java b/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionSemantics.java index b3b2e7f4aa..551783b2f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionSemantics.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/spi/CollectionSemantics.java @@ -10,9 +10,14 @@ import java.util.Iterator; import java.util.function.Consumer; import org.hibernate.Incubating; +import org.hibernate.LockMode; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.FetchParent; /** * Describes the semantics of a persistent collection such that Hibernate @@ -30,6 +35,8 @@ public interface CollectionSemantics { */ CollectionClassification getCollectionClassification(); + Class getCollectionJavaType(); + C instantiateRaw( int anticipatedSize, CollectionPersister collectionDescriptor); @@ -47,4 +54,16 @@ public interface CollectionSemantics { Iterator getElementIterator(C rawCollection); void visitElements(C rawCollection, Consumer action); + + /** + * todo (6.0) : clean this contract up! + */ + CollectionInitializerProducer createInitializerProducer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParent fetchParent, + boolean selected, + String resultVariable, + LockMode lockMode, + DomainResultCreationState creationState); } 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 626539c9c2..7825b5461c 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SubselectFetch.java @@ -27,7 +27,7 @@ public class SubselectFetch { private static final String FROM_STRING = " from "; - private final Set resultingEntityKeys; + private final Set resultingEntityKeys; private final String queryString; private final String alias; private final Loadable loadable; @@ -58,7 +58,7 @@ public class SubselectFetch { final String alias, final Loadable loadable, final QueryParameters queryParameters, - final Set resultingEntityKeys, + final Set resultingEntityKeys, final Map namedParameterLocMap) { this( createSubselectFetchQueryFragment( queryParameters ), @@ -90,7 +90,7 @@ public class SubselectFetch { final String alias, final Loadable loadable, final QueryParameters queryParameters, - final Set resultingEntityKeys, + final Set resultingEntityKeys, final Map namedParameterLocMap) { this.resultingEntityKeys = resultingEntityKeys; this.queryParameters = queryParameters; @@ -168,7 +168,7 @@ public class SubselectFetch { /** * Get the Set of EntityKeys */ - public Set getResult() { + public Set getResult() { return resultingEntityKeys; } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java index 7b6aaf4064..8b033107e1 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/Loader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/Loader.java @@ -1047,10 +1047,12 @@ public abstract class Loader { return false; } - private static Set[] transpose(List keys) { - Set[] result = new Set[( (EntityKey[]) keys.get( 0 ) ).length]; + private static Set[] transpose(List keys) { + //noinspection unchecked + final Set[] result = new Set[ keys.get( 0 ).length ]; + for ( int j = 0; j < result.length; j++ ) { - result[j] = new HashSet( keys.size() ); + result[j] = new HashSet<>( keys.size() ); for ( Object key : keys ) { result[j].add( ( (EntityKey[]) key )[j] ); } @@ -1061,7 +1063,7 @@ public abstract class Loader { private void createSubselects(List keys, QueryParameters queryParameters, SharedSessionContractImplementor session) { if ( keys.size() > 1 ) { //if we only returned one entity, query by key is more efficient - Set[] keySets = transpose( keys ); + Set[] keySets = transpose( keys ); Map namedParameterLocMap = buildNamedParameterLocMap( queryParameters ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/LoadPlan.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/LoadPlan.java new file mode 100644 index 0000000000..1655d4ebd8 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/LoadPlan.java @@ -0,0 +1,22 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.loader.internal; + +import org.hibernate.loader.spi.Loadable; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.sql.ast.tree.select.SelectStatement; + +/** + * Common contract for SQL AST based loading + * + * @author Steve Ebersole + */ +public interface LoadPlan { + Loadable getLoadable(); + ModelPart getRestrictivePart(); + SelectStatement getSqlAst(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/MetamodelSelectBuilderProcess.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/MetamodelSelectBuilderProcess.java index 17aedd2b41..bcb354e923 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/MetamodelSelectBuilderProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/MetamodelSelectBuilderProcess.java @@ -52,30 +52,26 @@ import org.jboss.logging.Logger; public class MetamodelSelectBuilderProcess { private static final Logger log = Logger.getLogger( MetamodelSelectBuilderProcess.class ); - public interface SqlAstDescriptor { - SelectStatement getSqlAst(); - List getJdbcParameters(); - } - - @SuppressWarnings("WeakerAccess") - public static SqlAstDescriptor createSelect( - SessionFactoryImplementor sessionFactory, + public static SelectStatement createSelect( Loadable loadable, List partsToSelect, ModelPart restrictedPart, - DomainResult domainResult, + DomainResult cachedDomainResult, int numberOfKeysToLoad, LoadQueryInfluencers loadQueryInfluencers, - LockOptions lockOptions) { + LockOptions lockOptions, + Consumer jdbcParameterConsumer, + SessionFactoryImplementor sessionFactory) { final MetamodelSelectBuilderProcess process = new MetamodelSelectBuilderProcess( sessionFactory, loadable, partsToSelect, restrictedPart, - domainResult, + cachedDomainResult, numberOfKeysToLoad, loadQueryInfluencers, - lockOptions + lockOptions, + jdbcParameterConsumer ); return process.execute(); @@ -85,10 +81,11 @@ public class MetamodelSelectBuilderProcess { private final Loadable loadable; private final List partsToSelect; private final ModelPart restrictedPart; - private final DomainResult domainResult; + private final DomainResult cachedDomainResult; private final int numberOfKeysToLoad; private final LoadQueryInfluencers loadQueryInfluencers; private final LockOptions lockOptions; + private final Consumer jdbcParameterConsumer; private MetamodelSelectBuilderProcess( @@ -96,21 +93,23 @@ public class MetamodelSelectBuilderProcess { Loadable loadable, List partsToSelect, ModelPart restrictedPart, - DomainResult domainResult, + DomainResult cachedDomainResult, int numberOfKeysToLoad, LoadQueryInfluencers loadQueryInfluencers, - LockOptions lockOptions) { + LockOptions lockOptions, + Consumer jdbcParameterConsumer) { this.creationContext = creationContext; this.loadable = loadable; this.partsToSelect = partsToSelect; this.restrictedPart = restrictedPart; - this.domainResult = domainResult; + this.cachedDomainResult = cachedDomainResult; this.numberOfKeysToLoad = numberOfKeysToLoad; this.loadQueryInfluencers = loadQueryInfluencers; this.lockOptions = lockOptions != null ? lockOptions : LockOptions.NONE; + this.jdbcParameterConsumer = jdbcParameterConsumer; } - private SqlAstDescriptor execute() { + private SelectStatement execute() { final QuerySpec rootQuerySpec = new QuerySpec( true ); final List domainResults; @@ -123,7 +122,7 @@ public class MetamodelSelectBuilderProcess { creationContext ); - final NavigablePath rootNavigablePath = new NavigablePath( loadable.getPathName() ); + final NavigablePath rootNavigablePath = new NavigablePath( loadable.getRootPathName() ); final TableGroup rootTableGroup = loadable.createRootTableGroup( rootNavigablePath, @@ -158,9 +157,9 @@ public class MetamodelSelectBuilderProcess { // allows re-use as they can be re-used to save on memory - they // do not share state between final DomainResult domainResult; - if ( this.domainResult != null ) { + if ( this.cachedDomainResult != null ) { // used the one passed to the constructor - domainResult = this.domainResult; + domainResult = this.cachedDomainResult; } else { // create one @@ -179,22 +178,17 @@ public class MetamodelSelectBuilderProcess { creationContext.getDomainModel().getTypeConfiguration() ); - final List jdbcParameters = new ArrayList<>( numberOfKeyColumns * numberOfKeysToLoad ); - applyKeyRestriction( rootQuerySpec, rootNavigablePath, rootTableGroup, restrictedPart, numberOfKeyColumns, - jdbcParameters::add, + jdbcParameterConsumer, sqlAstCreationState ); - return new SqlAstDescriptorImpl( - new SelectStatement( rootQuerySpec, domainResults ), - jdbcParameters - ); + return new SelectStatement( rootQuerySpec, domainResults ); } private void applyKeyRestriction( @@ -205,8 +199,6 @@ public class MetamodelSelectBuilderProcess { int numberOfKeyColumns, Consumer jdbcParameterConsumer, LoaderSqlAstCreationState sqlAstCreationState) { - final NavigablePath keyPath = rootNavigablePath.append( keyPart.getPartName() ); - final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); if ( numberOfKeyColumns == 1 ) { @@ -363,27 +355,5 @@ public class MetamodelSelectBuilderProcess { return fetches; } - - static class SqlAstDescriptorImpl implements SqlAstDescriptor { - private final SelectStatement sqlAst; - private final List jdbcParameters; - - public SqlAstDescriptorImpl( - SelectStatement sqlAst, - List jdbcParameters) { - this.sqlAst = sqlAst; - this.jdbcParameters = jdbcParameters; - } - - @Override - public SelectStatement getSqlAst() { - return sqlAst; - } - - @Override - public List getJdbcParameters() { - return jdbcParameters; - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleCollectionKeyLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleCollectionKeyLoader.java new file mode 100644 index 0000000000..585a8a076f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleCollectionKeyLoader.java @@ -0,0 +1,133 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.loader.internal; + +import java.util.Iterator; +import java.util.List; + +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.loader.spi.CollectionLoader; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; +import org.hibernate.sql.exec.spi.Callback; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcParameterBinding; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.results.internal.RowTransformerPassThruImpl; + +/** + * @author Steve Ebersole + */ +public class SingleCollectionKeyLoader implements CollectionLoader { + private final PluralAttributeMapping pluralAttributeMapping; + private final SelectStatement sqlAst; + private final List jdbcParameters; + + public SingleCollectionKeyLoader( + PluralAttributeMapping pluralAttributeMapping, + SelectStatement sqlAst, List jdbcParameters) { + this.pluralAttributeMapping = pluralAttributeMapping; + this.sqlAst = sqlAst; + this.jdbcParameters = jdbcParameters; + } + + @Override + public PluralAttributeMapping getLoadable() { + return pluralAttributeMapping; + } + + @Override + public PersistentCollection load(Object key, SharedSessionContractImplementor session) { + final CollectionKey collectionKey = new CollectionKey( pluralAttributeMapping.getCollectionDescriptor(), key ); + + final SessionFactoryImplementor sessionFactory = session.getFactory(); + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); + final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); + final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); + + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); + + final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( + pluralAttributeMapping.getKeyDescriptor().getJdbcTypeCount( sessionFactory.getTypeConfiguration() ) + ); + + final Iterator paramItr = jdbcParameters.iterator(); + + pluralAttributeMapping.getKeyDescriptor().visitJdbcValues( + key, + Clause.WHERE, + (value, type) -> { + assert paramItr.hasNext(); + final JdbcParameter parameter = paramItr.next(); + jdbcParameterBindings.addBinding( + parameter, + new JdbcParameterBinding() { + @Override + public JdbcMapping getBindType() { + return type; + } + + @Override + public Object getBindValue() { + return value; + } + } + ); + }, + session + ); + assert !paramItr.hasNext(); + + JdbcSelectExecutorStandardImpl.INSTANCE.list( + jdbcSelect, + jdbcParameterBindings, + new ExecutionContext() { + @Override + public SharedSessionContractImplementor getSession() { + return session; + } + + @Override + public CollectionKey getCollectionKey() { + return collectionKey; + } + + @Override + public QueryOptions getQueryOptions() { + return QueryOptions.NONE; + } + + @Override + public QueryParameterBindings getQueryParameterBindings() { + return QueryParameterBindings.NO_PARAM_BINDINGS; + } + + @Override + public Callback getCallback() { + return null; + } + }, + RowTransformerPassThruImpl.instance() + ); + + return session.getPersistenceContext().getCollection( collectionKey ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleEntityLoadPlan.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleEntityLoadPlan.java new file mode 100644 index 0000000000..167049c2c0 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleEntityLoadPlan.java @@ -0,0 +1,15 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.loader.internal; + +/** + * Defines a plan for loading a single entity - by id, by uk, etc + * + * @author Steve Ebersole + */ +public interface SingleEntityLoadPlan extends LoadPlan { +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderDynamicBatch.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderDynamicBatch.java index 6443f34382..9c60f4fe7b 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderDynamicBatch.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderDynamicBatch.java @@ -6,7 +6,9 @@ */ package org.hibernate.loader.internal; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import org.hibernate.LockOptions; import org.hibernate.engine.internal.BatchFetchQueueHelper; @@ -22,6 +24,7 @@ import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; import org.hibernate.sql.exec.spi.Callback; @@ -83,15 +86,19 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup log.debugf( "Batch loading entity [%s] : %s", getLoadable().getEntityName(), idsToLoad ); } - final MetamodelSelectBuilderProcess.SqlAstDescriptor sqlAstDescriptor = MetamodelSelectBuilderProcess.createSelect( - session.getFactory(), + final List jdbcParameters = new ArrayList<>(); + + final SelectStatement sqlAst = MetamodelSelectBuilderProcess.createSelect( getLoadable(), + // null here means to select everything null, getLoadable().getIdentifierMapping(), null, numberOfIds, session.getLoadQueryInfluencers(), - lockOptions + lockOptions, + jdbcParameters::add, + session.getFactory() ); final SessionFactoryImplementor sessionFactory = session.getFactory(); @@ -99,14 +106,14 @@ public class SingleIdEntityLoaderDynamicBatch extends SingleIdEntityLoaderSup final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAstDescriptor.getSqlAst() ); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( getLoadable().getIdentifierMapping().getJdbcTypeCount( sessionFactory.getTypeConfiguration() ) ); for ( int i = 0; i < numberOfIds; i++ ) { - final Iterator paramItr = sqlAstDescriptor.getJdbcParameters().iterator(); + final Iterator paramItr = jdbcParameters.iterator(); getLoadable().getIdentifierMapping().visitJdbcValues( idsToLoad[i], diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderStandardImpl.java index 32da87df2e..412d1ac234 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdEntityLoaderStandardImpl.java @@ -6,7 +6,9 @@ */ package org.hibernate.loader.internal; +import java.util.ArrayList; import java.util.EnumMap; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.hibernate.LockMode; @@ -16,7 +18,8 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.spi.InternalFetchProfile; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.JdbcParameter; /** * Standard implementation of SingleIdEntityLoader @@ -43,6 +46,7 @@ public class SingleIdEntityLoaderStandardImpl extends SingleIdEntityLoaderSup @Override public void prepare() { // see `org.hibernate.persister.entity.AbstractEntityPersister#createLoaders` + // we should pre-load a few - maybe LockMode.NONE and LockMode.READ } @@ -138,20 +142,26 @@ public class SingleIdEntityLoaderStandardImpl extends SingleIdEntityLoaderSup LockOptions lockOptions, LoadQueryInfluencers queryInfluencers, SessionFactoryImplementor sessionFactory) { - final MetamodelSelectBuilderProcess.SqlAstDescriptor sqlAstDescriptor = MetamodelSelectBuilderProcess.createSelect( - sessionFactory, + + final List jdbcParameters = new ArrayList<>(); + + final SelectStatement sqlAst = MetamodelSelectBuilderProcess.createSelect( getLoadable(), + // null here means to select everything null, getLoadable().getIdentifierMapping(), null, 1, queryInfluencers, - lockOptions + lockOptions, + jdbcParameters::add, + sessionFactory ); return new SingleIdLoadPlan<>( getLoadable().getIdentifierMapping(), - sqlAstDescriptor + sqlAst, + jdbcParameters ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdLoadPlan.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdLoadPlan.java index d87eb0b2fd..90a45fe03e 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdLoadPlan.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/SingleIdLoadPlan.java @@ -14,12 +14,14 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.loader.spi.Loadable; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; import org.hibernate.sql.exec.spi.Callback; @@ -31,17 +33,41 @@ import org.hibernate.sql.exec.spi.JdbcSelect; import org.hibernate.sql.results.internal.RowTransformerPassThruImpl; /** + * 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 */ -class SingleIdLoadPlan { +class SingleIdLoadPlan implements SingleEntityLoadPlan { private final ModelPart restrictivePart; - private final MetamodelSelectBuilderProcess.SqlAstDescriptor sqlAstDescriptor; + private final SelectStatement sqlAst; + private final List jdbcParameters; public SingleIdLoadPlan( ModelPart restrictivePart, - MetamodelSelectBuilderProcess.SqlAstDescriptor sqlAstDescriptor) { + SelectStatement sqlAst, + List jdbcParameters) { this.restrictivePart = restrictivePart; - this.sqlAstDescriptor = sqlAstDescriptor; + this.sqlAst = sqlAst; + this.jdbcParameters = jdbcParameters; + } + + @Override + public Loadable getLoadable() { + return null; + } + + @Override + public ModelPart getRestrictivePart() { + return restrictivePart; + } + + @Override + public SelectStatement getSqlAst() { + return sqlAst; } T load(Object restrictedValue, LockOptions lockOptions, SharedSessionContractImplementor session) { @@ -50,13 +76,13 @@ class SingleIdLoadPlan { final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); - final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAstDescriptor.getSqlAst() ); + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( restrictivePart.getJdbcTypeCount( sessionFactory.getTypeConfiguration() ) ); - final Iterator paramItr = sqlAstDescriptor.getJdbcParameters().iterator(); + final Iterator paramItr = jdbcParameters.iterator(); restrictivePart.visitJdbcValues( restrictedValue, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/SubSelectFetchCollectionLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/SubSelectFetchCollectionLoader.java new file mode 100644 index 0000000000..88172c1416 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/SubSelectFetchCollectionLoader.java @@ -0,0 +1,154 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.loader.internal; + +import java.util.ArrayList; +import java.util.Iterator; + +import org.hibernate.LockOptions; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; +import org.hibernate.engine.jdbc.spi.JdbcServices; +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.EntityKey; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.engine.spi.SubselectFetch; +import org.hibernate.loader.spi.CollectionLoader; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.spi.QueryOptions; +import org.hibernate.query.spi.QueryParameterBindings; +import org.hibernate.sql.ast.Clause; +import org.hibernate.sql.ast.SqlAstTranslatorFactory; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; +import org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl; +import org.hibernate.sql.exec.spi.Callback; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcParameter; +import org.hibernate.sql.exec.spi.JdbcParameterBinding; +import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.results.internal.RowTransformerPassThruImpl; +import org.hibernate.sql.results.spi.DomainResult; + +/** + * A one-time use CollectionLoader for applying a sub-select fetch + * + * @author Steve Ebersole + */ +public class SubSelectFetchCollectionLoader implements CollectionLoader { + private final PluralAttributeMapping attributeMapping; + private final DomainResult cachedDomainResult; + private final SubselectFetch subselect; + + private final SelectStatement sqlAst; + private final java.util.List jdbcParameters; + + public SubSelectFetchCollectionLoader( + PluralAttributeMapping attributeMapping, + DomainResult cachedDomainResult, + SubselectFetch subselect, + SharedSessionContractImplementor session) { + this.attributeMapping = attributeMapping; + this.cachedDomainResult = cachedDomainResult; + this.subselect = subselect; + + jdbcParameters = new ArrayList<>(); + + sqlAst = MetamodelSelectBuilderProcess.createSelect( + attributeMapping, + null, + attributeMapping.getKeyDescriptor(), + null, + subselect.getResult().size(), + session.getLoadQueryInfluencers(), + LockOptions.READ, + jdbcParameters::add, + session.getFactory() + ); + } + + @Override + public PluralAttributeMapping getLoadable() { + return attributeMapping; + } + + @Override + public PersistentCollection load(Object triggerKey, SharedSessionContractImplementor session) { + final SessionFactoryImplementor sessionFactory = session.getFactory(); + final JdbcServices jdbcServices = sessionFactory.getJdbcServices(); + final JdbcEnvironment jdbcEnvironment = jdbcServices.getJdbcEnvironment(); + final SqlAstTranslatorFactory sqlAstTranslatorFactory = jdbcEnvironment.getSqlAstTranslatorFactory(); + + final JdbcSelect jdbcSelect = sqlAstTranslatorFactory.buildSelectTranslator( sessionFactory ).translate( sqlAst ); + + final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( + attributeMapping.getKeyDescriptor().getJdbcTypeCount( sessionFactory.getTypeConfiguration() ) + ); + + for ( EntityKey key : subselect.getResult() ) { + final Iterator paramItr = jdbcParameters.iterator(); + + attributeMapping.getKeyDescriptor().visitJdbcValues( + key.getIdentifierValue(), + Clause.WHERE, + (value, type) -> { + assert paramItr.hasNext(); + final JdbcParameter parameter = paramItr.next(); + jdbcParameterBindings.addBinding( + parameter, + new JdbcParameterBinding() { + @Override + public JdbcMapping getBindType() { + return type; + } + + @Override + public Object getBindValue() { + return value; + } + } + ); + }, + session + ); + assert !paramItr.hasNext(); + } + + JdbcSelectExecutorStandardImpl.INSTANCE.list( + jdbcSelect, + jdbcParameterBindings, + new ExecutionContext() { + @Override + public SharedSessionContractImplementor getSession() { + return session; + } + + @Override + public QueryOptions getQueryOptions() { + return QueryOptions.NONE; + } + + @Override + public QueryParameterBindings getQueryParameterBindings() { + return QueryParameterBindings.NO_PARAM_BINDINGS; + } + + @Override + public Callback getCallback() { + return null; + } + }, + RowTransformerPassThruImpl.instance() + ); + + final CollectionKey collectionKey = new CollectionKey( attributeMapping.getCollectionDescriptor(), triggerKey ); + return session.getPersistenceContext().getCollection( collectionKey ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java index f8d8e47dcd..ece46b1477 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/plan/exec/process/internal/ResultSetProcessorImpl.java @@ -176,126 +176,4 @@ public class ResultSetProcessorImpl implements ResultSetProcessor { } } - -// private class LocalVisitationStrategy extends LoadPlanVisitationStrategyAdapter { -// private boolean hadSubselectFetches = false; -// -// @Override -// public void startingEntityFetch(EntityFetch entityFetch) { -// // only collections are currently supported for subselect fetching. -// // hadSubselectFetches = hadSubselectFetches -// // || entityFetch.getFetchStrategy().getStyle() == FetchStyle.SUBSELECT; -// } -// -// @Override -// public void startingCollectionFetch(CollectionFetch collectionFetch) { -// hadSubselectFetches = hadSubselectFetches -// || collectionFetch.getFetchStrategy().getStyle() == FetchStyle.SUBSELECT; -// } -// } -// -// private class MixedReturnRowReader extends AbstractRowReader implements RowReader { -// private final List returnReaders; -// private List entityReferenceReaders = new ArrayList(); -// private List collectionReferenceReaders = new ArrayList(); -// -// private final int numberOfReturns; -// -// public MixedReturnRowReader(LoadPlan loadPlan) { -// LoadPlanVisitor.visit( -// loadPlan, -// new LoadPlanVisitationStrategyAdapter() { -// @Override -// public void startingEntityFetch(EntityFetch entityFetch) { -// entityReferenceReaders.add( new EntityReferenceReader( entityFetch ) ); -// } -// -// @Override -// public void startingCollectionFetch(CollectionFetch collectionFetch) { -// collectionReferenceReaders.add( new CollectionReferenceReader( collectionFetch ) ); -// } -// } -// ); -// -// final List readers = new ArrayList(); -// -// for ( Return rtn : loadPlan.getReturns() ) { -// final ReturnReader returnReader = buildReturnReader( rtn ); -// if ( EntityReferenceReader.class.isInstance( returnReader ) ) { -// entityReferenceReaders.add( (EntityReferenceReader) returnReader ); -// } -// readers.add( returnReader ); -// } -// -// this.returnReaders = readers; -// this.numberOfReturns = readers.size(); -// } -// -// @Override -// protected List getEntityReferenceReaders() { -// return entityReferenceReaders; -// } -// -// @Override -// protected List getCollectionReferenceReaders() { -// return collectionReferenceReaders; -// } -// -// @Override -// protected Object readLogicalRow(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException { -// Object[] logicalRow = new Object[ numberOfReturns ]; -// int pos = 0; -// for ( ReturnReader reader : returnReaders ) { -// logicalRow[pos] = reader.read( resultSet, context ); -// pos++; -// } -// return logicalRow; -// } -// } -// -// private class CollectionInitializerRowReader extends AbstractRowReader implements RowReader { -// private final CollectionReturnReader returnReader; -// -// private List entityReferenceReaders = null; -// private final List collectionReferenceReaders = new ArrayList(); -// -// public CollectionInitializerRowReader(LoadPlan loadPlan) { -// returnReader = (CollectionReturnReader) buildReturnReader( loadPlan.getReturns().get( 0 ) ); -// -// LoadPlanVisitor.visit( -// loadPlan, -// new LoadPlanVisitationStrategyAdapter() { -// @Override -// public void startingEntityFetch(EntityFetch entityFetch) { -// if ( entityReferenceReaders == null ) { -// entityReferenceReaders = new ArrayList(); -// } -// entityReferenceReaders.add( new EntityReferenceReader( entityFetch ) ); -// } -// -// @Override -// public void startingCollectionFetch(CollectionFetch collectionFetch) { -// collectionReferenceReaders.add( new CollectionReferenceReader( collectionFetch ) ); -// } -// } -// ); -// -// collectionReferenceReaders.add( returnReader ); -// } -// -// @Override -// protected List getEntityReferenceReaders() { -// return entityReferenceReaders; -// } -// -// @Override -// protected List getCollectionReferenceReaders() { -// return collectionReferenceReaders; -// } -// -// @Override -// protected Object readLogicalRow(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException { -// return returnReader.read( resultSet, context ); -// } -// } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/CollectionLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/spi/CollectionLoader.java new file mode 100644 index 0000000000..6b770e6ff4 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/loader/spi/CollectionLoader.java @@ -0,0 +1,26 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.loader.spi; + +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; + +/** + * A loader (initialization) for collections + * + * @author Steve Ebersole + */ +public interface CollectionLoader extends Loader { + @Override + PluralAttributeMapping getLoadable(); + + /** + * Load a collection by its key (not necessarily the same as its owner's PK). + */ + PersistentCollection load(Object key, SharedSessionContractImplementor session); +} diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/Loadable.java b/hibernate-core/src/main/java/org/hibernate/loader/spi/Loadable.java index 1296333162..1ecc9093d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/Loadable.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/spi/Loadable.java @@ -36,7 +36,7 @@ public interface Loadable extends ModelPart, RootTableGroupProducer { boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers); boolean isAffectedByEnabledFetchProfiles(LoadQueryInfluencers influencers); - String getPathName(); + String getRootPathName(); @Override default TableGroup createRootTableGroup( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/spi/SingleEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/spi/SingleEntityLoader.java index a74a40580b..60fc500135 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/spi/SingleEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/spi/SingleEntityLoader.java @@ -9,7 +9,6 @@ package org.hibernate.loader.spi; import org.hibernate.LockOptions; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.metamodel.mapping.EntityMappingType; -import org.hibernate.persister.entity.EntityPersister; /** * Loader for loading a single entity by primary or unique key diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionIdentifierDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionIdentifierDescriptor.java index a33f269d6f..73561d381d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionIdentifierDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionIdentifierDescriptor.java @@ -7,19 +7,11 @@ package org.hibernate.metamodel.mapping; import org.hibernate.metamodel.CollectionClassification; -import org.hibernate.query.NavigablePath; -import org.hibernate.sql.ast.tree.from.TableGroup; -import org.hibernate.sql.results.spi.DomainResult; -import org.hibernate.sql.results.spi.DomainResultCreationState; /** * Descriptor for the collection identifier. Only used with {@link CollectionClassification#IDBAG} collections * * @author Steve Ebersole */ -public interface CollectionIdentifierDescriptor { - DomainResult createDomainResult( - NavigablePath collectionPath, - TableGroup tableGroup, - DomainResultCreationState creationState); +public interface CollectionIdentifierDescriptor extends CollectionPart, BasicValuedModelPart { } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java index 7076f50e26..f1b4ebceb8 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/CollectionPart.java @@ -14,7 +14,8 @@ import org.hibernate.sql.results.spi.Fetchable; public interface CollectionPart extends ModelPart, Fetchable { enum Nature { ELEMENT( "{element}" ), - INDEX( "{index}" ); + INDEX( "{index}" ), + ID( "{collection-id}" ); private final String name; @@ -27,11 +28,22 @@ public interface CollectionPart extends ModelPart, Fetchable { } public static Nature fromName(String name) { - if ( ELEMENT.name.equals( name ) ) { + if ( "key".equals( name ) || "{key}".equals( name ) + || "keys".equals( name ) || "{keys}".equals( name ) + || "index".equals( name ) || "{index}".equals( name ) + || "indices".equals( name ) || "{indices}".equals( name ) ) { + return INDEX; + } + + if ( "element".equals( name ) || "{element}".equals( name ) + || "elements".equals( name ) || "{elements}".equals( name ) + || "value".equals( name ) || "{value}".equals( name ) + || "values".equals( name ) || "{values}".equals( name ) ) { return ELEMENT; } - else if ( INDEX.name.equals( name ) ) { - return INDEX; + + if ( ID.name.equals( name ) ) { + return ID; } throw new IllegalArgumentException( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java index 278585092b..2f7f9cc627 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityMappingType.java @@ -55,7 +55,7 @@ public interface EntityMappingType extends ManagedMappingType, Loadable { } @Override - default String getPathName() { + default String getRootPathName() { return getEntityName(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java index 3e3e7cefde..4973f85b0d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/ForeignKeyDescriptor.java @@ -11,6 +11,7 @@ import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResultCreationState; @@ -30,6 +31,13 @@ public interface ForeignKeyDescriptor extends VirtualModelPart { SqlExpressionResolver sqlExpressionResolver, SqlAstCreationContext creationContext); + Predicate generateJoinPredicate( + TableReference lhs, + TableReference rhs, + JoinType joinType, + SqlExpressionResolver sqlExpressionResolver, + SqlAstCreationContext creationContext); + @Override default String getPartName() { return PART_NAME; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java index b30a84418e..1476178222 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/PluralAttributeMapping.java @@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping; import java.util.function.Consumer; +import org.hibernate.loader.spi.Loadable; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; import org.hibernate.sql.results.spi.Fetchable; @@ -17,7 +18,7 @@ import org.hibernate.sql.results.spi.FetchableContainer; * @author Steve Ebersole */ public interface PluralAttributeMapping - extends AttributeMapping, StateArrayContributorMapping, TableGroupJoinProducer, FetchableContainer { + extends AttributeMapping, StateArrayContributorMapping, TableGroupJoinProducer, FetchableContainer, Loadable { CollectionPersister getCollectionDescriptor(); @@ -25,6 +26,13 @@ public interface PluralAttributeMapping CollectionPart getIndexDescriptor(); + interface IndexMetadata { + CollectionPart getIndexDescriptor(); + int getListIndexBase(); + } + + IndexMetadata getIndexMetadata(); + CollectionPart getElementDescriptor(); CollectionIdentifierDescriptor getIdentifierDescriptor(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/SingularAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/SingularAttributeMapping.java index 95f927f724..6d31507c37 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/SingularAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/SingularAttributeMapping.java @@ -9,6 +9,5 @@ package org.hibernate.metamodel.mapping; /** * @author Steve Ebersole */ -public interface SingularAttributeMapping - extends AttributeMapping, StateArrayContributorMapping { +public interface SingularAttributeMapping extends AttributeMapping, StateArrayContributorMapping { } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java index 44c9e95fda..a1d74829f3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicValuedCollectionPart.java @@ -171,10 +171,10 @@ public class BasicValuedCollectionPart implements CollectionPart, BasicValuedMod nature.getName() ); - final SqlSelection sqlSelection = resolveSqlSelection( - creationState.getSqlAstCreationState().getFromClauseAccess().findTableGroup( fetchablePath.getParent() ), - creationState - ); + final TableGroup tableGroup = creationState.getSqlAstCreationState() + .getFromClauseAccess() + .findTableGroup( fetchablePath.getParent() ); + final SqlSelection sqlSelection = resolveSqlSelection( tableGroup, creationState ); return new BasicFetch( sqlSelection.getValuesArrayPosition(), diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java index 24bf03314e..7da19d7589 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/CollectionIdentifierDescriptorImpl.java @@ -6,45 +6,152 @@ */ package org.hibernate.metamodel.mapping.internal; +import org.hibernate.LockMode; +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; -import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.internal.domain.basic.BasicFetch; import org.hibernate.sql.results.internal.domain.basic.BasicResult; import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParent; import org.hibernate.type.BasicType; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * @author Steve Ebersole */ public class CollectionIdentifierDescriptorImpl implements CollectionIdentifierDescriptor { private final CollectionPersister collectionDescriptor; + private final String containingTableName; private final String columnName; private final BasicType type; public CollectionIdentifierDescriptorImpl( CollectionPersister collectionDescriptor, + String containingTableName, String columnName, BasicType type) { this.collectionDescriptor = collectionDescriptor; + this.containingTableName = containingTableName; this.columnName = columnName; this.type = type; } @Override + public Nature getNature() { + return Nature.ID; + } + + @Override + public String getContainingTableExpression() { + return containingTableName; + } + + @Override + public String getMappedColumnExpression() { + return columnName; + } + + @Override + public BasicValueConverter getConverter() { + return null; + } + + @Override + public MappingType getPartTypeDescriptor() { + return type; + } + + @Override + public JdbcMapping getJdbcMapping() { + return type; + } + + @Override + public MappingType getMappedTypeDescriptor() { + return type; + } + + @Override + public JavaTypeDescriptor getJavaTypeDescriptor() { + return getMappedTypeDescriptor().getMappedJavaTypeDescriptor(); + } + + @Override + public String getFetchableName() { + return null; + } + + @Override + public FetchStrategy getMappedFetchStrategy() { + return null; + } + + @Override + public Fetch generateFetch( + FetchParent fetchParent, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + LockMode lockMode, + String resultVariable, + DomainResultCreationState creationState) { + final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess(); + final TableGroup tableGroup = fromClauseAccess.getTableGroup( fetchablePath ); + + final SqlAstCreationState astCreationState = creationState.getSqlAstCreationState(); + final SqlAstCreationContext astCreationContext = astCreationState.getCreationContext(); + final SessionFactoryImplementor sessionFactory = astCreationContext.getSessionFactory(); + final SqlExpressionResolver sqlExpressionResolver = astCreationState.getSqlExpressionResolver(); + + final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( + sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( + tableGroup.getPrimaryTableReference(), + columnName + ), + p -> new ColumnReference( + tableGroup.getPrimaryTableReference().getIdentificationVariable(), + columnName, + type, + sessionFactory + ) + ), + type.getJavaTypeDescriptor(), + sessionFactory.getTypeConfiguration() + ); + + return new BasicFetch<>( + sqlSelection.getValuesArrayPosition(), + fetchParent, + fetchablePath, + this, + ! selected, + getConverter(), + FetchTiming.IMMEDIATE, + creationState + ); + } + public DomainResult createDomainResult( NavigablePath collectionPath, TableGroup tableGroup, DomainResultCreationState creationState) { - - final SqlAstCreationState astCreationState = creationState.getSqlAstCreationState(); final SqlAstCreationContext astCreationContext = astCreationState.getCreationContext(); final SessionFactoryImplementor sessionFactory = astCreationContext.getSessionFactory(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java index 3696c80167..829b2f9899 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java @@ -14,6 +14,10 @@ import org.hibernate.metamodel.mapping.CollectionPart; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.internal.domain.collection.EntityCollectionPartTableGroup; +import org.hibernate.sql.results.internal.domain.entity.EntityFetch; +import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResultCreationState; import org.hibernate.sql.results.spi.Fetch; import org.hibernate.sql.results.spi.FetchParent; @@ -71,6 +75,32 @@ public class EntityCollectionPart implements CollectionPart, EntityValuedModelPa LockMode lockMode, String resultVariable, DomainResultCreationState creationState) { + creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup( + fetchablePath, + np -> { + final TableGroup collectionTableGroup = creationState.getSqlAstCreationState() + .getFromClauseAccess() + .getTableGroup( fetchablePath.getParent() ); + return new EntityCollectionPartTableGroup( fetchablePath, collectionTableGroup, this ); + } + ); + return new EntityFetch( fetchParent, this, lockMode, selected, fetchablePath, creationState ); + } + + @Override + public DomainResult createDomainResult( + NavigablePath navigablePath, + TableGroup tableGroup, + String resultVariable, + DomainResultCreationState creationState) { + throw new NotYetImplementedFor6Exception( getClass() ); + } + + @Override + public void applySqlSelections( + NavigablePath navigablePath, + TableGroup tableGroup, + DomainResultCreationState creationState) { throw new NotYetImplementedFor6Exception( getClass() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java index 828da19f69..e7be0be38f 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java @@ -763,6 +763,7 @@ public class MappingModelCreationHelper { identifierDescriptor = new CollectionIdentifierDescriptorImpl( collectionDescriptor, + tableExpression, identifierColumnName, (BasicType) loadableCollection.getIdentifierType() ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java index 54aa094e4a..cef69538c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/PluralAttributeMappingImpl.java @@ -7,12 +7,15 @@ package org.hibernate.metamodel.mapping.internal; import java.util.function.Consumer; +import java.util.function.Supplier; import org.hibernate.LockMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.CascadeStyle; +import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.mapping.Collection; +import org.hibernate.mapping.List; import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor; import org.hibernate.metamodel.mapping.CollectionMappingType; import org.hibernate.metamodel.mapping.CollectionPart; @@ -26,20 +29,22 @@ import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.Joinable; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.query.NavigablePath; -import org.hibernate.sql.ast.spi.SqlAstCreationState; -import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAliasStemHelper; import org.hibernate.sql.ast.spi.SqlAstCreationContext; +import org.hibernate.sql.ast.spi.SqlAstCreationState; +import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupBuilder; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableReferenceCollector; import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.results.internal.domain.collection.CollectionDomainResult; import org.hibernate.sql.results.internal.domain.collection.DelayedCollectionFetch; import org.hibernate.sql.results.internal.domain.collection.EagerCollectionFetch; +import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResultCreationState; import org.hibernate.sql.results.spi.Fetch; import org.hibernate.sql.results.spi.FetchParent; @@ -48,6 +53,10 @@ import org.hibernate.sql.results.spi.FetchParent; * @author Steve Ebersole */ public class PluralAttributeMappingImpl extends AbstractAttributeMapping implements PluralAttributeMapping { + public interface Aware { + void injectAttributeMapping(PluralAttributeMapping attributeMapping); + } + private final int stateArrayPosition; private final PropertyAccess propertyAccess; private final StateArrayContributorMetadataAccess stateArrayContributorMetadataAccess; @@ -65,6 +74,8 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme private final String sqlAliasStem; + private final IndexMetadata indexMetadata; + @SuppressWarnings("WeakerAccess") public PluralAttributeMappingImpl( String attributeName, @@ -101,6 +112,33 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme else { separateCollectionTable = ( (Joinable) collectionDescriptor ).getTableName(); } + + indexMetadata = new IndexMetadata() { + final int baseIndex; + + { + if ( bootDescriptor instanceof List ) { + baseIndex = ( (List) bootDescriptor ).getBaseIndex(); + } + else { + baseIndex = -1; + } + + } + + @Override + public CollectionPart getIndexDescriptor() { + return indexDescriptor; + } + + @Override + public int getListIndexBase() { + return baseIndex; + } + }; + if ( collectionDescriptor instanceof Aware ) { + ( (Aware) collectionDescriptor ).injectAttributeMapping( this ); + } } @Override @@ -128,6 +166,11 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme return indexDescriptor; } + @Override + public IndexMetadata getIndexMetadata() { + return indexMetadata; + } + @Override public CollectionIdentifierDescriptor getIdentifierDescriptor() { return identifierDescriptor; @@ -163,6 +206,22 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme return fetchStrategy; } + @Override + public DomainResult createDomainResult( + NavigablePath navigablePath, + TableGroup tableGroup, + String resultVariable, + DomainResultCreationState creationState) { + final TableGroup collectionTableGroup = creationState.getSqlAstCreationState() + .getFromClauseAccess() + .getTableGroup( navigablePath ); + + assert collectionTableGroup != null; + + //noinspection unchecked + return new CollectionDomainResult( navigablePath, this, resultVariable, tableGroup, creationState ); + } + @Override public Fetch generateFetch( FetchParent fetchParent, @@ -201,7 +260,6 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme return new EagerCollectionFetch( fetchablePath, this, - fkDescriptor.createDomainResult( fetchablePath, collectionTableGroup, creationState ), getAttributeMetadataAccess().resolveAttributeMetadata( null ).isNullable(), fetchParent, creationState @@ -280,6 +338,57 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme getCollectionDescriptor().applyTableReferences( sqlAliasBase, baseJoinType, collector, sqlExpressionResolver, creationContext ); } + @Override + public TableGroup createRootTableGroup( + NavigablePath navigablePath, + String explicitSourceAlias, + JoinType tableReferenceJoinType, + LockMode lockMode, + SqlAliasBaseGenerator aliasBaseGenerator, + SqlExpressionResolver sqlExpressionResolver, + Supplier> additionalPredicateCollectorAccess, + SqlAstCreationContext creationContext) { + final SqlAliasBase sqlAliasBase = aliasBaseGenerator.createSqlAliasBase( getSqlAliasStem() ); + + final TableGroupBuilder tableGroupBuilder = TableGroupBuilder.builder( + navigablePath, + this, + lockMode, + sqlAliasBase, + creationContext.getSessionFactory() + ); + + applyTableReferences( + sqlAliasBase, + tableReferenceJoinType, + tableGroupBuilder, + sqlExpressionResolver, + creationContext + ); + + return tableGroupBuilder.build(); + } + + @Override + public boolean isAffectedByEnabledFilters(LoadQueryInfluencers influencers) { + return getCollectionDescriptor().isAffectedByEnabledFilters( influencers ); + } + + @Override + public boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) { + return getCollectionDescriptor().isAffectedByEntityGraph( influencers ); + } + + @Override + public boolean isAffectedByEnabledFetchProfiles(LoadQueryInfluencers influencers) { + return getCollectionDescriptor().isAffectedByEnabledFetchProfiles( influencers ); + } + + @Override + public String getRootPathName() { + return getCollectionDescriptor().getRole(); + } + @Override public ModelPart findSubPart(String name, EntityMappingType treatTargetType) { final CollectionPart.Nature nature = CollectionPart.Nature.fromName( name ); @@ -289,6 +398,9 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme else if ( nature == CollectionPart.Nature.INDEX ) { return indexDescriptor; } + else if ( nature == CollectionPart.Nature.ID ) { + return identifierDescriptor; + } return null; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index 5e0db3f58d..a1433bb935 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -10,10 +10,16 @@ import java.util.Collections; import java.util.List; import java.util.function.Consumer; +import org.hibernate.LockMode; +import org.hibernate.engine.FetchStrategy; +import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.ColumnConsumer; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.query.ComparisonOperator; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.Clause; @@ -31,6 +37,8 @@ import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.internal.domain.basic.BasicResult; import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParent; import org.hibernate.type.ForeignKeyDirection; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; import org.hibernate.type.spi.TypeConfiguration; @@ -38,7 +46,7 @@ import org.hibernate.type.spi.TypeConfiguration; /** * @author Steve Ebersole */ -public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor { +public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicValuedModelPart { private final String keyColumnContainingTable; private final String keyColumnExpression; private final String targetColumnContainingTable; @@ -97,6 +105,51 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor { ); } + @Override + public Predicate generateJoinPredicate( + TableReference lhs, + TableReference rhs, + JoinType joinType, + SqlExpressionResolver sqlExpressionResolver, + SqlAstCreationContext creationContext) { + if ( lhs.getTableExpression().equals( keyColumnContainingTable ) ) { + assert rhs.getTableExpression().equals( targetColumnContainingTable ); + return new ComparisonPredicate( + new ColumnReference( + lhs, + keyColumnExpression, + jdbcMapping, + creationContext.getSessionFactory() + ), + ComparisonOperator.EQUAL, + new ColumnReference( + rhs, + targetColumnExpression, + jdbcMapping, + creationContext.getSessionFactory() + ) + ); + } + else { + assert rhs.getTableExpression().equals( keyColumnContainingTable ); + return new ComparisonPredicate( + new ColumnReference( + lhs, + targetColumnExpression, + jdbcMapping, + creationContext.getSessionFactory() + ), + ComparisonOperator.EQUAL, + new ColumnReference( + rhs, + keyColumnExpression, + jdbcMapping, + creationContext.getSessionFactory() + ) + ); + } + } + @Override public Predicate generateJoinPredicate( TableGroup lhs, @@ -252,4 +305,52 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor { SharedSessionContractImplementor session) { valuesConsumer.consume( value, jdbcMapping ); } + + + @Override + public String getContainingTableExpression() { + return keyColumnContainingTable; + } + + @Override + public String getMappedColumnExpression() { + return keyColumnExpression; + } + + @Override + public BasicValueConverter getConverter() { + return null; + } + + @Override + public String getFetchableName() { + return PART_NAME; + } + + @Override + public FetchStrategy getMappedFetchStrategy() { + return FetchStrategy.IMMEDIATE_JOIN; + } + + @Override + public Fetch generateFetch( + FetchParent fetchParent, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + LockMode lockMode, + String resultVariable, + DomainResultCreationState creationState) { + return null; + } + + @Override + public MappingType getMappedTypeDescriptor() { + return null; + } + + @Override + public JdbcMapping getJdbcMapping() { + return jdbcMapping; + } } 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 b9eacba798..632bfcd862 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 @@ -10,6 +10,7 @@ import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; @@ -21,6 +22,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.Filter; import org.hibernate.HibernateException; +import org.hibernate.LockOptions; import org.hibernate.MappingException; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.QueryException; @@ -39,6 +41,9 @@ import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; 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.FetchProfile; +import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; @@ -56,6 +61,10 @@ import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectations; import org.hibernate.loader.collection.CollectionInitializer; +import org.hibernate.loader.internal.MetamodelSelectBuilderProcess; +import org.hibernate.loader.internal.SingleCollectionKeyLoader; +import org.hibernate.loader.internal.SubSelectFetchCollectionLoader; +import org.hibernate.loader.spi.CollectionLoader; import org.hibernate.mapping.BasicValue; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; @@ -67,6 +76,11 @@ import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Table; import org.hibernate.mapping.Value; import org.hibernate.metadata.CollectionMetadata; +import org.hibernate.metamodel.mapping.BasicValuedModelPart; +import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.entity.EntityPersister; @@ -85,6 +99,7 @@ import org.hibernate.persister.walking.spi.CompositeCollectionElementDefinition; import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.persister.walking.spi.EntityDefinition; import org.hibernate.pretty.MessageHelper; +import org.hibernate.query.ComparisonOperator; import org.hibernate.sql.Alias; import org.hibernate.sql.SelectFragment; import org.hibernate.sql.SimpleSelect; @@ -93,8 +108,16 @@ import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlExpressionResolver; +import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReferenceCollector; +import org.hibernate.sql.ast.tree.from.TableReferenceJoin; +import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; +import org.hibernate.sql.ast.tree.predicate.Predicate; +import org.hibernate.sql.ast.tree.select.SelectStatement; +import org.hibernate.sql.exec.spi.JdbcParameter; +import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.type.AnyType; import org.hibernate.type.AssociationType; import org.hibernate.type.BasicType; @@ -113,7 +136,7 @@ import org.jboss.logging.Logger; * @see OneToManyPersister */ public abstract class AbstractCollectionPersister - implements CollectionMetadata, SQLLoadableCollection { + implements CollectionMetadata, SQLLoadableCollection, PluralAttributeMappingImpl.Aware { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, AbstractCollectionPersister.class.getName() ); @@ -765,9 +788,92 @@ public abstract class AbstractCollectionPersister } } + private CollectionLoader standardCollectionLoader; + @Override public void initialize(Object key, SharedSessionContractImplementor session) throws HibernateException { - getAppropriateInitializer( key, session ).initialize( key, session ); +// getAppropriateInitializer( key, session ).initialize( key, session ); + determineLoaderToUse( key, session ).load( key, session ); + } + + protected CollectionLoader getStandardCollectionLoader() { + CollectionLoader localCopy = standardCollectionLoader; + if ( localCopy == null ) { + synchronized (this) { + if ( localCopy == null ) { + localCopy = createCollectionLoader( LoadQueryInfluencers.NONE ); + standardCollectionLoader = localCopy; + } + } + } + return localCopy; + } + + protected CollectionLoader determineLoaderToUse(Object key, SharedSessionContractImplementor session) { + if ( queryLoaderName != null ) { + // if there is a user-specified loader, return that + return getStandardCollectionLoader(); + } + + final CollectionLoader subSelectLoader = resolveSubSelectLoader( key, session ); + if ( subSelectLoader != null ) { + return subSelectLoader; + } + + if ( ! session.getLoadQueryInfluencers().hasEnabledFilters() ) { + return getStandardCollectionLoader(); + } + + return createCollectionLoader( session.getLoadQueryInfluencers() ); + } + + 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 ); + if ( subselect == null ) { + return null; + } + + // Take care of any entities that might have + // been evicted! + subselect.getResult().removeIf( o -> !persistenceContext.containsEntity( o ) ); + + // Run a subquery loader + return createSubSelectLoader( subselect, session ); + } + + protected CollectionLoader createSubSelectLoader(SubselectFetch subselect, SharedSessionContractImplementor session) { + //noinspection RedundantCast + return new SubSelectFetchCollectionLoader( + attributeMapping, + (DomainResult) null, + subselect, + session + ); + } + + protected CollectionLoader createCollectionLoader(LoadQueryInfluencers loadQueryInfluencers) { + final java.util.List jdbcParameters = new ArrayList<>(); + + final SelectStatement sqlAst = MetamodelSelectBuilderProcess.createSelect( + attributeMapping, + null, + attributeMapping.getKeyDescriptor(), + null, + 1, + loadQueryInfluencers, + LockOptions.READ, + jdbcParameters::add, + getFactory() + ); + + return new SingleCollectionKeyLoader( attributeMapping, sqlAst, jdbcParameters ); } protected CollectionInitializer getAppropriateInitializer(Object key, SharedSessionContractImplementor session) { @@ -1995,9 +2101,7 @@ public abstract class AbstractCollectionPersister @Override public boolean isAffectedByEnabledFilters(SharedSessionContractImplementor session) { - final Map enabledFilters = session.getLoadQueryInfluencers().getEnabledFilters(); - return filterHelper.isAffectedBy( enabledFilters ) || - ( isManyToMany() && manyToManyFilterHelper.isAffectedBy( enabledFilters ) ); + return isAffectedByEnabledFilters( session.getLoadQueryInfluencers() ); } public boolean isSubselectLoadable() { @@ -2382,6 +2486,47 @@ public abstract class AbstractCollectionPersister // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // "mapping model" + // todo (6.0) : atm there is no way to get a `PluralAttributeMapping` reference except through its declaring `ManagedTypeMapping` attributes. this is a backhand way + // of getting access to it for use from the persister + + private PluralAttributeMapping attributeMapping; + + @Override + public void injectAttributeMapping(PluralAttributeMapping attributeMapping) { + this.attributeMapping = attributeMapping; + } + + @Override + public boolean isAffectedByEnabledFilters(LoadQueryInfluencers influencers) { + if ( influencers.hasEnabledFilters() ) { + final Map enabledFilters = influencers.getEnabledFilters(); + return filterHelper.isAffectedBy( enabledFilters ) || + ( isManyToMany() && manyToManyFilterHelper.isAffectedBy( enabledFilters ) ); + } + + return false; + } + + @Override + public boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) { + // todo (6.0) : anything to do here? + return false; + } + + @Override + public boolean isAffectedByEnabledFetchProfiles(LoadQueryInfluencers influencers) { + if ( influencers.hasEnabledFetchProfiles() ) { + for ( String enabledFetchProfileName : influencers.getEnabledFetchProfileNames() ) { + final FetchProfile fetchProfile = getFactory().getFetchProfile( enabledFetchProfileName ); + final Fetch fetchByRole = fetchProfile.getFetchByRole( getRole() ); + if ( fetchByRole.getStyle() == Fetch.Style.JOIN ) { + return true; + } + } + } + + return false; + } @Override public void applyTableReferences( @@ -2413,6 +2558,16 @@ public abstract class AbstractCollectionPersister } if ( elementPersister != null ) { + collector.applyPrimaryJoinProducer( + (lhs, rhs) -> new TableReferenceJoin( + baseJoinType, + rhs, + generateEntityElementJoinPredicate( + lhs, rhs, baseJoinType, sqlExpressionResolver, creationContext + ) + ) + ); + elementPersister.applyTableReferences( sqlAliasBase, // todo (6.0) : determine the proper join-type to use @@ -2425,6 +2580,100 @@ public abstract class AbstractCollectionPersister } + private Predicate generateEntityElementJoinPredicate( + TableReference lhs, + TableReference rhs, + JoinType baseJoinType, + SqlExpressionResolver sqlExpressionResolver, + SqlAstCreationContext creationContext) { + final SessionFactoryImplementor sessionFactory = creationContext.getSessionFactory(); + + // `lhs` should be the collection table + // `rhs` should be the primary element table + assert lhs.getTableExpression().equals( getTableName() ); + + final String fkTargetModelPartName = getCollectionType().getRHSUniqueKeyPropertyName(); + final ModelPart fkTargetDescriptor; + if ( fkTargetModelPartName != null ) { + fkTargetDescriptor = elementPersister.findSubPart( fkTargetModelPartName ); + } + else { + fkTargetDescriptor = elementPersister.getIdentifierMapping(); + } + + final int jdbcTypeCount = fkTargetDescriptor.getJdbcTypeCount( sessionFactory.getTypeConfiguration() ); + assert jdbcTypeCount == elementColumnNames.length; + + if ( jdbcTypeCount == 1 ) { + final BasicValuedModelPart fkModelPartType = (BasicValuedModelPart) fkTargetDescriptor; + return new ComparisonPredicate( + sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( lhs, elementColumnNames[0] ), + sqlAstProcessingState -> new ColumnReference( + lhs, + elementColumnNames[0], + fkModelPartType.getJdbcMapping(), + sessionFactory + ) + ), + ComparisonOperator.EQUAL, + sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( rhs, elementColumnNames[0] ), + sqlAstProcessingState -> new ColumnReference( + rhs, + fkModelPartType.getMappedColumnExpression(), + fkModelPartType.getJdbcMapping(), + sessionFactory + ) + ) + ); + } + else { + // todo (6.0) : tuple or disjunction? + // for now use a tuple - its easier to build, even though disjunction is more universally supported at DB level + final java.util.List jdbcMappings = new ArrayList<>( jdbcTypeCount ); + final SqlTuple.Builder comparisonRhsBuilder = new SqlTuple.Builder( fkTargetDescriptor, jdbcTypeCount ); + fkTargetDescriptor.visitColumns( + (containingTableExpression, columnExpression, jdbcMapping) -> { + assert rhs.getTableExpression().equals( containingTableExpression ); + jdbcMappings.add( jdbcMapping ); + comparisonRhsBuilder.addSubExpression( + sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( rhs, elementColumnNames[0] ), + sqlAstProcessingState -> new ColumnReference( + rhs, + columnExpression, + jdbcMapping, + sessionFactory + ) + ) + ); + } + ); + final SqlTuple comparisonRhs = comparisonRhsBuilder.buildTuple(); + + final SqlTuple.Builder comparisonLhsBuilder = new SqlTuple.Builder( fkTargetDescriptor, jdbcTypeCount ); + for ( int i = 0; i < elementColumnNames.length; i++ ) { + final String lhsColumnName = elementColumnNames[i]; + final JdbcMapping jdbcMapping = jdbcMappings.get( i ); + comparisonLhsBuilder.addSubExpression( + sqlExpressionResolver.resolveSqlExpression( + SqlExpressionResolver.createColumnReferenceKey( lhs, elementColumnNames[0] ), + sqlAstProcessingState -> new ColumnReference( + lhs, + lhsColumnName, + jdbcMapping, + sessionFactory + ) + ) + ); + } + final SqlTuple comparisionLhs = comparisonLhsBuilder.buildTuple(); + + return new ComparisonPredicate( comparisionLhs, ComparisonOperator.EQUAL, comparisonRhs ); + } + } + @Override public CollectionSemantics getCollectionSemantics() { return collectionSemantics; 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 54bc7dbde8..b8d2ed3c20 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 @@ -11,6 +11,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Comparator; import java.util.Map; +import java.util.function.Consumer; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -19,23 +20,28 @@ import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.IdentifierGenerator; import org.hibernate.metadata.CollectionMetadata; import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.walking.spi.CollectionDefinition; import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.spi.SqlAliasBase; +import org.hibernate.sql.ast.spi.SqlAliasStemHelper; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.tree.from.TableReferenceCollector; import org.hibernate.sql.ast.tree.from.TableReferenceContributor; import org.hibernate.type.CollectionType; import org.hibernate.type.Type; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * A strategy for persisting a collection role. Defines a contract between @@ -72,6 +78,8 @@ import org.hibernate.type.Type; * @author Gavin King */ public interface CollectionPersister extends CollectionDefinition, TableReferenceContributor { + NavigableRole getNavigableRole(); + /** * Initialize the given collection with the given key */ @@ -87,8 +95,6 @@ public interface CollectionPersister extends CollectionDefinition, TableReferenc */ CollectionDataAccess getCacheAccessStrategy(); - NavigableRole getNavigableRole(); - /** * Get the cache structure */ @@ -301,6 +307,18 @@ public interface CollectionPersister extends CollectionDefinition, TableReferenc boolean isAffectedByEnabledFilters(SharedSessionContractImplementor session); + default boolean isAffectedByEnabledFilters(LoadQueryInfluencers influencers) { + throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" ); + } + + default boolean isAffectedByEntityGraph(LoadQueryInfluencers influencers) { + throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" ); + } + + default boolean isAffectedByEnabledFetchProfiles(LoadQueryInfluencers influencers) { + throw new UnsupportedOperationException( "CollectionPersister used for [" + getRole() + "] does not support SQL AST" ); + } + /** * Generates the collection's key column aliases, based on the given * suffix. 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 b623dfc545..4bb585c16b 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 @@ -4552,15 +4552,14 @@ public abstract class AbstractEntityPersister //Relational based Persisters should be content with this implementation protected void createLoaders() { - // We load the entity loaders for the most common lock modes. - + // create the entity loaders for the most common lock modes. noneLockLoader = createEntityLoader( LockMode.NONE ); readLockLoader = createEntityLoader( LockMode.READ ); - - // The loaders for the other lock modes are lazily loaded and will later be stored in this map, - // unless this setting is disabled - if ( ! factory.getSessionFactoryOptions().isDelayBatchFetchLoaderCreationsEnabled() ) { + // see if the user enabled delaying creation of the remaining loaders + final boolean delayCreationsEnabled = factory.getSessionFactoryOptions().isDelayBatchFetchLoaderCreationsEnabled(); + if ( ! delayCreationsEnabled ) { + // the setting is disabled -> do the creations for ( LockMode lockMode : EnumSet.complementOf( EnumSet.of( LockMode.NONE, LockMode.READ, LockMode.WRITE ) ) ) { loaders.put( lockMode, createEntityLoader( lockMode ) ); } @@ -4568,7 +4567,6 @@ public abstract class AbstractEntityPersister // And finally, create the internal merge and refresh load plans - loaders.put( "merge", new CascadeEntityLoader( this, CascadingActions.MERGE, getFactory() ) @@ -4715,7 +4713,11 @@ public abstract class AbstractEntityPersister @Override public boolean isAffectedByEntityGraph(LoadQueryInfluencers loadQueryInfluencers) { - return loadQueryInfluencers.getEffectiveEntityGraph().getGraph() != null; + if ( loadQueryInfluencers.getEffectiveEntityGraph().getGraph() == null ) { + return false; + } + + return loadQueryInfluencers.getEffectiveEntityGraph().getGraph().appliesTo( getEntityName() ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/SqlTuple.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/SqlTuple.java index fe81cf6b8d..b199a6235e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/SqlTuple.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/expression/SqlTuple.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.List; import org.hibernate.metamodel.mapping.MappingModelExpressable; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.sql.ast.spi.SqlAstWalker; /** @@ -48,6 +49,11 @@ public class SqlTuple implements Expression { this.valueMapping = valueMapping; } + public Builder(MappingModelExpressable valueMapping, int jdbcTypeCount) { + this( valueMapping ); + expressions = new ArrayList<>( jdbcTypeCount ); + } + public void addSubExpression(Expression expression) { if ( expressions == null ) { expressions = new ArrayList<>(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupBuilder.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupBuilder.java index 825f50e456..d319a7a0bf 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupBuilder.java @@ -8,6 +8,7 @@ package org.hibernate.sql.ast.tree.from; import java.util.ArrayList; import java.util.Collections; +import java.util.function.BiFunction; import java.util.function.Function; import org.hibernate.LockMode; @@ -29,6 +30,16 @@ public class TableGroupBuilder implements TableReferenceCollector { return new TableGroupBuilder( path, producer, lockMode, sqlAliasBase, sessionFactory ); } + public static TableGroupBuilder builder( + NavigablePath path, + TableGroupProducer producer, + LockMode lockMode, + SqlAliasBase sqlAliasBase, + BiFunction primaryJoinProducer, + SessionFactoryImplementor sessionFactory) { + return new TableGroupBuilder( path, producer, lockMode, sqlAliasBase, primaryJoinProducer, sessionFactory ); + } + private final NavigablePath path; private final TableGroupProducer producer; private final SessionFactoryImplementor sessionFactory; @@ -36,7 +47,7 @@ public class TableGroupBuilder implements TableReferenceCollector { private final SqlAliasBase sqlAliasBase; private final LockMode lockMode; - private final Function primaryJoinProducer; + private BiFunction primaryJoinProducer; private TableReference primaryTableReference; private TableReference secondaryTableLhs; @@ -57,7 +68,7 @@ public class TableGroupBuilder implements TableReferenceCollector { TableGroupProducer producer, LockMode lockMode, SqlAliasBase sqlAliasBase, - Function primaryJoinProducer, + BiFunction primaryJoinProducer, SessionFactoryImplementor sessionFactory) { this.path = path; this.producer = producer; @@ -67,6 +78,11 @@ public class TableGroupBuilder implements TableReferenceCollector { this.sessionFactory = sessionFactory; } + @Override + public void applyPrimaryJoinProducer(BiFunction primaryJoinProducer) { + this.primaryJoinProducer = primaryJoinProducer; + } + public TableGroup build() { if ( primaryTableReference == null ) { throw new IllegalStateException( "Primary TableReference was not specified : " + path ); @@ -87,9 +103,7 @@ public class TableGroupBuilder implements TableReferenceCollector { public void applyPrimaryReference(TableReference tableReference) { if ( primaryTableReference != null ) { assert primaryJoinProducer != null; - - addTableReferenceJoin( primaryJoinProducer.apply( tableReference ) ); - + addTableReferenceJoin( primaryJoinProducer.apply( primaryTableReference, tableReference ) ); } else { primaryTableReference = tableReference; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableReferenceCollector.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableReferenceCollector.java index f9b5ed6eeb..7be2beb8b9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableReferenceCollector.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableReferenceCollector.java @@ -6,6 +6,8 @@ */ package org.hibernate.sql.ast.tree.from; +import java.util.function.BiFunction; + import org.hibernate.sql.ast.JoinType; /** @@ -15,6 +17,8 @@ import org.hibernate.sql.ast.JoinType; * @author Steve Ebersole */ public interface TableReferenceCollector { + void applyPrimaryJoinProducer(BiFunction primaryJoinProducer); + void applyPrimaryReference(TableReference tableReference); /** diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableReferenceJoin.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableReferenceJoin.java index f4a5e07fc1..db16bc2503 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableReferenceJoin.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableReferenceJoin.java @@ -22,7 +22,7 @@ public class TableReferenceJoin implements SqlAstNode { private final Predicate predicate; public TableReferenceJoin(JoinType joinType, TableReference joinedTableBinding, Predicate predicate) { - this.joinType = joinType; + this.joinType = joinType == null ? JoinType.LEFT : joinType; this.joinedTableBinding = joinedTableBinding; this.predicate = predicate; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/LoadingCollectionEntryImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/LoadingCollectionEntryImpl.java index 39a922d566..082222412a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/LoadingCollectionEntryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/LoadingCollectionEntryImpl.java @@ -38,7 +38,7 @@ public class LoadingCollectionEntryImpl implements LoadingCollectionEntry { this.key = key; this.collectionInstance = collectionInstance; - collectionInstance.beforeInitialize( getCollectionDescriptor(), -1 ); + collectionInstance.beforeInitialize( collectionDescriptor, -1 ); collectionInstance.beginRead(); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java index 938f3a617a..56fe431956 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/StandardRowReader.java @@ -13,7 +13,6 @@ import org.hibernate.query.named.RowReaderMemento; import org.hibernate.sql.exec.spi.Callback; import org.hibernate.sql.results.spi.CollectionInitializer; import org.hibernate.sql.results.spi.DomainResultAssembler; -import org.hibernate.sql.results.spi.EntityInitializer; import org.hibernate.sql.results.spi.Initializer; import org.hibernate.sql.results.spi.JdbcValuesSourceProcessingOptions; import org.hibernate.sql.results.spi.JdbcValuesSourceProcessingState; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/AbstractFetchParent.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/AbstractFetchParent.java index a4f0fe28af..5ef8c4409d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/AbstractFetchParent.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/AbstractFetchParent.java @@ -30,6 +30,7 @@ public abstract class AbstractFetchParent implements FetchParent { public AbstractFetchParent(FetchableContainer fetchContainer, NavigablePath navigablePath) { this.fetchContainer = fetchContainer; this.navigablePath = navigablePath; + assert fetchContainer instanceof ManagedMappingType; } protected void afterInitialize(DomainResultCreationState creationState) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/ArrayInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/ArrayInitializer.java index 3cc4221637..6abe64d347 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/ArrayInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/ArrayInitializer.java @@ -8,6 +8,7 @@ package org.hibernate.sql.results.internal.domain; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.collection.internal.PersistentArrayHolder; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.spi.CollectionInitializer; @@ -44,6 +45,11 @@ public class ArrayInitializer implements CollectionInitializer { this.navigablePath = navigablePath; } + @Override + public PluralAttributeMapping getInitializedPart() { + throw new NotYetImplementedFor6Exception( getClass() ); + } + @Override public CollectionPersister getInitializingCollectionDescriptor() { return arrayDescriptor; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractCollectionInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractCollectionInitializer.java new file mode 100644 index 0000000000..b8b075c1f7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractCollectionInitializer.java @@ -0,0 +1,170 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.RowProcessingState; + +/** + * Base support for CollectionInitializer implementations + * + * @author Steve Ebersole + */ +public abstract class AbstractCollectionInitializer implements CollectionInitializer { + private final NavigablePath collectionPath; + private final PluralAttributeMapping collectionAttributeMapping; + + private final FetchParentAccess parentAccess; + + private final boolean selected; + + /** + * refers to the collection's container value - which collection-key? + */ + private final DomainResultAssembler keyContainerAssembler; + + /** + * refers to the rows entry in the collection. null indicates that the collection is empty + */ + private final DomainResultAssembler keyCollectionAssembler; + + // per-row state + private Object keyContainerValue; + private Object keyCollectionValue; + + private CollectionKey collectionKey; + + + @SuppressWarnings("WeakerAccess") + protected AbstractCollectionInitializer( + NavigablePath collectionPath, + PluralAttributeMapping collectionAttributeMapping, + FetchParentAccess parentAccess, + boolean selected, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler) { + this.collectionPath = collectionPath; + this.collectionAttributeMapping = collectionAttributeMapping; + this.parentAccess = parentAccess; + this.selected = selected; + this.keyContainerAssembler = keyContainerAssembler; + this.keyCollectionAssembler = keyCollectionAssembler; + } + + @Override + public NavigablePath getNavigablePath() { + return collectionPath; + } + + public PluralAttributeMapping getCollectionAttributeMapping() { + return collectionAttributeMapping; + } + + @Override + public PluralAttributeMapping getInitializedPart() { + return getCollectionAttributeMapping(); + } + + /** + * Are the values for performing this initialization present in the current + * {@link org.hibernate.sql.results.spi.JdbcValuesSourceProcessingState}? + * Or should a separate/subsequent select be performed + * + * todo (6.0) : opportunity for performance gain by batching these selects triggered at the end of processing the JdbcValuesSource + */ + protected boolean isSelected() { + return selected; + } + + protected FetchParentAccess getParentAccess() { + return parentAccess; + } + + /** + * The value of the container/owner side of the collection key (FK). Identifies the + * owner of the collection + */ + @SuppressWarnings("WeakerAccess") + protected Object getKeyContainerValue() { + return keyContainerValue; + } + + /** + * The value of the collection side of the collection key (FK). Identifies + * inclusion in the collection. Can be null to indicate that the current row + * does not contain any collection values + */ + @SuppressWarnings("WeakerAccess") + protected Object getKeyCollectionValue() { + return keyCollectionValue; + } + + public CollectionKey resolveCollectionKey(RowProcessingState rowProcessingState) { + resolveKey( rowProcessingState ); + return collectionKey; + } + + @Override + public void resolveKey(RowProcessingState rowProcessingState) { + if ( collectionKey != null ) { + // already resolved + return; + } + + final CollectionKey loadingKey = rowProcessingState.getCollectionKey(); + if ( loadingKey != null ) { + collectionKey = loadingKey; + return; + } + + keyContainerValue = keyContainerAssembler.assemble( + rowProcessingState, + rowProcessingState.getJdbcValuesSourceProcessingState().getProcessingOptions() + ); + + if ( keyCollectionAssembler == null || keyContainerAssembler == keyCollectionAssembler ) { + keyCollectionValue = keyContainerValue; + } + else { + keyCollectionValue = keyCollectionAssembler.assemble( + rowProcessingState, + rowProcessingState.getJdbcValuesSourceProcessingState().getProcessingOptions() + ); + } + + Object keyContainerValue = getKeyContainerValue(); + if ( keyContainerValue != null ) { + this.collectionKey = new CollectionKey( + collectionAttributeMapping.getCollectionDescriptor(), + keyContainerValue + ); + + if ( CollectionLoadingLogger.DEBUG_ENABLED ) { + CollectionLoadingLogger.INSTANCE.debugf( + "(%s) Current row collection key : %s", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), this.collectionKey.getKey() ) + ); + } + } + } + + @Override + public void finishUpRow(RowProcessingState rowProcessingState) { + keyContainerValue = null; + keyCollectionValue = null; + + collectionKey = null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractCollectionResultNode.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractCollectionResultNode.java new file mode 100644 index 0000000000..faddecaa0c --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractCollectionResultNode.java @@ -0,0 +1,53 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.CollectionResultNode; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * @author Steve Ebersole + */ +public abstract class AbstractCollectionResultNode implements CollectionResultNode { + private final NavigablePath navigablePath; + private final PluralAttributeMapping attributeMapping; + + private final String resultVariable; + + @SuppressWarnings("WeakerAccess") + protected AbstractCollectionResultNode( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + String resultVariable) { + this.navigablePath = navigablePath; + this.attributeMapping = attributeMapping; + this.resultVariable = resultVariable; + } + + @Override + public NavigablePath getNavigablePath() { + return navigablePath; + } + + + @SuppressWarnings("WeakerAccess") + protected PluralAttributeMapping getAttributeMapping() { + return attributeMapping; + } + + @Override + public JavaTypeDescriptor getResultJavaTypeDescriptor() { + return attributeMapping.getJavaTypeDescriptor(); + } + + public String getResultVariable() { + return resultVariable; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractImmediateCollectionInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractImmediateCollectionInitializer.java new file mode 100644 index 0000000000..b2a47d47eb --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/AbstractImmediateCollectionInitializer.java @@ -0,0 +1,314 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionSemantics; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionEntry; +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.results.internal.LoadingCollectionEntryImpl; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.LoadingCollectionEntry; +import org.hibernate.sql.results.spi.RowProcessingState; + +/** + * Base support for CollectionInitializer implementations that represent + * an immediate initialization of some sort (join, select, batch, sub-select) + * for a persistent collection. + * + * @implNote Mainly an intention contract wrt the immediacy of the fetch. + * + * @author Steve Ebersole + */ +@SuppressWarnings("WeakerAccess") +public abstract class AbstractImmediateCollectionInitializer extends AbstractCollectionInitializer { + private final LockMode lockMode; + + // per-row state + private PersistentCollection collectionInstance; + private boolean responsible; + private boolean collectionEmpty = true; + + public AbstractImmediateCollectionInitializer( + NavigablePath collectionPath, + PluralAttributeMapping collectionAttributeMapping, + FetchParentAccess parentAccess, + boolean selected, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler) { + super( collectionPath, collectionAttributeMapping, parentAccess, selected, keyContainerAssembler, keyCollectionAssembler ); + this.lockMode = lockMode; + } + + @Override + public PersistentCollection getCollectionInstance() { + return collectionInstance; + } + + @Override + public void resolveInstance(RowProcessingState rowProcessingState) { + if ( collectionInstance != null ) { + return; + } + + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContext(); + + final CollectionKey collectionKey = resolveCollectionKey( rowProcessingState ); + + if ( CollectionLoadingLogger.TRACE_ENABLED ) { + CollectionLoadingLogger.INSTANCE.tracef( + "(%s) Beginning Initializer#resolveInstance for collection : %s", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), collectionKey.getKey() ) + ); + } + + // determine the PersistentCollection instance to use and whether + // we (this initializer) is responsible for loading its state + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // First, look for a LoadingCollectionEntry + + final LoadingCollectionEntry existingLoadingEntry = persistenceContext + .getLoadContexts() + .findLoadingCollectionEntry( collectionKey ); +// final LoadingCollectionEntry existingLoadingEntry = rowProcessingState.getJdbcValuesSourceProcessingState() +// .findLoadingCollectionLocally( getCollectionDescriptor(), collectionKey.getKey() ); + + final PluralAttributeMapping collectionAttributeMapping = getCollectionAttributeMapping(); + final CollectionPersister collectionDescriptor = collectionAttributeMapping.getCollectionDescriptor(); + final CollectionSemantics collectionSemantics = collectionDescriptor.getCollectionSemantics(); + + if ( existingLoadingEntry != null ) { + collectionInstance = existingLoadingEntry.getCollectionInstance(); + + if ( CollectionLoadingLogger.DEBUG_ENABLED ) { + CollectionLoadingLogger.INSTANCE.debugf( + "(%s) Found existing loading collection entry [%s]; using loading collection instance - %s", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), collectionKey.getKey() ), + LoggingHelper.toLoggableString( collectionInstance ) + ); + } + + if ( existingLoadingEntry.getInitializer() == this ) { + // we are responsible for loading the collection values + responsible = true; + } + else { + // the entity is already being loaded elsewhere + if ( CollectionLoadingLogger.DEBUG_ENABLED ) { + CollectionLoadingLogger.INSTANCE.debugf( + "(%s) Collection [%s] being loaded by another initializer [%s] - skipping processing", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), collectionKey.getKey() ), + existingLoadingEntry.getInitializer() + ); + } + + // EARLY EXIT!!! + return; + } + } + else { + final PersistentCollection existing = persistenceContext.getCollection( collectionKey ); + if ( existing != null ) { + collectionInstance = existing; + + // we found the corresponding collection instance on the Session. If + // it is already initialized we have nothing to do + + if ( collectionInstance.wasInitialized() ) { + if ( CollectionLoadingLogger.DEBUG_ENABLED ) { + CollectionLoadingLogger.INSTANCE.debugf( + "(%s) Found existing collection instance [%s] in Session; skipping processing - [%s]", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), collectionKey.getKey() ), + LoggingHelper.toLoggableString( collectionInstance ) + ); + } + + // EARLY EXIT!!! + return; + } + else { + assert isSelected(); + takeResponsibility( rowProcessingState, collectionKey ); + } + } + else { + final PersistentCollection existingUnowned = persistenceContext.useUnownedCollection( collectionKey ); + if ( existingUnowned != null ) { + collectionInstance = existingUnowned; + + // we found the corresponding collection instance as unowned on the Session. If + // it is already initialized we have nothing to do + + if ( collectionInstance.wasInitialized() ) { + if ( CollectionLoadingLogger.DEBUG_ENABLED ) { + CollectionLoadingLogger.INSTANCE.debugf( + "(%s) Found existing unowned collection instance [%s] in Session; skipping processing - [%s]", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), collectionKey.getKey() ), + LoggingHelper.toLoggableString( collectionInstance ) + ); + } + + // EARLY EXIT!!! + return; + } + else { + assert isSelected(); + takeResponsibility( rowProcessingState, collectionKey ); + } + } + } + + if ( ! isSelected() ) { + collectionInstance = collectionSemantics.instantiateWrapper( + collectionKey.getKey(), + collectionDescriptor, + session + ); + persistenceContext.addNonLazyCollection( collectionInstance ); + + // EARLY EXIT!!! + return; + } + } + + if ( collectionInstance == null && collectionKey != null ) { + collectionInstance = collectionSemantics.instantiateWrapper( + collectionKey.getKey(), + getInitializingCollectionDescriptor(), + session + ); + + if ( CollectionLoadingLogger.DEBUG_ENABLED ) { + CollectionLoadingLogger.INSTANCE.debugf( + "(%s) Created new collection wrapper [%s] : %s", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), collectionKey.getKey() ), + LoggingHelper.toLoggableString( collectionInstance ) + ); + } + + persistenceContext.addUninitializedCollection( collectionDescriptor, collectionInstance, collectionKey.getKey() ); + + takeResponsibility( rowProcessingState, collectionKey ); + } + + if ( responsible ) { + if ( CollectionLoadingLogger.DEBUG_ENABLED ) { + CollectionLoadingLogger.INSTANCE.debugf( + "(%s) Responsible for loading collection [%s] : %s", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), collectionKey.getKey() ), + LoggingHelper.toLoggableString( collectionInstance ) + ); + } + + if ( getParentAccess() != null ) { + getParentAccess().registerResolutionListener( + owner -> collectionInstance.setOwner( owner ) + ); + } + +// if ( getCollectionDescriptor().getSemantics().getCollectionClassification() == CollectionClassification.ARRAY ) { +// persistenceContext.addCollectionHolder( collectionInstance ); +// } + } + } + + protected void takeResponsibility(RowProcessingState rowProcessingState, CollectionKey collectionKey) { + rowProcessingState.getJdbcValuesSourceProcessingState().registerLoadingCollection( + collectionKey, + new LoadingCollectionEntryImpl( + getCollectionAttributeMapping().getCollectionDescriptor(), + this, + collectionKey.getKey(), + collectionInstance + ) + ); + responsible = true; + } + + @Override + public void initializeInstance(RowProcessingState rowProcessingState) { + if ( !responsible ) { + return; + } + + final PersistenceContext persistenceContext = rowProcessingState.getSession().getPersistenceContext(); + + // the LHS key value of the association + final CollectionKey collectionKey = resolveCollectionKey( rowProcessingState ); + // the RHS key value of the association + final Object keyCollectionValue = getKeyCollectionValue(); + + if ( keyCollectionValue != null ) { + // the row contains an element in the collection... + if ( CollectionLoadingLogger.DEBUG_ENABLED ) { + CollectionLoadingLogger.INSTANCE.debugf( + "(%s) Reading element from row for collection [%s] -> %s", + StringHelper.collapse( this.getClass().getName() ), + LoggingHelper.toLoggableString( getNavigablePath(), collectionKey.getKey() ), + LoggingHelper.toLoggableString( collectionInstance ) + ); + } + + readCollectionRow( rowProcessingState ); + collectionEmpty = false; + } + } + + protected abstract void readCollectionRow(RowProcessingState rowProcessingState); + + @Override + public void finishUpRow(RowProcessingState rowProcessingState) { + super.finishUpRow( rowProcessingState ); + + collectionInstance = null; + responsible = false; + collectionEmpty = true; + } + + @Override + public void endLoading(ExecutionContext context) { + if ( getParentAccess() == null && collectionEmpty ) { + // collection is empty; handle special logic here. + final CollectionKey collectionKey = context.getCollectionKey(); + if ( collectionKey != null ) { + // We expected to load a collection with this collection key but we found the collection + // contained no results, therefore we need to do the collection init phase here because + // the LoadingCollectionEntry won't finalize this for us without at least one row. + final PersistenceContext persistenceContext = context.getSession().getPersistenceContext(); + final PersistentCollection collection = persistenceContext.getCollection( collectionKey ); + collection.beforeInitialize( getCollectionAttributeMapping().getCollectionDescriptor(), 0 ); + collection.beginRead(); + collection.endRead(); + + final CollectionEntry entry = persistenceContext.getCollectionEntry( collection ); + if ( entry != null ) { + entry.postInitialize( collection ); + } + } + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ArrayInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ArrayInitializer.java new file mode 100644 index 0000000000..321fd4fc5a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ArrayInitializer.java @@ -0,0 +1,70 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.LockMode; +import org.hibernate.collection.internal.PersistentArrayHolder; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.RowProcessingState; + +/** + * @author Chris Cranford + */ +public class ArrayInitializer extends AbstractImmediateCollectionInitializer { + private final DomainResultAssembler listIndexAssembler; + private final DomainResultAssembler elementAssembler; + + private final int indexBase; + + public ArrayInitializer( + NavigablePath navigablePath, + PluralAttributeMapping arrayDescriptor, + FetchParentAccess parentAccess, + boolean selected, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + DomainResultAssembler listIndexAssembler, + DomainResultAssembler elementAssembler) { + super( + navigablePath, + arrayDescriptor, + parentAccess, + selected, + lockMode, + keyContainerAssembler, + keyCollectionAssembler + ); + this.listIndexAssembler = listIndexAssembler; + this.elementAssembler = elementAssembler; + + this.indexBase = getCollectionAttributeMapping().getIndexMetadata().getListIndexBase(); + } + + @Override + public PersistentArrayHolder getCollectionInstance() { + return (PersistentArrayHolder) super.getCollectionInstance(); + } + + @Override + protected void readCollectionRow(RowProcessingState rowProcessingState) { + int index = (int) listIndexAssembler.assemble( rowProcessingState ); + if ( indexBase != 0 ) { + index -= indexBase; + } + getCollectionInstance().load( index, elementAssembler.assemble( rowProcessingState ) ); + } + + @Override + public String toString() { + return "ArrayInitializer{" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ArrayInitializerProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ArrayInitializerProducer.java new file mode 100644 index 0000000000..9e3bd23644 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ArrayInitializerProducer.java @@ -0,0 +1,72 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import java.util.function.Consumer; + +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.AssemblerCreationState; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.Initializer; + +/** + * @author Chris Cranford + */ +public class ArrayInitializerProducer implements CollectionInitializerProducer { + private final PluralAttributeMapping arrayDescriptor; + private final boolean joined; + private final Fetch listIndexFetch; + private final Fetch elementFetch; + + public ArrayInitializerProducer( + PluralAttributeMapping arrayDescriptor, + boolean joined, + Fetch listIndexFetch, + Fetch elementFetch) { + this.arrayDescriptor = arrayDescriptor; + this.joined = joined; + this.listIndexFetch = listIndexFetch; + this.elementFetch = elementFetch; + } + + @Override + public CollectionInitializer produceInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + Consumer initializerConsumer, + AssemblerCreationState creationState) { + return new ArrayInitializer( + navigablePath, + arrayDescriptor, + parentAccess, + joined, + lockMode, + keyContainerAssembler, + keyCollectionAssembler, + listIndexFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ), + elementFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/BagInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/BagInitializer.java new file mode 100644 index 0000000000..4745c3c14f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/BagInitializer.java @@ -0,0 +1,62 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.LockMode; +import org.hibernate.collection.internal.PersistentBag; +import org.hibernate.collection.internal.PersistentIdentifierBag; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.RowProcessingState; + +/** + * Initializer for both {@link PersistentBag} and {@link PersistentIdentifierBag} + * collections + * + * @author Steve Ebersole + */ +public class BagInitializer extends AbstractImmediateCollectionInitializer { + private final DomainResultAssembler elementAssembler; + private final DomainResultAssembler collectionIdAssembler; + + public BagInitializer( + PluralAttributeMapping bagDescriptor, + FetchParentAccess parentAccess, + NavigablePath navigablePath, + boolean selected, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + DomainResultAssembler elementAssembler, + DomainResultAssembler collectionIdAssembler) { + super( navigablePath, bagDescriptor, parentAccess, selected, lockMode, keyContainerAssembler, keyCollectionAssembler ); + this.elementAssembler = elementAssembler; + this.collectionIdAssembler = collectionIdAssembler; + } + + @Override + @SuppressWarnings("unchecked") + protected void readCollectionRow(RowProcessingState rowProcessingState) { + if ( collectionIdAssembler != null ) { + ( (PersistentIdentifierBag) getCollectionInstance() ).load( + collectionIdAssembler.assemble( rowProcessingState ), + elementAssembler.assemble( rowProcessingState ) + ); + } + else { + ( (PersistentBag) getCollectionInstance() ).load( elementAssembler.assemble( rowProcessingState ) ); + } + } + + @Override + public String toString() { + return "BagInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/BagInitializerProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/BagInitializerProducer.java new file mode 100644 index 0000000000..0add24dbb3 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/BagInitializerProducer.java @@ -0,0 +1,94 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import java.util.function.Consumer; + +import org.hibernate.LockMode; + +import org.hibernate.collection.spi.CollectionInitializerProducer; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.AssemblerCreationState; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.Initializer; + +/** + * @author Steve Ebersole + */ +public class BagInitializerProducer implements CollectionInitializerProducer { + private final PluralAttributeMapping bagDescriptor; + private final boolean selected; + private final Fetch collectionIdFetch; + private final Fetch elementFetch; + + public BagInitializerProducer( + PluralAttributeMapping bagDescriptor, + boolean selected, + Fetch collectionIdFetch, + Fetch elementFetch) { + this.bagDescriptor = bagDescriptor; + this.selected = selected; + + if ( bagDescriptor.getIdentifierDescriptor() != null ) { + assert collectionIdFetch != null; + this.collectionIdFetch = collectionIdFetch; + } + else { + assert collectionIdFetch == null; + this.collectionIdFetch = null; + } + + this.elementFetch = elementFetch; + + } + + @Override + public CollectionInitializer produceInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + Consumer initializerConsumer, + AssemblerCreationState creationState) { + final DomainResultAssembler elementAssembler = elementFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ); + + final DomainResultAssembler collectionIdAssembler; + if ( bagDescriptor.getIdentifierDescriptor() == null ) { + collectionIdAssembler = null; + } + else { + collectionIdAssembler = collectionIdFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ); + } + + return new BagInitializer( + bagDescriptor, + parentAccess, + navigablePath, + selected, + lockMode, + keyContainerAssembler, + keyCollectionAssembler, + elementAssembler, + collectionIdAssembler + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionDomainResult.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionDomainResult.java new file mode 100644 index 0000000000..ccfcbd40e7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionDomainResult.java @@ -0,0 +1,315 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import java.util.List; +import java.util.function.Consumer; + +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; +import org.hibernate.collection.spi.CollectionSemantics; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.internal.LoadingCollectionEntryImpl; +import org.hibernate.sql.results.spi.AssemblerCreationState; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.CollectionResultNode; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.DomainResultCreationState; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParent; +import org.hibernate.sql.results.spi.FetchableContainer; +import org.hibernate.sql.results.spi.Initializer; +import org.hibernate.sql.results.spi.LoadContexts; +import org.hibernate.sql.results.spi.LoadingCollectionEntry; +import org.hibernate.sql.results.spi.RowProcessingState; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * @author Steve Ebersole + */ +public class CollectionDomainResult implements DomainResult, CollectionResultNode, FetchParent { + private final NavigablePath loadingPath; + private final PluralAttributeMapping loadingAttribute; + + private final String resultVariable; + + private final DomainResult fkResult; + + private final CollectionInitializerProducer initializerProducer; + + public CollectionDomainResult( + NavigablePath loadingPath, + PluralAttributeMapping loadingAttribute, + String resultVariable, + TableGroup tableGroup, + DomainResultCreationState creationState) { + this.loadingPath = loadingPath; + this.loadingAttribute = loadingAttribute; + this.resultVariable = resultVariable; + + fkResult = loadingAttribute.getKeyDescriptor().createDomainResult( + loadingPath, + tableGroup, + creationState + ); + + final CollectionSemantics collectionSemantics = loadingAttribute.getCollectionDescriptor().getCollectionSemantics(); + initializerProducer = collectionSemantics.createInitializerProducer( + loadingPath, + loadingAttribute, + this, + true, + null, + LockMode.READ, + creationState + ); + } + + @Override + public String getResultVariable() { + return resultVariable; + } + + @Override + public JavaTypeDescriptor getResultJavaTypeDescriptor() { + return loadingAttribute.getJavaTypeDescriptor(); + } + + @Override + public DomainResultAssembler createResultAssembler( + Consumer initializerCollector, + AssemblerCreationState creationState) { + + final DomainResultAssembler fkAssembler = fkResult.createResultAssembler( + initializerCollector, + creationState + ); + + final CollectionInitializer initializer = initializerProducer.produceInitializer( + loadingPath, + loadingAttribute, + null, + LockMode.READ, + fkAssembler, + fkAssembler, + initializerCollector, + creationState + ); + + initializerCollector.accept( initializer ); + + return new EagerCollectionAssembler( loadingAttribute, initializer ); + } + + @Override + public FetchableContainer getReferencedMappingContainer() { + return loadingAttribute; + } + + @Override + public FetchableContainer getReferencedMappingType() { + return getReferencedMappingContainer(); + } + + @Override + public NavigablePath getNavigablePath() { + return loadingPath; + } + + @Override + public List getFetches() { + return null; + } + + @Override + public Fetch findFetch(String fetchableName) { + return null; + } + + + private static class InitializerImpl implements CollectionInitializer { + private final NavigablePath loadingPath; + private final PluralAttributeMapping loadingCollection; + + private final DomainResultAssembler fkAssembler; + private final DomainResultAssembler elementAssembler; + private final DomainResultAssembler indexAssembler; + private final DomainResultAssembler identifierAssembler; + + private CollectionKey collectionKey; + + private boolean managing; + private Object fkValue; + + // todo (6.0) : consider using the initializer itself as the holder of the various "temp" collections + // used while reading a collection. that would mean collection-type specific initializers (List, versus Set) + private PersistentCollection instance; + + public InitializerImpl( + NavigablePath loadingPath, + PluralAttributeMapping loadingCollection, + DomainResult fkResult, + Fetch elementFetch, + Fetch indexFetch, + DomainResult identifierResult, + Consumer collector, + AssemblerCreationState creationState) { + this.loadingPath = loadingPath; + this.loadingCollection = loadingCollection; + + this.fkAssembler = fkResult.createResultAssembler( collector, creationState ); + + // questionable what should be the parent access here + this.elementAssembler = elementFetch.createAssembler( null, collector, creationState ); + this.indexAssembler = indexFetch == null + ? null + : indexFetch.createAssembler( null, collector, creationState ); + this.identifierAssembler = identifierResult == null + ? null + : identifierResult.createResultAssembler( collector, creationState ); + } + + @Override + public PluralAttributeMapping getInitializedPart() { + return loadingCollection; + } + + @Override + public PersistentCollection getCollectionInstance() { + return instance; + } + + @Override + public NavigablePath getNavigablePath() { + return loadingPath; + } + + @Override + public void finishUpRow(RowProcessingState rowProcessingState) { + collectionKey = null; + managing = false; + instance = null; + } + + @Override + public void resolveKey(RowProcessingState rowProcessingState) { + if ( collectionKey != null ) { + // already resolved + return; + } + + final Object fkValue = fkAssembler.assemble( rowProcessingState ); + assert fkValue != null; + + collectionKey = new CollectionKey( + loadingCollection.getCollectionDescriptor(), + fkValue + ); + } + + @Override + public void resolveInstance(RowProcessingState rowProcessingState) { + if ( instance != null ) { + // already resolved + return; + } + + final PersistenceContext persistenceContext = rowProcessingState.getSession().getPersistenceContext(); + + // see if the collection is already being initialized + + final LoadContexts loadContexts = persistenceContext.getLoadContexts(); + final LoadingCollectionEntry existingEntry = loadContexts.findLoadingCollectionEntry( collectionKey ); + + if ( existingEntry != null ) { + this.instance = existingEntry.getCollectionInstance(); + if ( existingEntry.getInitializer() == this ) { + this.managing = true; + } + return; + } + + // see if it has been already registered with the Session + + final PersistentCollection registeredInstance = persistenceContext.getCollection( collectionKey ); + if ( registeredInstance != null ) { + this.instance = registeredInstance; + // it was already registered, so use that wrapper. + if ( ! registeredInstance.wasInitialized() ) { + // if the existing wrapper is not initialized, we will take responsibility for initializing it + managing = true; + rowProcessingState.getJdbcValuesSourceProcessingState().registerLoadingCollection( + collectionKey, + new LoadingCollectionEntryImpl( + getInitializingCollectionDescriptor(), + this, + collectionKey, + registeredInstance + ) + ); + return; + } + } + + this.instance = makePersistentCollection( loadingCollection, collectionKey, rowProcessingState ); + this.managing = true; + + rowProcessingState.getJdbcValuesSourceProcessingState().registerLoadingCollection( + collectionKey, + new LoadingCollectionEntryImpl( + loadingCollection.getCollectionDescriptor(), + this, + collectionKey.getKey(), + instance + ) + ); + + } + + private static PersistentCollection makePersistentCollection( + PluralAttributeMapping fetchedMapping, + CollectionKey collectionKey, + RowProcessingState rowProcessingState) { + final CollectionPersister collectionDescriptor = fetchedMapping.getCollectionDescriptor(); + final CollectionSemantics collectionSemantics = collectionDescriptor.getCollectionSemantics(); + return collectionSemantics.instantiateWrapper( + collectionKey.getKey(), + collectionDescriptor, + rowProcessingState.getSession() + ); + } + + @Override + public void initializeInstance(RowProcessingState rowProcessingState) { + if ( ! managing ) { + return; + } + + final Object fkValue = fkAssembler.assemble( rowProcessingState ); + if ( fkValue == null ) { + // this row contains no collection element + return; + } + + final PersistentCollection collectionInstance = getCollectionInstance(); + collectionInstance.readFrom( + rowProcessingState, + elementAssembler, + indexAssembler, + identifierAssembler, + collectionInstance.getOwner() + ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionFetch.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionFetch.java index 6db5da4572..5ff5bf5ed2 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionFetch.java @@ -8,7 +8,6 @@ package org.hibernate.sql.results.internal.domain.collection; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.NavigablePath; -import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.spi.Fetch; import org.hibernate.sql.results.spi.FetchParent; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionLoadingLogger.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionLoadingLogger.java new file mode 100644 index 0000000000..9d1b3afe46 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/CollectionLoadingLogger.java @@ -0,0 +1,27 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.sql.results.SqlResultsLogger; + +import org.jboss.logging.BasicLogger; +import org.jboss.logging.Logger; + +/** + * @author Steve Ebersole + */ +public interface CollectionLoadingLogger extends BasicLogger { + String LOGGER_NAME = SqlResultsLogger.LOGGER_NAME + "loading.collection"; + + /** + * Static access to the logging instance + */ + Logger INSTANCE = Logger.getLogger( LOGGER_NAME ); + + boolean TRACE_ENABLED = INSTANCE.isTraceEnabled(); + boolean DEBUG_ENABLED = INSTANCE.isDebugEnabled(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/DelayedCollectionAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/DelayedCollectionAssembler.java index 25ed916c48..8f629c773f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/DelayedCollectionAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/DelayedCollectionAssembler.java @@ -6,7 +6,6 @@ */ package org.hibernate.sql.results.internal.domain.collection; -import java.io.Serializable; import java.util.function.Consumer; import org.hibernate.collection.spi.CollectionSemantics; @@ -143,8 +142,8 @@ public class DelayedCollectionAssembler implements DomainResultAssembler { } @Override - public CollectionPersister getInitializingCollectionDescriptor() { - return fetchedMapping.getCollectionDescriptor(); + public PluralAttributeMapping getInitializedPart() { + return fetchedMapping; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/DelayedCollectionInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/DelayedCollectionInitializer.java new file mode 100644 index 0000000000..5e031eb28f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/DelayedCollectionInitializer.java @@ -0,0 +1,113 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.collection.spi.CollectionSemantics; +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.metamodel.CollectionClassification; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.persister.collection.CollectionPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.RowProcessingState; + +/** + * @author Steve Ebersole + */ +public class DelayedCollectionInitializer extends AbstractCollectionInitializer { + + // per-row state + private PersistentCollection collectionInstance; + + public DelayedCollectionInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler) { + super( navigablePath, attributeMapping, parentAccess, false, keyContainerAssembler, keyCollectionAssembler ); + } + + @Override + public PersistentCollection getCollectionInstance() { + return collectionInstance; + } + + @Override + public void resolveKey(RowProcessingState rowProcessingState) { + super.resolveKey( rowProcessingState ); + + getParentAccess().registerResolutionListener( + owner -> collectionInstance.setOwner( owner ) + ); + } + + @Override + public void resolveInstance(RowProcessingState rowProcessingState) { + final CollectionKey collectionKey = resolveCollectionKey( rowProcessingState ); + if(collectionKey != null) { + + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContext(); + + final PluralAttributeMapping attributeMapping = getCollectionAttributeMapping(); + final CollectionPersister collectionDescriptor = attributeMapping.getCollectionDescriptor(); + + // todo (6.0) : look for LoadingCollectionEntry? + + final PersistentCollection existing = persistenceContext.getCollection( collectionKey ); + if ( existing != null ) { + collectionInstance = existing; + } + else { + final CollectionSemantics collectionSemantics = collectionDescriptor.getCollectionSemantics(); + + collectionInstance = collectionSemantics.instantiateWrapper( + collectionKey.getKey(), + collectionDescriptor, + session + ); + + getParentAccess().registerResolutionListener( + owner -> collectionInstance.setOwner( owner ) + ); + + persistenceContext.addUninitializedCollection( + collectionDescriptor, + collectionInstance, + collectionKey.getKey() + ); + + if ( collectionSemantics.getCollectionClassification() == CollectionClassification.ARRAY ) { + session.getPersistenceContext().addCollectionHolder( collectionInstance ); + } + } + } + } + + @Override + public void initializeInstance(RowProcessingState rowProcessingState) { + /// nothing to do + } + + @Override + public void finishUpRow(RowProcessingState rowProcessingState) { + collectionInstance = null; + + super.finishUpRow( rowProcessingState ); + } + + @Override + public String toString() { + return "DelayedCollectionInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EagerCollectionAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EagerCollectionAssembler.java index 16d9a65d47..16ae2be9a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EagerCollectionAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EagerCollectionAssembler.java @@ -6,26 +6,10 @@ */ package org.hibernate.sql.results.internal.domain.collection; -import java.util.function.Consumer; - -import org.hibernate.collection.spi.CollectionSemantics; -import org.hibernate.collection.spi.PersistentCollection; -import org.hibernate.engine.spi.CollectionKey; -import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.persister.collection.CollectionPersister; -import org.hibernate.query.NavigablePath; -import org.hibernate.sql.results.internal.LoadingCollectionEntryImpl; -import org.hibernate.sql.results.spi.AssemblerCreationState; import org.hibernate.sql.results.spi.CollectionInitializer; -import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResultAssembler; -import org.hibernate.sql.results.spi.Fetch; -import org.hibernate.sql.results.spi.FetchParentAccess; -import org.hibernate.sql.results.spi.Initializer; import org.hibernate.sql.results.spi.JdbcValuesSourceProcessingOptions; -import org.hibernate.sql.results.spi.LoadContexts; -import org.hibernate.sql.results.spi.LoadingCollectionEntry; import org.hibernate.sql.results.spi.RowProcessingState; import org.hibernate.type.descriptor.java.JavaTypeDescriptor; @@ -34,36 +18,13 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; */ public class EagerCollectionAssembler implements DomainResultAssembler { private final PluralAttributeMapping fetchedMapping; - private final FetchParentAccess parentAccess; - private final CollectionInitializer initializer; public EagerCollectionAssembler( - NavigablePath fetchPath, PluralAttributeMapping fetchedMapping, - DomainResult fkResult, - Fetch elementFetch, - Fetch indexFetch, - DomainResult identifierResult, - FetchParentAccess parentAccess, - Consumer collector, - AssemblerCreationState creationState) { + CollectionInitializer initializer) { this.fetchedMapping = fetchedMapping; - this.parentAccess = parentAccess; - - this.initializer = new InitializerImpl( - fetchPath, - fetchedMapping, - parentAccess, - fkResult, - elementFetch, - indexFetch, - identifierResult, - collector, - creationState - ); - - collector.accept( initializer ); + this.initializer = initializer; } @Override @@ -76,183 +37,4 @@ public class EagerCollectionAssembler implements DomainResultAssembler { return fetchedMapping.getJavaTypeDescriptor(); } - private static class InitializerImpl implements CollectionInitializer { - private final NavigablePath fetchedPath; - private final PluralAttributeMapping fetchedMapping; - private final FetchParentAccess parentAccess; - - private final DomainResultAssembler fkAssembler; - private final DomainResultAssembler elementAssembler; - private final DomainResultAssembler indexAssembler; - private final DomainResultAssembler identifierAssembler; - - private CollectionKey collectionKey; - - private boolean managing; - private Object fkValue; - - // todo (6.0) : consider using the initializer itself as the holder of the various "temp" collections - // used while reading a collection. that would mean collection-type specific initializers (List, versus Set) - private PersistentCollection instance; - - public InitializerImpl( - NavigablePath fetchedPath, - PluralAttributeMapping fetchedMapping, - FetchParentAccess parentAccess, - DomainResult fkResult, - Fetch elementFetch, - Fetch indexFetch, - DomainResult identifierResult, - Consumer collector, - AssemblerCreationState creationState) { - this.fetchedPath = fetchedPath; - this.fetchedMapping = fetchedMapping; - this.parentAccess = parentAccess; - - this.fkAssembler = fkResult.createResultAssembler( collector, creationState ); - - // questionable what should be the parent access here - this.elementAssembler = elementFetch.createAssembler( parentAccess, collector, creationState ); - this.indexAssembler = indexFetch == null - ? null - : indexFetch.createAssembler( parentAccess, collector, creationState ); - this.identifierAssembler = identifierResult == null - ? null - : identifierResult.createResultAssembler( collector, creationState ); - } - - @Override - public CollectionPersister getInitializingCollectionDescriptor() { - return fetchedMapping.getCollectionDescriptor(); - } - - @Override - public PersistentCollection getCollectionInstance() { - return instance; - } - - @Override - public NavigablePath getNavigablePath() { - return fetchedPath; - } - - @Override - public void finishUpRow(RowProcessingState rowProcessingState) { - collectionKey = null; - managing = false; - instance = null; - } - - @Override - public void resolveKey(RowProcessingState rowProcessingState) { - if ( collectionKey != null ) { - // already resolved - return; - } - - collectionKey = new CollectionKey( - fetchedMapping.getCollectionDescriptor(), - parentAccess.getParentKey() - ); - - final Object fkValue = fkAssembler.assemble( rowProcessingState ); - if ( fkValue == null ) { - // this row has no collection element - return; - } - } - - @Override - public void resolveInstance(RowProcessingState rowProcessingState) { - if ( instance != null ) { - // already resolved - return; - } - - final PersistenceContext persistenceContext = rowProcessingState.getSession().getPersistenceContext(); - - // see if the collection is already being initialized - - final LoadContexts loadContexts = persistenceContext.getLoadContexts(); - final LoadingCollectionEntry existingEntry = loadContexts.findLoadingCollectionEntry( collectionKey ); - - if ( existingEntry != null ) { - this.instance = existingEntry.getCollectionInstance(); - if ( existingEntry.getInitializer() == this ) { - this.managing = true; - } - return; - } - - // see if it has been already registered with the Session - - final PersistentCollection registeredInstance = persistenceContext.getCollection( collectionKey ); - if ( registeredInstance != null ) { - this.instance = registeredInstance; - // it was already registered, so use that wrapper. - if ( ! registeredInstance.wasInitialized() ) { - // if the existing wrapper is not initialized, we will take responsibility for initializing it - managing = true; - rowProcessingState.getJdbcValuesSourceProcessingState().registerLoadingCollection( - collectionKey, - new LoadingCollectionEntryImpl( - getInitializingCollectionDescriptor(), - this, - collectionKey, - registeredInstance - ) - ); - return; - } - } - - this.instance = makePersistentCollection( fetchedMapping, collectionKey, rowProcessingState ); - this.managing = true; - - rowProcessingState.getJdbcValuesSourceProcessingState().registerLoadingCollection( - collectionKey, - new LoadingCollectionEntryImpl( - fetchedMapping.getCollectionDescriptor(), - this, - collectionKey.getKey(), - instance - ) - ); - - } - - private static PersistentCollection makePersistentCollection( - PluralAttributeMapping fetchedMapping, - CollectionKey collectionKey, - RowProcessingState rowProcessingState) { - final CollectionPersister collectionDescriptor = fetchedMapping.getCollectionDescriptor(); - final CollectionSemantics collectionSemantics = collectionDescriptor.getCollectionSemantics(); - return collectionSemantics.instantiateWrapper( - collectionKey.getKey(), - collectionDescriptor, - rowProcessingState.getSession() - ); - } - - @Override - public void initializeInstance(RowProcessingState rowProcessingState) { - if ( ! managing ) { - return; - } - - final Object fkValue = fkAssembler.assemble( rowProcessingState ); - if ( fkValue == null ) { - // this row contains no collection element - return; - } - - getCollectionInstance().readFrom( - rowProcessingState, - elementAssembler, - indexAssembler, - identifierAssembler, - parentAccess.getFetchParentInstance() - ); - } - } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EagerCollectionFetch.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EagerCollectionFetch.java index 76abda93f2..baaddec0c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EagerCollectionFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EagerCollectionFetch.java @@ -9,12 +9,17 @@ package org.hibernate.sql.results.internal.domain.collection; import java.util.List; import java.util.function.Consumer; -import org.hibernate.metamodel.mapping.CollectionIdentifierDescriptor; +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; +import org.hibernate.collection.spi.CollectionSemantics; import org.hibernate.metamodel.mapping.CollectionPart; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.results.spi.AssemblerCreationState; +import org.hibernate.sql.results.spi.CollectionInitializer; import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResultAssembler; import org.hibernate.sql.results.spi.DomainResultCreationState; @@ -29,31 +34,43 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; * @author Steve Ebersole */ public class EagerCollectionFetch extends CollectionFetch implements FetchParent { - private final DomainResult fkResult; + private final DomainResult keyContainerResult; + private final DomainResult keyCollectionResult; private final Fetch elementFetch; private final Fetch indexFetch; - private final DomainResult identifierResult; private final List fetches; + private final CollectionInitializerProducer initializerProducer; + public EagerCollectionFetch( NavigablePath fetchedPath, PluralAttributeMapping fetchedAttribute, - DomainResult fkResult, boolean nullable, FetchParent fetchParent, DomainResultCreationState creationState) { super( fetchedPath, fetchedAttribute, nullable, fetchParent ); - this.fkResult = fkResult; - final CollectionIdentifierDescriptor identifierDescriptor = fetchedAttribute.getIdentifierDescriptor(); - if ( identifierDescriptor == null ) { - this.identifierResult = null; + final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess(); + final TableGroup collectionTableGroup = fromClauseAccess.getTableGroup( fetchedPath ); + final NavigablePath parentPath = fetchedPath.getParent(); + final TableGroup parentTableGroup = parentPath == null ? null : fromClauseAccess.findTableGroup( parentPath ); + + final ForeignKeyDescriptor keyDescriptor = fetchedAttribute.getKeyDescriptor(); + if ( parentTableGroup != null ) { + // join fetch + keyContainerResult = keyDescriptor.createDomainResult( fetchedPath, parentTableGroup, creationState ); + keyCollectionResult = keyDescriptor.createDomainResult( fetchedPath, collectionTableGroup, creationState ); } else { - final TableGroup collectionTableGroup = creationState.getSqlAstCreationState().getFromClauseAccess().getTableGroup( fetchedPath ); - this.identifierResult = identifierDescriptor.createDomainResult( fetchedPath, collectionTableGroup, creationState ); + // select fetch + // todo (6.0) : we could potentially leverage batch fetching for performance + keyContainerResult = keyDescriptor.createDomainResult( fetchedPath, collectionTableGroup, creationState ); + + // use null for `keyCollectionResult`... the initializer will see that as trigger to use + // the assembled container-key value as the collection-key value. + keyCollectionResult = null; } fetches = creationState.visitFetches( this ); @@ -67,6 +84,18 @@ public class EagerCollectionFetch extends CollectionFetch implements FetchParent indexFetch = null; elementFetch = fetches.get( 0 ); } + + final CollectionSemantics collectionSemantics = getFetchedMapping().getCollectionDescriptor().getCollectionSemantics(); + initializerProducer = collectionSemantics.createInitializerProducer( + fetchedPath, + fetchedAttribute, + fetchParent, + nullable, + null, + // todo (6.0) : we need to propagate these lock modes + LockMode.READ, + creationState + ); } @Override @@ -74,17 +103,36 @@ public class EagerCollectionFetch extends CollectionFetch implements FetchParent FetchParentAccess parentAccess, Consumer collector, AssemblerCreationState creationState) { - return new EagerCollectionAssembler( - getNavigablePath(), - getFetchedMapping(), - fkResult, - elementFetch, - indexFetch, - identifierResult, - parentAccess, + final DomainResultAssembler keyContainerAssembler = keyContainerResult.createResultAssembler( collector, creationState ); + + final DomainResultAssembler keyCollectionAssembler; + if ( keyCollectionResult == null ) { + keyCollectionAssembler = null; + } + else { + keyCollectionAssembler = keyCollectionResult.createResultAssembler( + collector, + creationState + ); + } + + final CollectionInitializer initializer = initializerProducer.produceInitializer( + getNavigablePath(), + getFetchedMapping(), + parentAccess, + null, + keyContainerAssembler, + keyCollectionAssembler, + collector, + creationState + ); + + collector.accept( initializer ); + + return new EagerCollectionAssembler( getFetchedMapping(), initializer ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EntityCollectionPartTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EntityCollectionPartTableGroup.java new file mode 100644 index 0000000000..1cb90d748e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/EntityCollectionPartTableGroup.java @@ -0,0 +1,120 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.hibernate.LockMode; +import org.hibernate.metamodel.mapping.ModelPartContainer; +import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.spi.SqlAstWalker; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.from.TableGroupJoin; +import org.hibernate.sql.ast.tree.from.TableReference; +import org.hibernate.sql.ast.tree.from.TableReferenceJoin; + +/** + * @author Steve Ebersole + */ +public class EntityCollectionPartTableGroup implements TableGroup { + private final NavigablePath collectionPartPath; + private final TableGroup collectionTableGroup; + private final EntityCollectionPart collectionPart; + + public EntityCollectionPartTableGroup( + NavigablePath collectionPartPath, + TableGroup collectionTableGroup, + EntityCollectionPart collectionPart) { + this.collectionPartPath = collectionPartPath; + this.collectionTableGroup = collectionTableGroup; + this.collectionPart = collectionPart; + } + + @Override + public NavigablePath getNavigablePath() { + return collectionPartPath; + } + + @Override + public String getGroupAlias() { + return null; + } + + @Override + public EntityCollectionPart getModelPart() { + return collectionPart; + } + + @Override + public LockMode getLockMode() { + return collectionTableGroup.getLockMode(); + } + + @Override + public Set getTableGroupJoins() { + return collectionTableGroup.getTableGroupJoins(); + } + + @Override + public boolean hasTableGroupJoins() { + return collectionTableGroup.hasTableGroupJoins(); + } + + @Override + public void setTableGroupJoins(Set joins) { + collectionTableGroup.setTableGroupJoins( joins ); + } + + @Override + public void addTableGroupJoin(TableGroupJoin join) { + collectionTableGroup.addTableGroupJoin( join ); + } + + @Override + public void visitTableGroupJoins(Consumer consumer) { + collectionTableGroup.visitTableGroupJoins( consumer ); + } + + @Override + public void applyAffectedTableNames(Consumer nameCollector) { + collectionTableGroup.applyAffectedTableNames( nameCollector ); + } + + @Override + public TableReference getPrimaryTableReference() { + return collectionTableGroup.getPrimaryTableReference(); + } + + @Override + public List getTableReferenceJoins() { + return collectionTableGroup.getTableReferenceJoins(); + } + + @Override + public boolean isInnerJoinPossible() { + return collectionTableGroup.isInnerJoinPossible(); + } + + @Override + public TableReference resolveTableReference(String tableExpression, Supplier creator) { + return collectionTableGroup.resolveTableReference( tableExpression, creator ); + } + + @Override + public TableReference resolveTableReference(String tableExpression) { + return collectionTableGroup.resolveTableReference( tableExpression ); + } + + @Override + public void accept(SqlAstWalker sqlTreeWalker) { + // do nothing + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ListInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ListInitializer.java new file mode 100644 index 0000000000..45f018f275 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ListInitializer.java @@ -0,0 +1,65 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.LockMode; +import org.hibernate.collection.internal.PersistentList; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.RowProcessingState; + +/** + * CollectionInitializer for PersistentList loading + * + * @author Steve Ebersole + */ +public class ListInitializer extends AbstractImmediateCollectionInitializer { + private final DomainResultAssembler listIndexAssembler; + private final DomainResultAssembler elementAssembler; + + private final int listIndexBase; + + public ListInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + boolean selected, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + DomainResultAssembler listIndexAssembler, + DomainResultAssembler elementAssembler) { + super( navigablePath, attributeMapping, parentAccess, selected, lockMode, keyContainerAssembler, keyCollectionAssembler ); + this.listIndexAssembler = listIndexAssembler; + this.elementAssembler = elementAssembler; + + listIndexBase = attributeMapping.getIndexMetadata().getListIndexBase(); + } + + @Override + public PersistentList getCollectionInstance() { + return (PersistentList) super.getCollectionInstance(); + } + + @Override + protected void readCollectionRow(RowProcessingState rowProcessingState) { + int index = (int) listIndexAssembler.assemble( rowProcessingState ); + getCollectionAttributeMapping().getIndexMetadata().getIndexDescriptor(); + if ( listIndexBase != 0 ) { + index -= listIndexBase; + } + getCollectionInstance().load( index, elementAssembler.assemble( rowProcessingState ) ); + } + + @Override + public String toString() { + return "ListInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ListInitializerProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ListInitializerProducer.java new file mode 100644 index 0000000000..2963fb2593 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/ListInitializerProducer.java @@ -0,0 +1,73 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import java.util.function.Consumer; + +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.AssemblerCreationState; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.Initializer; + +/** + * @author Steve Ebersole + */ +public class ListInitializerProducer implements CollectionInitializerProducer { + private final PluralAttributeMapping attributeMapping; + private final boolean joined; + private final Fetch listIndexFetch; + private final Fetch elementFetch; + + public ListInitializerProducer( + PluralAttributeMapping attributeMapping, + boolean joined, + Fetch listIndexFetch, + Fetch elementFetch) { + this.attributeMapping = attributeMapping; + this.joined = joined; + this.listIndexFetch = listIndexFetch; + this.elementFetch = elementFetch; + } + + @Override + public CollectionInitializer produceInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + Consumer initializerConsumer, + AssemblerCreationState creationState) { + return new ListInitializer( + navigablePath, + attributeMapping, + parentAccess, + joined, + lockMode, + keyContainerAssembler, + keyCollectionAssembler, + listIndexFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ), + elementFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/MapInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/MapInitializer.java new file mode 100644 index 0000000000..76c587ee81 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/MapInitializer.java @@ -0,0 +1,63 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.LockMode; +import org.hibernate.collection.internal.PersistentMap; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.RowProcessingState; + +/** + * Represents an immediate initialization of some sort (join, select, batch, sub-select) + * of a persistent Map valued attribute. + * + * @see DelayedCollectionInitializer + * + * @author Steve Ebersole + */ +public class MapInitializer extends AbstractImmediateCollectionInitializer { + private final DomainResultAssembler mapKeyAssembler; + private final DomainResultAssembler mapValueAssembler; + + public MapInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + boolean selected, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + DomainResultAssembler mapKeyAssembler, + DomainResultAssembler mapValueAssembler) { + super( navigablePath, attributeMapping, parentAccess, selected, lockMode, keyContainerAssembler, keyCollectionAssembler ); + this.mapKeyAssembler = mapKeyAssembler; + this.mapValueAssembler = mapValueAssembler; + } + + @Override + public PersistentMap getCollectionInstance() { + return (PersistentMap) super.getCollectionInstance(); + } + + @Override + @SuppressWarnings("unchecked") + protected void readCollectionRow(RowProcessingState rowProcessingState) { + getCollectionInstance().load( + mapKeyAssembler.assemble( rowProcessingState ), + mapValueAssembler.assemble( rowProcessingState ) + ); + } + + @Override + public String toString() { + return "MapInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/MapInitializerProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/MapInitializerProducer.java new file mode 100644 index 0000000000..265fba04e5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/MapInitializerProducer.java @@ -0,0 +1,76 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import java.util.function.Consumer; + +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.AssemblerCreationState; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.Initializer; + +/** + * @author Steve Ebersole + */ +public class MapInitializerProducer implements CollectionInitializerProducer { + private final PluralAttributeMapping mapDescriptor; + private final boolean isJoined; + private final Fetch mapKeyFetch; + private final Fetch mapValueFetch; + + public MapInitializerProducer( + PluralAttributeMapping mapDescriptor, + boolean isJoined, + Fetch mapKeyFetch, + Fetch mapValueFetch) { + this.mapDescriptor = mapDescriptor; + this.isJoined = isJoined; + this.mapKeyFetch = mapKeyFetch; + this.mapValueFetch = mapValueFetch; + } + + @Override + public CollectionInitializer produceInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + Consumer initializerConsumer, + AssemblerCreationState creationState) { + final DomainResultAssembler mapKeyAssembler = mapKeyFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ); + + final DomainResultAssembler mapValueAssembler = mapValueFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ); + + return new MapInitializer( + navigablePath, + mapDescriptor, + parentAccess, + isJoined, + lockMode, + keyContainerAssembler, + keyCollectionAssembler, + mapKeyAssembler, + mapValueAssembler + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/PluralAttributeAssemblerImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/PluralAttributeAssemblerImpl.java new file mode 100644 index 0000000000..0724e02a2a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/PluralAttributeAssemblerImpl.java @@ -0,0 +1,41 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.JdbcValuesSourceProcessingOptions; +import org.hibernate.sql.results.spi.RowProcessingState; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * @author Steve Ebersole + */ +public class PluralAttributeAssemblerImpl implements DomainResultAssembler { + private final CollectionInitializer initializer; + + public PluralAttributeAssemblerImpl(CollectionInitializer initializer) { + this.initializer = initializer; + } + + @Override + public Object assemble( + RowProcessingState rowProcessingState, + JdbcValuesSourceProcessingOptions options) { + PersistentCollection collectionInstance = initializer.getCollectionInstance(); + if ( collectionInstance == null ) { + return null; + } + return collectionInstance.getValue(); + } + + @Override + public JavaTypeDescriptor getAssembledJavaTypeDescriptor() { + return initializer.getInitializedPart().getJavaTypeDescriptor(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/SetInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/SetInitializer.java new file mode 100644 index 0000000000..f8712cac1b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/SetInitializer.java @@ -0,0 +1,51 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import org.hibernate.LockMode; +import org.hibernate.collection.internal.PersistentSet; +import org.hibernate.internal.log.LoggingHelper; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.RowProcessingState; + +/** + * @author Steve Ebersole + */ +public class SetInitializer extends AbstractImmediateCollectionInitializer { + private final DomainResultAssembler elementAssembler; + + public SetInitializer( + NavigablePath navigablePath, + PluralAttributeMapping setDescriptor, + FetchParentAccess parentAccess, + boolean selected, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + DomainResultAssembler elementAssembler) { + super( navigablePath, setDescriptor, parentAccess, selected, lockMode, keyContainerAssembler, keyCollectionAssembler ); + this.elementAssembler = elementAssembler; + } + + @Override + public PersistentSet getCollectionInstance() { + return (PersistentSet) super.getCollectionInstance(); + } + + @Override + protected void readCollectionRow(RowProcessingState rowProcessingState) { + getCollectionInstance().load( elementAssembler.assemble( rowProcessingState ) ); + } + + @Override + public String toString() { + return "SetInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/SetInitializerProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/SetInitializerProducer.java new file mode 100644 index 0000000000..60604add0a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/collection/SetInitializerProducer.java @@ -0,0 +1,67 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.internal.domain.collection; + +import java.util.function.Consumer; + +import org.hibernate.LockMode; +import org.hibernate.collection.spi.CollectionInitializerProducer; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.spi.AssemblerCreationState; +import org.hibernate.sql.results.spi.CollectionInitializer; +import org.hibernate.sql.results.spi.DomainResult; +import org.hibernate.sql.results.spi.DomainResultAssembler; +import org.hibernate.sql.results.spi.Fetch; +import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.Initializer; + +/** + * @author Steve Ebersole + */ +public class SetInitializerProducer implements CollectionInitializerProducer { + private final PluralAttributeMapping setDescriptor; + private final boolean isSelected; + private final Fetch elementFetch; + + public SetInitializerProducer( + PluralAttributeMapping setDescriptor, + boolean isSelected, + Fetch elementFetch) { + this.setDescriptor = setDescriptor; + this.elementFetch = elementFetch; + this.isSelected = isSelected; + } + + @Override + public CollectionInitializer produceInitializer( + NavigablePath navigablePath, + PluralAttributeMapping attributeMapping, + FetchParentAccess parentAccess, + LockMode lockMode, + DomainResultAssembler keyContainerAssembler, + DomainResultAssembler keyCollectionAssembler, + Consumer initializerConsumer, + AssemblerCreationState creationState) { + final DomainResultAssembler elementAssembler = elementFetch.createAssembler( + parentAccess, + initializerConsumer, + creationState + ); + + return new SetInitializer( + navigablePath, + setDescriptor, + parentAccess, + isSelected, + lockMode, + keyContainerAssembler, + keyCollectionAssembler, + elementAssembler + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/AbstractCompositeInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/AbstractCompositeInitializer.java index 3ba83194d4..5947fc4446 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/AbstractCompositeInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/AbstractCompositeInitializer.java @@ -18,7 +18,7 @@ import org.hibernate.sql.results.internal.NullValueAssembler; import org.hibernate.sql.results.internal.domain.AbstractFetchParentAccess; import org.hibernate.sql.results.spi.AssemblerCreationState; import org.hibernate.sql.results.spi.CompositeInitializer; -import org.hibernate.sql.results.spi.CompositeResultMappingNode; +import org.hibernate.sql.results.spi.CompositeResultNode; import org.hibernate.sql.results.spi.DomainResultAssembler; import org.hibernate.sql.results.spi.Fetch; import org.hibernate.sql.results.spi.FetchParentAccess; @@ -42,7 +42,7 @@ public abstract class AbstractCompositeInitializer extends AbstractFetchParentAc @SuppressWarnings("WeakerAccess") public AbstractCompositeInitializer( - CompositeResultMappingNode resultDescriptor, + CompositeResultNode resultDescriptor, FetchParentAccess fetchParentAccess, Consumer initializerConsumer, AssemblerCreationState creationState) { @@ -69,7 +69,7 @@ public abstract class AbstractCompositeInitializer extends AbstractFetchParentAc } @Override - public EmbeddableValuedModelPart getInitializingModelPart() { + public EmbeddableValuedModelPart getInitializedPart() { return embeddedModelPartDescriptor; } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeAssembler.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeAssembler.java index e76aa09b1d..0bb0f713fd 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeAssembler.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeAssembler.java @@ -25,7 +25,7 @@ public class CompositeAssembler implements DomainResultAssembler { @Override public JavaTypeDescriptor getAssembledJavaTypeDescriptor() { - return initializer.getInitializingModelPart().getJavaTypeDescriptor(); + return initializer.getInitializedPart().getJavaTypeDescriptor(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeFetch.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeFetch.java index 2a2764dfa4..763c0b716f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeFetch.java @@ -17,7 +17,7 @@ import org.hibernate.sql.ast.JoinType; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.results.internal.domain.AbstractFetchParent; import org.hibernate.sql.results.spi.AssemblerCreationState; -import org.hibernate.sql.results.spi.CompositeResultMappingNode; +import org.hibernate.sql.results.spi.CompositeResultNode; import org.hibernate.sql.results.spi.DomainResultAssembler; import org.hibernate.sql.results.spi.DomainResultCreationState; import org.hibernate.sql.results.spi.Fetch; @@ -29,7 +29,7 @@ import org.hibernate.sql.results.spi.Initializer; /** * @author Steve Ebersole */ -public class CompositeFetch extends AbstractFetchParent implements CompositeResultMappingNode, Fetch { +public class CompositeFetch extends AbstractFetchParent implements CompositeResultNode, Fetch { private final FetchParent fetchParent; private final FetchTiming fetchTiming; private final boolean nullable; @@ -41,7 +41,7 @@ public class CompositeFetch extends AbstractFetchParent implements CompositeResu FetchTiming fetchTiming, boolean nullable, DomainResultCreationState creationState) { - super( embeddedPartDescriptor, navigablePath ); + super( embeddedPartDescriptor.getEmbeddableTypeDescriptor(), navigablePath ); this.fetchParent = fetchParent; this.fetchTiming = fetchTiming; @@ -50,7 +50,7 @@ public class CompositeFetch extends AbstractFetchParent implements CompositeResu creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup( getNavigablePath(), np -> { - final TableGroupJoin tableGroupJoin = getFetchContainer().createTableGroupJoin( + final TableGroupJoin tableGroupJoin = getReferencedMappingContainer().createTableGroupJoin( getNavigablePath(), creationState.getSqlAstCreationState() .getFromClauseAccess() @@ -77,23 +77,23 @@ public class CompositeFetch extends AbstractFetchParent implements CompositeResu } @Override - public EmbeddableValuedModelPart getFetchContainer() { - return (EmbeddableValuedModelPart) super.getFetchContainer(); + public EmbeddableMappingType getFetchContainer() { + return (EmbeddableMappingType) super.getFetchContainer(); } @Override public EmbeddableValuedModelPart getReferencedMappingContainer() { - return getFetchContainer(); + return getFetchContainer().getEmbeddedValueMapping(); } @Override public Fetchable getFetchedMapping() { - return getFetchContainer(); + return getReferencedMappingContainer(); } @Override public EmbeddableMappingType getReferencedMappingType() { - return getFetchContainer().getEmbeddableTypeDescriptor(); + return getFetchContainer(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeFetchInitializer.java index 90cf75b29e..ce5f2842f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeFetchInitializer.java @@ -10,7 +10,7 @@ import java.util.function.Consumer; import org.hibernate.sql.results.spi.AssemblerCreationState; import org.hibernate.sql.results.spi.CompositeInitializer; -import org.hibernate.sql.results.spi.CompositeResultMappingNode; +import org.hibernate.sql.results.spi.CompositeResultNode; import org.hibernate.sql.results.spi.FetchParentAccess; import org.hibernate.sql.results.spi.Initializer; @@ -22,7 +22,7 @@ public class CompositeFetchInitializer implements CompositeInitializer { public CompositeFetchInitializer( FetchParentAccess fetchParentAccess, - CompositeResultMappingNode resultDescriptor, + CompositeResultNode resultDescriptor, Consumer initializerConsumer, AssemblerCreationState creationState) { super( resultDescriptor, fetchParentAccess, initializerConsumer, creationState ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeResult.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeResult.java index d5ce6e0b72..d02262931c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeResult.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeResult.java @@ -17,7 +17,7 @@ import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.results.internal.domain.AbstractFetchParent; import org.hibernate.sql.results.spi.AssemblerCreationState; -import org.hibernate.sql.results.spi.CompositeResultMappingNode; +import org.hibernate.sql.results.spi.CompositeResultNode; import org.hibernate.sql.results.spi.DomainResult; import org.hibernate.sql.results.spi.DomainResultAssembler; import org.hibernate.sql.results.spi.DomainResultCreationState; @@ -27,7 +27,7 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * @author Steve Ebersole */ -public class CompositeResult extends AbstractFetchParent implements CompositeResultMappingNode, DomainResult { +public class CompositeResult extends AbstractFetchParent implements CompositeResultNode, DomainResult { private final String resultVariable; public CompositeResult( @@ -35,7 +35,7 @@ public class CompositeResult extends AbstractFetchParent implements Composite EmbeddableValuedModelPart modelPart, String resultVariable, DomainResultCreationState creationState) { - super( modelPart, navigablePath ); + super( modelPart.getEmbeddableTypeDescriptor(), navigablePath ); this.resultVariable = resultVariable; final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess(); @@ -68,8 +68,8 @@ public class CompositeResult extends AbstractFetchParent implements Composite } @Override - public EmbeddableValuedModelPart getFetchContainer() { - return (EmbeddableValuedModelPart) super.getFetchContainer(); + public EmbeddableMappingType getFetchContainer() { + return (EmbeddableMappingType) super.getFetchContainer(); } @Override @@ -79,12 +79,12 @@ public class CompositeResult extends AbstractFetchParent implements Composite @Override public EmbeddableMappingType getReferencedMappingType() { - return getFetchContainer().getEmbeddableTypeDescriptor(); + return getFetchContainer(); } @Override public EmbeddableValuedModelPart getReferencedMappingContainer() { - return getFetchContainer(); + return getFetchContainer().getEmbeddedValueMapping(); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeRootInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeRootInitializer.java index 8d61eb731b..50f0dc9609 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeRootInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/composite/CompositeRootInitializer.java @@ -9,7 +9,7 @@ package org.hibernate.sql.results.internal.domain.composite; import java.util.function.Consumer; import org.hibernate.sql.results.spi.AssemblerCreationState; -import org.hibernate.sql.results.spi.CompositeResultMappingNode; +import org.hibernate.sql.results.spi.CompositeResultNode; import org.hibernate.sql.results.spi.Initializer; /** @@ -17,7 +17,7 @@ import org.hibernate.sql.results.spi.Initializer; */ public class CompositeRootInitializer extends AbstractCompositeInitializer { public CompositeRootInitializer( - CompositeResultMappingNode resultDescriptor, + CompositeResultNode resultDescriptor, Consumer initializerConsumer, AssemblerCreationState creationState) { super( resultDescriptor, null, initializerConsumer, creationState ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityFecth.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityFetch.java similarity index 88% rename from hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityFecth.java rename to hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityFetch.java index 7a37ad89ac..553793313a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityFecth.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityFetch.java @@ -9,7 +9,6 @@ package org.hibernate.sql.results.internal.domain.entity; import java.util.function.Consumer; import org.hibernate.LockMode; -import org.hibernate.metamodel.mapping.internal.SingularAssociationAttributeMapping; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.spi.AssemblerCreationState; import org.hibernate.sql.results.spi.DomainResultAssembler; @@ -23,16 +22,16 @@ import org.hibernate.sql.results.spi.Initializer; /** * @author Andrea Boriero */ -public abstract class AbstractEntityFecth implements Fetch { +public abstract class AbstractEntityFetch implements Fetch { private final FetchParent fetchParent; - private final SingularAssociationAttributeMapping fetchedAttribute; + private final Fetchable fetchedAttribute; private final NavigablePath navigablePath; private final boolean nullable; private final LockMode lockMode; - public AbstractEntityFecth( + public AbstractEntityFetch( FetchParent fetchParent, - SingularAssociationAttributeMapping fetchedAttribute, + Fetchable fetchedAttribute, NavigablePath navigablePath, boolean nullable, LockMode lockMode) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityInitializer.java index 43160a489b..a274b9deea 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityInitializer.java @@ -323,8 +323,6 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces session.getPersistenceContext().getBatchFetchQueue().addBatchLoadableEntityKey( entityKey ); } } - - // todo (6.0) : subselect fetches similar to batch fetch handling above } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityResultNode.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityResultNode.java index 240207bb18..e19b00ed67 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityResultNode.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/AbstractEntityResultNode.java @@ -48,7 +48,7 @@ public abstract class AbstractEntityResultNode extends AbstractFetchParent imple NavigablePath navigablePath, EntityMappingType targetType, DomainResultCreationState creationState) { - super( referencedModelPart, navigablePath ); + super( referencedModelPart.getEntityMappingType(), navigablePath ); this.referencedModelPart = referencedModelPart; this.lockMode = lockMode; this.targetType = targetType; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchImpl.java index e5e0a2fc6e..234395ca97 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/DelayedEntityFetchImpl.java @@ -24,7 +24,7 @@ import org.hibernate.sql.results.spi.Initializer; * @author Andrea Boriero * @author Steve Ebersole */ -public class DelayedEntityFetchImpl extends AbstractEntityFecth { +public class DelayedEntityFetchImpl extends AbstractEntityFetch { private final DomainResult result; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/EntityFetch.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/EntityFetch.java index 0fbbdb2ca4..f8d1b80b18 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/EntityFetch.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/entity/EntityFetch.java @@ -10,26 +10,26 @@ import java.util.function.Consumer; import org.hibernate.LockMode; import org.hibernate.metamodel.mapping.EntityValuedModelPart; -import org.hibernate.metamodel.mapping.internal.SingularAssociationAttributeMapping; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.spi.AssemblerCreationState; import org.hibernate.sql.results.spi.DomainResultCreationState; import org.hibernate.sql.results.spi.EntityInitializer; import org.hibernate.sql.results.spi.FetchParent; import org.hibernate.sql.results.spi.FetchParentAccess; +import org.hibernate.sql.results.spi.Fetchable; import org.hibernate.sql.results.spi.Initializer; /** * @author Andrea Boriero * @author Steve Ebersole */ -public class EntityFetch extends AbstractEntityFecth { +public class EntityFetch extends AbstractEntityFetch { private final EntityResultImpl entityResult; public EntityFetch( FetchParent fetchParent, - SingularAssociationAttributeMapping fetchedAttribute, + Fetchable fetchedAttribute, LockMode lockMode, boolean nullable, NavigablePath navigablePath, @@ -38,7 +38,7 @@ public class EntityFetch extends AbstractEntityFecth { entityResult = new EntityResultImpl( navigablePath, - (EntityValuedModelPart) fetchedAttribute.getMappedTypeDescriptor(), + (EntityValuedModelPart) fetchedAttribute, null, creationState ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CollectionInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CollectionInitializer.java index 213d8d714a..1eab0bd218 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CollectionInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CollectionInitializer.java @@ -7,6 +7,8 @@ package org.hibernate.sql.results.spi; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -16,7 +18,12 @@ import org.hibernate.sql.exec.spi.ExecutionContext; * @author Steve Ebersole */ public interface CollectionInitializer extends Initializer { - CollectionPersister getInitializingCollectionDescriptor(); + @Override + PluralAttributeMapping getInitializedPart(); + + default CollectionPersister getInitializingCollectionDescriptor() { + return getInitializedPart().getCollectionDescriptor(); + } PersistentCollection getCollectionInstance(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CollectionResultNode.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CollectionResultNode.java new file mode 100644 index 0000000000..961258af21 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CollectionResultNode.java @@ -0,0 +1,13 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.sql.results.spi; + +/** + * @author Steve Ebersole + */ +public interface CollectionResultNode extends ResultSetMappingNode { +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeInitializer.java index 690daacc02..f19378960b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeInitializer.java @@ -13,7 +13,8 @@ import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; * @author Steve Ebersole */ public interface CompositeInitializer extends Initializer, FetchParentAccess { - EmbeddableValuedModelPart getInitializingModelPart(); + @Override + EmbeddableValuedModelPart getInitializedPart(); Object getCompositeInstance(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeResultMappingNode.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeResultNode.java similarity index 89% rename from hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeResultMappingNode.java rename to hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeResultNode.java index bd5f20fba1..77585e3a73 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeResultMappingNode.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/CompositeResultNode.java @@ -14,7 +14,7 @@ import org.hibernate.query.NavigablePath; /** * @author Steve Ebersole */ -public interface CompositeResultMappingNode extends ResultSetMappingNode, FetchParent { +public interface CompositeResultNode extends ResultSetMappingNode, FetchParent { @Override default NavigablePath getNavigablePath() { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/EntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/EntityInitializer.java index 58926c8022..5fc0933bf3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/EntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/EntityInitializer.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.results.spi; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.persister.entity.EntityPersister; /** @@ -14,6 +15,10 @@ import org.hibernate.persister.entity.EntityPersister; * @author Steve Ebersole */ public interface EntityInitializer extends Initializer, FetchParentAccess { + @Override + default ModelPart getInitializedPart() { + return getEntityDescriptor(); + } /** * Get the descriptor for the type of entity being initialized diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/Initializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/Initializer.java index 21145c6168..a3099ad08b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/spi/Initializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/spi/Initializer.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.results.spi; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -17,10 +18,12 @@ import org.hibernate.sql.exec.spi.ExecutionContext; * @author Steve Ebersole */ public interface Initializer { - Object getInitializedInstance(); - NavigablePath getNavigablePath(); + ModelPart getInitializedPart(); + + Object getInitializedInstance(); + /** * Step 1 - Resolve the key value for this initializer for the current * row. diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/MappedFetchTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/MappedFetchTests.java index bfddfb5440..9b72b96f39 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/MappedFetchTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/MappedFetchTests.java @@ -29,6 +29,7 @@ import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.ast.tree.select.SelectStatement; import org.hibernate.sql.results.internal.domain.basic.BasicFetch; import org.hibernate.sql.results.internal.domain.collection.DelayedCollectionFetch; import org.hibernate.sql.results.internal.domain.collection.EagerCollectionFetch; @@ -39,13 +40,10 @@ import org.hibernate.sql.results.spi.Fetch; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; - import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.hamcrest.CoreMatchers; - import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -68,20 +66,23 @@ public class MappedFetchTests { final SessionFactoryImplementor sessionFactory = scope.getSessionFactory(); final DomainMetamodel domainModel = sessionFactory.getDomainModel(); final EntityPersister rootEntityDescriptor = domainModel.getEntityDescriptor( RootEntity.class ); - final MetamodelSelectBuilderProcess.SqlAstDescriptor sqlAstDescriptor = MetamodelSelectBuilderProcess.createSelect( - sessionFactory, + + final SelectStatement sqlAst = MetamodelSelectBuilderProcess.createSelect( rootEntityDescriptor, null, rootEntityDescriptor.getIdentifierMapping(), null, 1, LoadQueryInfluencers.NONE, - LockOptions.NONE + LockOptions.NONE, + jdbcParameter -> { + }, + sessionFactory ); - assertThat( sqlAstDescriptor.getSqlAst().getDomainResultDescriptors().size(), is( 1 ) ); + assertThat( sqlAst.getDomainResultDescriptors().size(), is( 1 ) ); - final DomainResult domainResult = sqlAstDescriptor.getSqlAst().getDomainResultDescriptors().get( 0 ); + final DomainResult domainResult = sqlAst.getDomainResultDescriptors().get( 0 ); assertThat( domainResult, instanceOf( EntityResult.class ) ); final EntityResult entityResult = (EntityResult) domainResult; @@ -105,7 +106,7 @@ public class MappedFetchTests { assertThat( simpleEntitiesFetch, instanceOf( DelayedCollectionFetch.class ) ); - final QuerySpec querySpec = sqlAstDescriptor.getSqlAst().getQuerySpec(); + final QuerySpec querySpec = sqlAst.getQuerySpec(); final TableGroup tableGroup = querySpec.getFromClause().getRoots().get( 0 ); assertThat( tableGroup.getModelPart(), is( rootEntityDescriptor ) ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/MapOperationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/MapOperationTests.java index 0f3b7b2e29..364925572a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/MapOperationTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/MapOperationTests.java @@ -6,21 +6,7 @@ */ package org.hibernate.orm.test.metamodel.mapping.collections; -import java.util.EnumSet; -import java.util.Map; - import org.hibernate.Hibernate; -import org.hibernate.service.spi.ServiceRegistryImplementor; -import org.hibernate.tool.schema.SourceType; -import org.hibernate.tool.schema.TargetType; -import org.hibernate.tool.schema.spi.CommandAcceptanceException; -import org.hibernate.tool.schema.spi.ExceptionHandler; -import org.hibernate.tool.schema.spi.ExecutionOptions; -import org.hibernate.tool.schema.spi.SchemaManagementTool; -import org.hibernate.tool.schema.spi.ScriptSourceInput; -import org.hibernate.tool.schema.spi.ScriptTargetOutput; -import org.hibernate.tool.schema.spi.SourceDescriptor; -import org.hibernate.tool.schema.spi.TargetDescriptor; import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.DomainModelScope; @@ -81,68 +67,7 @@ public class MapOperationTests { // uber hacky temp way: - final ServiceRegistryImplementor serviceRegistry = scope.getSessionFactory().getServiceRegistry(); - final SchemaManagementTool schemaTool = serviceRegistry.getService( SchemaManagementTool.class ); - - final ExecutionOptions executionOptions = new ExecutionOptions() { - @Override - public Map getConfigurationValues() { - return scope.getSessionFactory().getProperties(); - } - - @Override - public boolean shouldManageNamespaces() { - return false; - } - - @Override - public ExceptionHandler getExceptionHandler() { - return new ExceptionHandler() { - @Override - public void handleException(CommandAcceptanceException exception) { - throw exception; - } - }; - } - }; - - final SourceDescriptor sourceDescriptor = new SourceDescriptor() { - @Override - public SourceType getSourceType() { - return SourceType.METADATA; - } - - @Override - public ScriptSourceInput getScriptSourceInput() { - return null; - } - }; - - final TargetDescriptor targetDescriptor = new TargetDescriptor() { - @Override - public EnumSet getTargetTypes() { - return EnumSet.of( TargetType.DATABASE ); - } - - @Override - public ScriptTargetOutput getScriptTargetOutput() { - return null; - } - }; - - schemaTool.getSchemaDropper( scope.getSessionFactory().getProperties() ).doDrop( - domainModelScope.getDomainModel(), - executionOptions, - sourceDescriptor, - targetDescriptor - ); - - schemaTool.getSchemaCreator( scope.getSessionFactory().getProperties() ).doCreation( - domainModelScope.getDomainModel(), - executionOptions, - sourceDescriptor, - targetDescriptor - ); + TempDropDataHelper.cleanDatabaseSchema( scope, domainModelScope ); } @Test diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/SetOperationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/SetOperationTests.java new file mode 100644 index 0000000000..9ebb4997ad --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/SetOperationTests.java @@ -0,0 +1,139 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.metamodel.mapping.collections; + +import org.hibernate.Hibernate; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.FailureExpected; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * @author Steve Ebersole + */ +@SuppressWarnings("WeakerAccess") +@DomainModel( + annotatedClasses = { + SimpleEntity.class, + EntityContainingSets.class, + SomeStuff.class + } +) +@ServiceRegistry +@SessionFactory +public class SetOperationTests { + @BeforeEach + public void createData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final EntityContainingSets entity = new EntityContainingSets( 1, "first-map-entity" ); + entity.addBasic( "a value" ); + entity.addBasic( "another value" ); + + entity.addEnum( EnumValue.ONE ); + entity.addEnum( EnumValue.TWO ); + + entity.addConvertedBasic( EnumValue.ONE ); + entity.addConvertedBasic( EnumValue.THREE ); + + entity.addComponent( new SomeStuff( "the stuff - 1", "the stuff - 2" ) ); + entity.addComponent( new SomeStuff( "other stuff - 1", "other stuff - 2" ) ); + + session.save( entity ); + } + ); + } + + @AfterEach + public void dropData(SessionFactoryScope scope, DomainModelScope domainModelScope) { +// scope.inTransaction( +// session -> { +// final EntityContainingSets entity = session.load( EntityContainingSets.class, 1 ); +// session.delete( entity ); +// } +// ); + + TempDropDataHelper.cleanDatabaseSchema( scope, domainModelScope ); + } + + @Test + public void testLoad(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final EntityContainingSets entity = session.load( EntityContainingSets.class, 1 ); + assertThat( entity, is( notNullValue() ) ); + assertThat( Hibernate.isInitialized( entity ), is( false ) ); + } + ); + } + + @Test + public void testGet(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final EntityContainingSets entity = session.get( EntityContainingSets.class, 1 ); + assertThat( entity, is( notNullValue() ) ); + assertThat( Hibernate.isInitialized( entity ), is( true ) ); + assertThat( Hibernate.isInitialized( entity.getSetOfBasics() ), is( false ) ); + } + ); + } + + @Test + public void testSqmFetch(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final EntityContainingSets entity = session.createQuery( + "select e from EntityContainingSets e join fetch e.setOfBasics", + EntityContainingSets.class + ).getSingleResult(); + + assert Hibernate.isInitialized( entity.getSetOfBasics() ); + + assert entity.getSetOfBasics().size() == 2; + } + ); + } + + @Test + @FailureExpected( reason = "FlushVisitor collection handling" ) + public void testDeleteWithElementCollectionData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final EntityContainingSets entity = session.load( EntityContainingSets.class, 1 ); + session.delete( entity ); + } + ); + } + + @Test + @FailureExpected( reason = "not sure" ) + public void testTriggerFetch(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + final EntityContainingSets entity = session.get( EntityContainingSets.class, 1 ); + assert ! Hibernate.isInitialized( entity.getSetOfBasics() ); + + assertThat( entity.getSetOfBasics().size(), is( 2 ) ); + + assert Hibernate.isInitialized( entity.getSetOfBasics() ); + + assert ! Hibernate.isInitialized( entity.getSetOfEnums() ); + } + ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/TempDropDataHelper.java b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/TempDropDataHelper.java new file mode 100644 index 0000000000..02f3cf7992 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/metamodel/mapping/collections/TempDropDataHelper.java @@ -0,0 +1,95 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later + * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.orm.test.metamodel.mapping.collections; + +import java.util.EnumSet; +import java.util.Map; + +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.tool.schema.SourceType; +import org.hibernate.tool.schema.TargetType; +import org.hibernate.tool.schema.spi.CommandAcceptanceException; +import org.hibernate.tool.schema.spi.ExceptionHandler; +import org.hibernate.tool.schema.spi.ExecutionOptions; +import org.hibernate.tool.schema.spi.SchemaManagementTool; +import org.hibernate.tool.schema.spi.ScriptSourceInput; +import org.hibernate.tool.schema.spi.ScriptTargetOutput; +import org.hibernate.tool.schema.spi.SourceDescriptor; +import org.hibernate.tool.schema.spi.TargetDescriptor; + +import org.hibernate.testing.orm.junit.DomainModelScope; +import org.hibernate.testing.orm.junit.SessionFactoryScope; + +/** + * @author Steve Ebersole + */ +public class TempDropDataHelper { + static void cleanDatabaseSchema(SessionFactoryScope scope, DomainModelScope domainModelScope) { + final ServiceRegistryImplementor serviceRegistry = scope.getSessionFactory().getServiceRegistry(); + final SchemaManagementTool schemaTool = serviceRegistry.getService( SchemaManagementTool.class ); + + final ExecutionOptions executionOptions = new ExecutionOptions() { + @Override + public Map getConfigurationValues() { + return scope.getSessionFactory().getProperties(); + } + + @Override + public boolean shouldManageNamespaces() { + return false; + } + + @Override + public ExceptionHandler getExceptionHandler() { + return new ExceptionHandler() { + @Override + public void handleException(CommandAcceptanceException exception) { + throw exception; + } + }; + } + }; + + final SourceDescriptor sourceDescriptor = new SourceDescriptor() { + @Override + public SourceType getSourceType() { + return SourceType.METADATA; + } + + @Override + public ScriptSourceInput getScriptSourceInput() { + return null; + } + }; + + final TargetDescriptor targetDescriptor = new TargetDescriptor() { + @Override + public EnumSet getTargetTypes() { + return EnumSet.of( TargetType.DATABASE ); + } + + @Override + public ScriptTargetOutput getScriptTargetOutput() { + return null; + } + }; + + schemaTool.getSchemaDropper( scope.getSessionFactory().getProperties() ).doDrop( + domainModelScope.getDomainModel(), + executionOptions, + sourceDescriptor, + targetDescriptor + ); + + schemaTool.getSchemaCreator( scope.getSessionFactory().getProperties() ).doCreation( + domainModelScope.getDomainModel(), + executionOptions, + sourceDescriptor, + targetDescriptor + ); + } +} diff --git a/hibernate-core/src/test/resources/log4j.properties b/hibernate-core/src/test/resources/log4j.properties index 74500c4dba..48fedd2a71 100644 --- a/hibernate-core/src/test/resources/log4j.properties +++ b/hibernate-core/src/test/resources/log4j.properties @@ -24,6 +24,8 @@ log4j.logger.org.hibernate.orm.query.hql=debug log4j.logger.org.hibernate.tool.hbm2ddl=trace log4j.logger.org.hibernate.testing.cache=debug +log4j.logger.org.hibernate.orm.sql.results.loading.collection=trace + # SQL Logging - HHH-6833 log4j.logger.org.hibernate.SQL=debug