diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java index cecf891f57..1e2990fa19 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSelectBuilder.java @@ -8,6 +8,7 @@ package org.hibernate.loader.ast.internal; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -33,6 +34,7 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.PluralAttributeMapping; +import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.persister.collection.CollectionPersister; @@ -80,6 +82,8 @@ import static org.hibernate.sql.ast.spi.SqlExpressionResolver.createColumnRefere public class LoaderSelectBuilder { private static final Logger log = Logger.getLogger( LoaderSelectBuilder.class ); + private HashMap> visitedNavigablePath = new HashMap<>(); + /** * Create an SQL AST select-statement based on matching one-or-more keys * @@ -209,7 +213,7 @@ public class LoaderSelectBuilder { new SimpleFromClauseAccessImpl(), lockOptions, this::visitFetches, - numberOfKeysToLoad > 1, + numberOfKeysToLoad > 1 || restrictedPart instanceof ForeignKeyDescriptor, creationContext ); @@ -430,15 +434,22 @@ public class LoaderSelectBuilder { } final List fetches = new ArrayList<>(); + String fullPath = fetchParent.getNavigablePath().getFullPath(); + final List fullPathFetches = visitedNavigablePath.get( fullPath ); + if ( fullPathFetches != null ) { + return fullPathFetches; + } final BiConsumer processor = createFetchableBiConsumer( fetchParent, querySpec, creationState, fetches ); final FetchableContainer referencedMappingContainer = fetchParent.getReferencedMappingContainer(); if ( fetchParent.getNavigablePath().getParent() != null ) { - referencedMappingContainer.visitKeyFetchables( fetchable -> processor.accept( fetchable, true ), null ); + referencedMappingContainer.visitKeyFetchables( + fetchable -> processor.accept( fetchable, true ), null ); } - referencedMappingContainer.visitFetchables( fetchable -> processor.accept( fetchable, false ), null ); - + referencedMappingContainer.visitFetchables( + fetchable -> processor.accept( fetchable, false ), null ); + visitedNavigablePath.put( fullPath, fetches ); return fetches; } @@ -501,12 +512,14 @@ public class LoaderSelectBuilder { joined = false; } else if ( fetchDepth > maximumFetchDepth ) { - return; + if ( !( fetchable instanceof BasicValuedModelPart ) && !( fetchable instanceof EmbeddedAttributeMapping ) ) { + return; + } } } try { - if ( !( fetchable instanceof BasicValuedModelPart ) ) { + if ( !( fetchable instanceof BasicValuedModelPart ) && !( fetchable instanceof EmbeddedAttributeMapping ) ) { fetchDepth++; } final Fetch fetch = fetchable.generateFetch( @@ -537,7 +550,7 @@ public class LoaderSelectBuilder { } } finally { - if ( !( fetchable instanceof BasicValuedModelPart ) ) { + if ( !( fetchable instanceof BasicValuedModelPart ) && !( fetchable instanceof EmbeddedAttributeMapping ) ) { fetchDepth--; } if ( entityGraphTraversalState != null ) { diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java index abc9ee0ec0..32a421328f 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderSqlAstCreationState.java @@ -7,7 +7,9 @@ package org.hibernate.loader.ast.internal; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import javax.persistence.CacheRetrieveMode; import javax.persistence.CacheStoreMode; @@ -16,6 +18,7 @@ import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.graph.spi.AppliedGraph; +import org.hibernate.metamodel.mapping.AssociationKey; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.Limit; import org.hibernate.query.NavigablePath; @@ -48,13 +51,15 @@ public class LoaderSqlAstCreationState private final QuerySpec querySpec; private final SqlAliasBaseManager sqlAliasBaseManager; - private boolean forceIdentifierSelection; + private final boolean forceIdentifierSelection; private final SqlAstCreationContext sf; private final SqlAstQuerySpecProcessingStateImpl processingState; private final FromClauseAccess fromClauseAccess; private final LockOptions lockOptions; private final FetchProcessor fetchProcessor; + private Set visitedAssociationKeys = new HashSet<>(); + public LoaderSqlAstCreationState( QuerySpec querySpec, SqlAliasBaseManager sqlAliasBaseManager, @@ -143,6 +148,16 @@ public class LoaderSqlAstCreationState return this; } + @Override + public void registerVisitedAssociationKey(AssociationKey associationKey) { + visitedAssociationKeys.add( associationKey ); + } + + @Override + public boolean isAssociationKeyVisited(AssociationKey associationKey) { + return visitedAssociationKeys.contains( associationKey ); + } + @Override public ModelPart resolveModelPart(NavigablePath navigablePath) { // for now, let's assume that the navigable-path refers to TableGroup diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java index 004984b079..523b0435e3 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleUniqueKeyEntityLoaderStandard.java @@ -20,7 +20,9 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.ast.spi.SingleUniqueKeyEntityLoader; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.JdbcMapping; +import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.SingularAttributeMapping; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.query.spi.QueryOptions; import org.hibernate.query.spi.QueryOptionsAdapter; import org.hibernate.query.spi.QueryParameterBindings; @@ -40,13 +42,18 @@ import org.hibernate.sql.exec.spi.JdbcSelect; */ public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEntityLoader { private final EntityMappingType entityDescriptor; - private final SingularAttributeMapping uniqueKeyAttribute; + private final ModelPart uniqueKeyAttribute; public SingleUniqueKeyEntityLoaderStandard( EntityMappingType entityDescriptor, SingularAttributeMapping uniqueKeyAttribute) { this.entityDescriptor = entityDescriptor; - this.uniqueKeyAttribute = uniqueKeyAttribute; + if ( uniqueKeyAttribute instanceof ToOneAttributeMapping ) { + this.uniqueKeyAttribute = ( (ToOneAttributeMapping) uniqueKeyAttribute ).getForeignKeyDescriptor(); + } + else { + this.uniqueKeyAttribute = uniqueKeyAttribute; + } } @Override @@ -142,7 +149,11 @@ public class SingleUniqueKeyEntityLoaderStandard implements SingleUniqueKeyEn true ); - assert list.size() == 1; + int size = list.size(); + assert size <= 1; + if ( size == 0 ) { + return null; + } //noinspection unchecked return (T) list.get( 0 ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/AssociationKey.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/AssociationKey.java new file mode 100644 index 0000000000..d0c5c1485b --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/AssociationKey.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.metamodel.mapping; + +import java.util.List; + +/** + * @author Andrea Boriero + */ +public class AssociationKey { + private final String table; + private final List columns; + + public AssociationKey(String table, List columns) { + this.table = table; + this.columns = columns; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final AssociationKey that = (AssociationKey) o; + return table.equals( that.table ) && columns.equals( that.columns ); + } + + @Override + public int hashCode() { + return table.hashCode(); + } + + private String str; + + @Override + public String toString() { + if ( str == null ) { + str = "AssociationKey(table=" + table + ", columns={" + String.join( ",", columns ) + "})"; + } + return str; + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java index 55a7d58cb7..d741ad4842 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EmbeddableMappingType.java @@ -345,7 +345,18 @@ public class EmbeddableMappingType implements ManagedMappingType { Clause clause, TypeConfiguration typeConfiguration) { attributeMappings.forEach( - (s, attributeMapping) -> attributeMapping.visitJdbcTypes( action, clause, typeConfiguration ) + (s, attributeMapping) -> { + if ( attributeMapping instanceof ToOneAttributeMapping ) { + ( (ToOneAttributeMapping) attributeMapping ).getKeyTargetMatchPart().visitJdbcTypes( + action, + clause, + typeConfiguration + ); + } + else { + attributeMapping.visitJdbcTypes( action, clause, typeConfiguration ); + } + } ); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java index b94e413caa..c4191c06b3 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java @@ -6,14 +6,12 @@ */ package org.hibernate.metamodel.mapping; -import org.hibernate.sql.results.graph.Fetchable; - /** * Commonality between `many-to-one`, `one-to-one` and `any`, as well as entity-valued collection elements and map-keys * * @author Steve Ebersole */ -public interface EntityAssociationMapping extends ModelPart, Fetchable { +public interface EntityAssociationMapping extends ModelPart, Association { @Override default String getFetchableName() { return getPartName(); 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 2d93b15b94..696b447fc8 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 @@ -202,7 +202,6 @@ public interface EntityMappingType extends ManagedMappingType, Loadable { final Object value = assembler == null ? UNFETCHED_PROPERTY : assembler.assemble( rowProcessingState ); values[index++] = value; - } } ); 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 4da7699a3b..d627d5883e 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 @@ -51,10 +51,6 @@ public interface ForeignKeyDescriptor extends VirtualModelPart { return PART_NAME; } - String getReferringTableExpression(); - - String getTargetTableExpression(); - /** * Visits the FK "referring" columns */ @@ -67,6 +63,5 @@ public interface ForeignKeyDescriptor extends VirtualModelPart { void visitTargetColumns(ColumnConsumer consumer); - - boolean areTargetColumnNamesEqualsTo(String[] columnNames); + AssociationKey getAssociationKey(); } 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 2640a86abd..5e7242a3a6 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 @@ -55,4 +55,6 @@ public interface PluralAttributeMapping } String getSeparateCollectionTable(); + + String getMappedBy(); } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractSingularAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractSingularAttributeMapping.java index 462dbea864..c79e928b54 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractSingularAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractSingularAttributeMapping.java @@ -7,7 +7,6 @@ package org.hibernate.metamodel.mapping.internal; import org.hibernate.engine.FetchStrategy; -import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java index e7e6849d73..779b89eb13 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedAttributeMapping.java @@ -98,6 +98,7 @@ public class EmbeddedAttributeMapping public EmbeddableMappingType getEmbeddableTypeDescriptor() { return embeddableMappingType; } + @Override public SingularAttributeMapping getParentInjectionAttributeMapping() { // todo (6.0) : implement diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java index def43976b2..24a533f972 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EmbeddedForeignKeyDescriptor.java @@ -8,8 +8,10 @@ package org.hibernate.metamodel.mapping.internal; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import org.hibernate.HibernateException; +import org.hibernate.metamodel.mapping.AssociationKey; import org.hibernate.metamodel.mapping.ColumnConsumer; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -20,6 +22,7 @@ import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.query.ComparisonOperator; import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationState; @@ -49,6 +52,7 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor, Model private final List targetColumnExpressions; private final EmbeddableValuedModelPart mappingType; private final List jdbcMappings; + private AssociationKey associationKey; public EmbeddedForeignKeyDescriptor( EmbeddedIdentifierMappingImpl mappingType, @@ -305,16 +309,6 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor, Model throw new IllegalStateException( "Could not resolve binding for table `" + table + "`" ); } - @Override - public String getReferringTableExpression() { - return keyColumnContainingTable; - } - - @Override - public String getTargetTableExpression() { - return targetColumnContainingTable; - } - @Override public void visitReferringColumns(ColumnConsumer consumer) { for ( int i = 0; i < keyColumnExpressions.size(); i++ ) { @@ -330,17 +324,11 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor, Model } @Override - public boolean areTargetColumnNamesEqualsTo(String[] columnNames) { - int length = columnNames.length; - if ( length != targetColumnExpressions.size() ) { - return false; + public AssociationKey getAssociationKey() { + if ( associationKey == null ) { + associationKey = new AssociationKey( keyColumnContainingTable, keyColumnExpressions ); } - for ( int i = 0; i < length; i++ ) { - if ( !targetColumnExpressions.contains( columnNames[i] ) ) { - return false; - } - } - return true; + return associationKey; } @Override @@ -363,4 +351,9 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor, Model throw new UnsupportedOperationException(); } + @Override + public void visitJdbcTypes( + Consumer action, Clause clause, TypeConfiguration typeConfiguration) { + mappingType.visitJdbcTypes( action, clause, typeConfiguration ); + } } 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 38da2e76ce..34deacb1ed 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 @@ -22,6 +22,7 @@ import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; 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.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -38,7 +39,7 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; * @author Steve Ebersole */ public class EntityCollectionPart - implements CollectionPart, EntityAssociationMapping, EntityValuedFetchable, Association, FetchOptions { + implements CollectionPart, EntityAssociationMapping, EntityValuedFetchable, FetchOptions { private final NavigableRole navigableRole; private final CollectionPersister collectionDescriptor; private final Nature nature; @@ -137,18 +138,17 @@ public class EntityCollectionPart LockMode lockMode, String resultVariable, DomainResultCreationState creationState) { -// assert fetchParent.getReferencedMappingContainer() instanceof PluralAttributeMapping; - // find or create the TableGroup associated with this `fetchablePath` - creationState.getSqlAstCreationState().getFromClauseAccess().resolveTableGroup( + final FromClauseAccess fromClauseAccess = creationState.getSqlAstCreationState().getFromClauseAccess(); + creationState.registerVisitedAssociationKey( getForeignKeyDescriptor().getAssociationKey() ); + + fromClauseAccess.resolveTableGroup( fetchablePath, np -> { // We need to create one. The Result will be able to find it later by path // first, find the collection's TableGroup - final TableGroup collectionTableGroup = creationState.getSqlAstCreationState() - .getFromClauseAccess() - .getTableGroup( fetchParent.getNavigablePath() ); + final TableGroup collectionTableGroup = fromClauseAccess.getTableGroup( fetchParent.getNavigablePath() ); assert collectionTableGroup != null; 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 3ef9ec44ab..b7af77d66b 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 @@ -22,6 +22,7 @@ import org.hibernate.jpa.spi.JpaCompliance; import org.hibernate.mapping.Collection; import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.List; +import org.hibernate.mapping.OneToOne; import org.hibernate.mapping.Property; import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.BasicValuedModelPart; @@ -45,6 +46,7 @@ import org.hibernate.persister.entity.Joinable; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.SqlAstJoinType; +import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.SqlAliasBase; import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator; import org.hibernate.sql.ast.spi.SqlAliasStemHelper; @@ -94,6 +96,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme private final FetchStyle fetchStyle; private final CascadeStyle cascadeStyle; + private final String mappedBy; private final CollectionPersister collectionDescriptor; private final String separateCollectionTable; @@ -169,6 +172,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme this.fetchStyle = fetchStyle; this.cascadeStyle = cascadeStyle; this.collectionDescriptor = collectionDescriptor; + this.mappedBy = bootDescriptor.getMappedByProperty(); this.sqlAliasStem = SqlAliasStemHelper.INSTANCE.generateStemFromAttributeName( attributeName ); @@ -380,6 +384,11 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme return separateCollectionTable; } + @Override + public String getMappedBy() { + return mappedBy; + } + @Override public int getStateArrayPosition() { return stateArrayPosition; @@ -442,12 +451,15 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme DomainResultCreationState creationState) { final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); + creationState.registerVisitedAssociationKey( fkDescriptor.getAssociationKey() ); + if ( fetchTiming == FetchTiming.IMMEDIATE) { if ( selected ) { - final TableGroup collectionTableGroup = sqlAstCreationState.getFromClauseAccess().resolveTableGroup( + final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess(); + final TableGroup collectionTableGroup = fromClauseAccess.resolveTableGroup( fetchablePath, p -> { - final TableGroup lhsTableGroup = sqlAstCreationState.getFromClauseAccess().getTableGroup( + final TableGroup lhsTableGroup = fromClauseAccess.getTableGroup( fetchParent.getNavigablePath() ); final TableGroupJoin tableGroupJoin = createTableGroupJoin( fetchablePath, 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 126f61fa7a..e299c88ac2 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 @@ -6,6 +6,7 @@ */ package org.hibernate.metamodel.mapping.internal; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -14,6 +15,7 @@ import org.hibernate.LockMode; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.AssociationKey; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.metamodel.mapping.ColumnConsumer; import org.hibernate.metamodel.mapping.EntityMappingType; @@ -53,6 +55,7 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa private final String targetColumnContainingTable; private final String targetColumnExpression; private final JdbcMapping jdbcMapping; + private AssociationKey associationKey; public SimpleForeignKeyDescriptor( String keyColumnContainingTable, @@ -285,32 +288,23 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa throw new UnsupportedOperationException(); } - @Override - public String getReferringTableExpression() { - return keyColumnContainingTable; - } - @Override public void visitReferringColumns(ColumnConsumer consumer) { consumer.accept( keyColumnContainingTable, keyColumnExpression, jdbcMapping ); } - @Override - public String getTargetTableExpression() { - return targetColumnContainingTable; - } - @Override public void visitTargetColumns(ColumnConsumer consumer) { consumer.accept( targetColumnContainingTable, targetColumnExpression, jdbcMapping ); } @Override - public boolean areTargetColumnNamesEqualsTo(String[] columnNames) { - if ( columnNames.length != 1 ) { - return false; + public AssociationKey getAssociationKey() { + if ( associationKey == null ) { + final List associationKeyColumns = Collections.singletonList( keyColumnExpression ); + associationKey = new AssociationKey( keyColumnContainingTable, associationKeyColumns ); } - return targetColumnExpression.equals( columnNames[0] ); + return associationKey; } @Override @@ -393,11 +387,9 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa return jdbcMapping; } - public String getTargetColumnContainingTable() { - return targetColumnContainingTable; - } - - public String getTargetColumnExpression() { - return targetColumnExpression; + @Override + public String toString() { + return "SimpleForeignKeyDescriptor : " + keyColumnContainingTable + "." + keyColumnExpression + + " --> " + targetColumnContainingTable + "." + targetColumnExpression; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index 6c12fde195..9d2d7441c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -6,23 +6,24 @@ */ package org.hibernate.metamodel.mapping.internal; -import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.engine.FetchStrategy; import org.hibernate.engine.FetchTiming; +import org.hibernate.internal.util.StringHelper; import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.OneToOne; import org.hibernate.mapping.ToOne; -import org.hibernate.metamodel.mapping.Association; +import org.hibernate.metamodel.mapping.AssociationKey; import org.hibernate.metamodel.mapping.EntityAssociationMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.persister.entity.SingleTableEntityPersister; import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.query.NavigablePath; import org.hibernate.sql.ast.SqlAstJoinType; @@ -42,21 +43,21 @@ import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.FetchParent; -import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; import org.hibernate.sql.results.graph.entity.EntityFetch; -import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; -import org.hibernate.sql.results.graph.entity.internal.EntityFetchDelayedImpl; +import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl; import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; -import org.hibernate.sql.results.internal.domain.BiDirectionalFetchImpl; +import org.hibernate.sql.results.internal.domain.CircularFetchImpl; +import org.hibernate.sql.results.internal.domain.CircularBiDirectionalFetchImpl; import org.hibernate.type.ForeignKeyDirection; /** * @author Steve Ebersole */ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping - implements EntityValuedFetchable, EntityAssociationMapping, Association, TableGroupJoinProducer { + implements EntityValuedFetchable, EntityAssociationMapping, TableGroupJoinProducer { public enum Cardinality { ONE_TO_ONE, @@ -75,6 +76,7 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping private final boolean referringPrimaryKey; private final Cardinality cardinality; + private String mappedBy; private ForeignKeyDescriptor foreignKeyDescriptor; private ForeignKeyDirection foreignKeyDirection; @@ -124,6 +126,13 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping else { assert bootValue instanceof OneToOne; cardinality = Cardinality.ONE_TO_ONE; + String mappedByProperty = ( (OneToOne) bootValue ).getMappedByProperty(); + if ( mappedByProperty == null ) { + mappedBy = StringHelper.nullIfEmpty( referencedPropertyName ); + } + else { + mappedBy = mappedByProperty; + } } this.navigableRole = navigableRole; @@ -169,101 +178,142 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping NavigablePath fetchablePath, FetchParent fetchParent, DomainResultCreationState creationState) { - // NOTE - a circular fetch reference ultimately needs 2 pieces of information: - // 1) The NavigablePath that is circular (`fetchablePath`) - // 2) The NavigablePath to the entity-valued-reference that is the "other side" of the circularity + final AssociationKey associationKey = foreignKeyDescriptor.getAssociationKey(); - final ModelPart parentModelPart = fetchParent.getReferencedModePart(); - - if ( ! Fetchable.class.isInstance( parentModelPart ) ) { - // the `fetchParent` would have to be a Fetch as well for this to be circular... - return null; - } - - final FetchParent associationFetchParent = fetchParent.resolveContainingAssociationParent(); - if ( associationFetchParent == null ) { - return null; - } - final ModelPart referencedModePart = associationFetchParent.getReferencedModePart(); - assert referencedModePart instanceof Association; - - final Association associationParent = (Association) referencedModePart; - - if ( foreignKeyDescriptor.equals( associationParent.getForeignKeyDescriptor() ) ) { - // we need to determine the NavigablePath referring to the entity that the bi-dir - // fetch will "return" for its Assembler. so we walk "up" the FetchParent graph - // to find the "referenced entity" reference - - return createBiDirectionalFetch( fetchablePath, fetchParent ); - } - - // this is the case of a JoinTable - // PARENT(id) - // PARENT_CHILD(parent_id, child_id) - // CHILD(id) - // the FKDescriptor for the association `Parent.child` will be - // PARENT_CHILD.child.id -> CHILD.id - // and the FKDescriptor for the association `Child.parent` will be - // PARENT_CHILD.parent.id -> PARENT.id - // in such a case the associationParent.getIdentifyingColumnExpressions() is PARENT_CHILD.parent_id - // while the getIdentifyingColumnExpressions for this association is PARENT_CHILD.child_id - // so we will check if the parentAssociation ForeignKey Target match with the association entity identifier table and columns - final ForeignKeyDescriptor associationParentForeignKeyDescriptor = associationParent.getForeignKeyDescriptor(); - if ( referencedModePart instanceof ToOneAttributeMapping - && ( (ToOneAttributeMapping) referencedModePart ).getDeclaringType() == getPartMappingType() ) { - if ( this.foreignKeyDescriptor.getReferringTableExpression() - .equals( associationParentForeignKeyDescriptor.getReferringTableExpression() ) ) { - final SingleTableEntityPersister entityPersister = (SingleTableEntityPersister) getDeclaringType(); - if ( associationParentForeignKeyDescriptor.getTargetTableExpression() - .equals( entityPersister.getTableName() ) ) { - final String[] identifierColumnNames = entityPersister.getIdentifierColumnNames(); - if ( associationParentForeignKeyDescriptor.areTargetColumnNamesEqualsTo( identifierColumnNames ) ) { - return createBiDirectionalFetch( fetchablePath, fetchParent ); - } - return null; + if ( creationState.isAssociationKeyVisited( associationKey ) ) { + NavigablePath parent = fetchablePath.getParent(); + ModelPart modelPart = creationState.resolveModelPart( parent ); + if ( modelPart instanceof EmbeddedIdentifierMappingImpl ) { + while ( parent.getFullPath().endsWith( EntityIdentifierMapping.ROLE_LOCAL_NAME ) ) { + parent = parent.getParent(); } + } + while ( modelPart instanceof EmbeddableValuedFetchable ) { + parent = parent.getParent(); + modelPart = creationState.resolveModelPart( parent ); + } + if ( this.mappedBy != null && parent.getFullPath().endsWith( this.mappedBy ) ) { + /* + class Child { + @OneToOne(mappedBy = "biologicalChild") + private Mother mother; + } + + class Mother { + @OneToOne + private Child biologicalChild; + } + + fetchablePath= Mother.biologicalChild.mother + this.mappedBy = "biologicalChild" + parent.getFullPath() = "Mother.biologicalChild" + */ + return createCircularBiDirectionalFetch( + fetchablePath, + fetchParent, + parent.getParent(), + LockMode.READ + ); + } + + /* + check if mappedBy is on the other side of the association + */ + final String otherSideMappedBy = getOtherSideMappedBy( modelPart, parent.getParent(), creationState ); + if ( otherSideMappedBy != null ) { + /* + class Child { + @OneToOne(mappedBy = "biologicalChild") + private Mother mother; + } + + class Mother { + @OneToOne + private Child biologicalChild; + } + + fetchablePath = "Child.mother.biologicalChild" + otherSideAssociationModelPart = ToOneAttributeMapping("Child.mother") + otherSideMappedBy = "biologicalChild" + + */ + + if ( fetchablePath.getFullPath().endsWith( otherSideMappedBy ) ) { + return createCircularBiDirectionalFetch( + fetchablePath, + fetchParent, + parent.getParent(), + LockMode.READ + ); + } + } + /* + class Child { + @OneToOne + private Mother mother; + } + + class Mother { + @OneToOne + private Child stepMother; + } + + We have a cirularity but it is not bidirectional + */ + if ( referringPrimaryKey ) { + final TableGroup parentTableGroup = creationState + .getSqlAstCreationState() + .getFromClauseAccess() + .getTableGroup( fetchParent.getNavigablePath() ); + return new CircularFetchImpl( + getEntityMappingType(), + getTiming(), + fetchablePath, + fetchParent, + this, + fetchablePath, + foreignKeyDescriptor.createDomainResult( fetchablePath, parentTableGroup, creationState ) + ); } } return null; } - private Fetch createBiDirectionalFetch(NavigablePath fetchablePath, FetchParent fetchParent) { - final EntityResultGraphNode referencedEntityReference = resolveEntityGraphNode( fetchParent ); - - if ( referencedEntityReference == null ) { - throw new HibernateException( - "Could not locate entity-valued reference for circular path `" + fetchablePath + "`" - ); + private String getOtherSideMappedBy( + ModelPart modelPart, + NavigablePath parentOfParent, + DomainResultCreationState creationState) { + if ( modelPart instanceof ToOneAttributeMapping ) { + return ( (ToOneAttributeMapping) modelPart ).getMappedBy(); } - return new BiDirectionalFetchImpl( + if ( modelPart instanceof EntityCollectionPart ) { + return ( (PluralAttributeMapping) creationState.resolveModelPart( parentOfParent ) ).getMappedBy(); + } + + return null; + } + + public String getMappedBy(){ + return mappedBy; + } + + private Fetch createCircularBiDirectionalFetch( + NavigablePath fetchablePath, + FetchParent fetchParent, + NavigablePath referencedNavigablePath, + LockMode lockMode) { + return new CircularBiDirectionalFetchImpl( FetchTiming.IMMEDIATE, fetchablePath, fetchParent, this, - referencedEntityReference.getNavigablePath() + lockMode, + referencedNavigablePath ); } - protected EntityResultGraphNode resolveEntityGraphNode(FetchParent fetchParent) { - FetchParent processingParent = fetchParent; - while ( processingParent != null ) { - if ( processingParent instanceof EntityResultGraphNode ) { - return (EntityResultGraphNode) processingParent; - } - - if ( processingParent instanceof Fetch ) { - processingParent = ( (Fetch) processingParent ).getFetchParent(); - continue; - } - - processingParent = null; - } - - return null; - } - @Override public EntityFetch generateFetch( FetchParent fetchParent, @@ -273,6 +323,7 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping LockMode lockMode, String resultVariable, DomainResultCreationState creationState) { + final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess(); @@ -292,7 +343,6 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping sqlAstJoinType = SqlAstJoinType.INNER; } - final TableGroupJoin tableGroupJoin = createTableGroupJoin( fetchablePath, parentTableGroup, @@ -306,6 +356,7 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping } ); + creationState.registerVisitedAssociationKey( foreignKeyDescriptor.getAssociationKey() ); return new EntityFetchJoinedImpl( fetchParent, this, @@ -319,12 +370,41 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping //noinspection rawtypes final DomainResult keyResult; + /* + 1. No JoinTable + Model: + EntityA{ + @ManyToOne + EntityB b + } + + EntityB{ + @ManyToOne + EntityA a + } + + Relational: + ENTITY_A( id ) + ENTITY_B( id, entity_a_id) + + 1.1 EntityA -> EntityB : as keyResult we need ENTITY_B.id + 1.2 EntityB -> EntityA : as keyResult we need ENTITY_B.entity_a_id (FK referring column) + + 2. JoinTable + + */ + + boolean selectByUniqueKey; if ( referringPrimaryKey ) { + // case 1.2 keyResult = foreignKeyDescriptor.createDomainResult( fetchablePath, parentTableGroup, creationState ); + selectByUniqueKey = false; } else { - keyResult = ( (EntityPersister) getDeclaringType() ).getIdentifierMapping() + keyResult = ((EntityPersister) getDeclaringType()).getIdentifierMapping() .createDomainResult( fetchablePath, parentTableGroup, null, creationState ); + // case 1.1 + selectByUniqueKey = true; } assert !selected; @@ -332,19 +412,17 @@ public class ToOneAttributeMapping extends AbstractSingularAttributeMapping return new EntityFetchSelectImpl( fetchParent, this, - lockMode, isNullable, fetchablePath, keyResult, + selectByUniqueKey, creationState ); } - return new EntityFetchDelayedImpl( + return new EntityDelayedFetchImpl( fetchParent, this, - lockMode, - isNullable, fetchablePath, keyResult ); 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 6d4193e2e1..cae6116515 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 @@ -6347,10 +6347,10 @@ public abstract class AbstractEntityPersister public void visitKeyFetchables( Consumer fetchableConsumer, EntityMappingType treatTargetType) { - if ( getIdentifierMapping() instanceof FetchableContainer ) { - // essentially means the entity has a composite id - ask the embeddable to visit its fetchables - ( (FetchableContainer) getIdentifierMapping() ).visitFetchables( fetchableConsumer, treatTargetType ); - } +// if ( getIdentifierMapping() instanceof FetchableContainer ) { +// // essentially means the entity has a composite id - ask the embeddable to visit its fetchables +// ( (FetchableContainer) getIdentifierMapping() ).visitFetchables( fetchableConsumer, treatTargetType ); +// } // otherwise, nothing to do } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/UniqueKeyLoadable.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/UniqueKeyLoadable.java index 2c73653eff..362ec37c0a 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/UniqueKeyLoadable.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/UniqueKeyLoadable.java @@ -18,6 +18,7 @@ public interface UniqueKeyLoadable extends Loadable { */ Object loadByUniqueKey(String propertyName, Object uniqueKey, SharedSessionContractImplementor session); + /** * Get the property number of the unique key property */ diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java index 1d519fc154..733e61d9a7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/DomainResultCreationState.java @@ -10,6 +10,7 @@ import java.util.List; import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.metamodel.mapping.AssociationKey; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.jdbc.spi.JdbcValues; @@ -31,6 +32,13 @@ public interface DomainResultCreationState { return (SqlAliasBaseManager) getSqlAstCreationState().getSqlAliasBaseGenerator(); } + default void registerVisitedAssociationKey(AssociationKey associationKey){ + } + + default boolean isAssociationKeyVisited(AssociationKey associationKey){ + return false; + } + /** * Resolve the ModelPart associated with a given NavigablePath. More specific ModelParts should be preferred - e.g. * the SingularAssociationAttributeMapping rather than just the EntityTypeMapping for the associated type diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableForeignKeyResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableForeignKeyResultImpl.java index 517d8cfa69..d7f6506f82 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableForeignKeyResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableForeignKeyResultImpl.java @@ -29,7 +29,7 @@ import org.hibernate.sql.results.graph.basic.BasicFetch; import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; -import org.hibernate.sql.results.graph.entity.internal.EntityFetchDelayedImpl; +import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchImpl; import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl; /** @@ -76,11 +76,9 @@ public class EmbeddableForeignKeyResultImpl ); Fetch fetch; if ( toOneAttributeMapping.getMappedFetchOptions().getTiming() == FetchTiming.DELAYED ) { - fetch = new EntityFetchDelayedImpl( + fetch = new EntityDelayedFetchImpl( this, toOneAttributeMapping, - null, - false, navigablePath.append( fetchable.getFetchableName() ), domainResult ); @@ -89,10 +87,10 @@ public class EmbeddableForeignKeyResultImpl fetch = new EntityFetchSelectImpl( this, toOneAttributeMapping, - null, false, navigablePath.append( fetchable.getFetchableName() ), domainResult, + false, creationState ); } @@ -129,6 +127,11 @@ public class EmbeddableForeignKeyResultImpl return new EmbeddableAssembler( initializer ); } + @Override + public NavigablePath getNavigablePath() { + return super.getNavigablePath().append( "{fk}"); + } + @Override public EmbeddableMappingType getReferencedMappingType() { return (EmbeddableMappingType) getFetchContainer().getPartMappingType(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index 392e236388..c82d503082 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -121,7 +121,6 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces for ( int i = 0; i < identifierInitializers.size(); i++ ) { final Initializer existing = identifierInitializers.get( i ); if ( existing.getNavigablePath().equals( navigablePath ) ) { - identifierInitializers.add( existing ); return existing; } } @@ -328,9 +327,24 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces @SuppressWarnings("WeakerAccess") protected void initializeIdentifier(RowProcessingState rowProcessingState) { + if ( EntityLoadingLogger.TRACE_ENABLED ) { + EntityLoadingLogger.LOGGER.tracef( + "(%s) Beginning Initializer#initializeIdentifier process for entity (%s) ", + StringHelper.collapse( this.getClass().getName() ), + getNavigablePath() + ); + } + identifierInitializers.forEach( initializer -> initializer.resolveKey( rowProcessingState ) ); identifierInitializers.forEach( initializer -> initializer.resolveInstance( rowProcessingState ) ); - identifierInitializers.forEach( initializer -> initializer.initializeInstance( rowProcessingState ) ); + + if ( EntityLoadingLogger.TRACE_ENABLED ) { + EntityLoadingLogger.LOGGER.tracef( + "(%s) Fiish Initializer#initializeIdentifier process for entity (%s) ", + StringHelper.collapse( this.getClass().getName() ), + getNavigablePath() + ); + } } @SuppressWarnings("WeakerAccess") @@ -351,8 +365,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces else { id = identifierAssembler.assemble( rowProcessingState, - jdbcValuesSourceProcessingState - .getProcessingOptions() + jdbcValuesSourceProcessingState.getProcessingOptions() ); } @@ -364,6 +377,10 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces // 2) build the EntityKey this.entityKey = new EntityKey( id, concreteDescriptor ); + if ( jdbcValuesSourceProcessingState.findInitializer( entityKey ) == null ) { + jdbcValuesSourceProcessingState.registerInitilaizer( entityKey, this ); + } + // 3) schedule the EntityKey for batch loading, if possible if ( concreteDescriptor.isBatchLoadable() ) { if ( !session.getPersistenceContext().containsEntity( entityKey ) ) { @@ -377,6 +394,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces if ( missing ) { return; } + identifierInitializers.forEach( initializer -> initializer.initializeInstance( rowProcessingState ) ); final Object entityIdentifier = entityKey.getIdentifier(); @@ -509,7 +527,6 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces ); } - // todo (6.0): do we really need this check ? if ( persistenceContext.containsEntity( entityKey ) ) { Status status = persistenceContext.getEntry( persistenceContext.getEntity( entityKey ) ) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityInitializer.java index acecb4344c..c760cfeb66 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityInitializer.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.results.graph.entity; +import org.hibernate.LockMode; import org.hibernate.engine.spi.EntityKey; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Initializer; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchDelayedImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchImpl.java similarity index 84% rename from hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchDelayedImpl.java rename to hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchImpl.java index 40478d7a5d..5ea8c74fac 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchDelayedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchImpl.java @@ -6,7 +6,6 @@ */ package org.hibernate.sql.results.graph.entity.internal; -import org.hibernate.LockMode; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.query.NavigablePath; @@ -21,23 +20,16 @@ import org.hibernate.sql.results.graph.entity.EntityInitializer; * @author Andrea Boriero * @author Steve Ebersole */ -public class EntityFetchDelayedImpl extends AbstractNonJoinedEntityFetch { - private final LockMode lockMode; - private final boolean nullable; +public class EntityDelayedFetchImpl extends AbstractNonJoinedEntityFetch { private final DomainResult keyResult; - public EntityFetchDelayedImpl( + public EntityDelayedFetchImpl( FetchParent fetchParent, ToOneAttributeMapping fetchedAttribute, - LockMode lockMode, - boolean nullable, NavigablePath navigablePath, DomainResult keyResult) { super( navigablePath, fetchedAttribute, fetchParent ); - this.lockMode = lockMode; - this.nullable = nullable; - this.keyResult = keyResult; } @@ -57,7 +49,7 @@ public class EntityFetchDelayedImpl extends AbstractNonJoinedEntityFetch { AssemblerCreationState creationState) { final EntityInitializer entityInitializer = (EntityInitializer) creationState.resolveInitializer( getNavigablePath(), - () -> new EntityFetchDelayedInitializer( + () -> new EntityDelayedFetchInitializer( getNavigablePath(), getEntityValuedModelPart().getEntityMappingType().getEntityPersister(), keyResult.createResultAssembler( creationState ) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchDelayedInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchInitializer.java similarity index 93% rename from hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchDelayedInitializer.java rename to hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchInitializer.java index d591eb855f..f69370733a 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchDelayedInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityDelayedFetchInitializer.java @@ -11,6 +11,7 @@ import java.util.function.Consumer; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.internal.log.LoggingHelper; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.graph.AbstractFetchParentAccess; @@ -23,7 +24,7 @@ import org.hibernate.sql.results.jdbc.spi.RowProcessingState; * @author Andrea Boriero * @author Steve Ebersole */ -public class EntityFetchDelayedInitializer extends AbstractFetchParentAccess implements EntityInitializer { +public class EntityDelayedFetchInitializer extends AbstractFetchParentAccess implements EntityInitializer { private final NavigablePath navigablePath; private final EntityPersister concreteDescriptor; @@ -32,7 +33,7 @@ public class EntityFetchDelayedInitializer extends AbstractFetchParentAccess imp private Object entityInstance; private Object identifier; - protected EntityFetchDelayedInitializer( + public EntityDelayedFetchInitializer( NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, DomainResultAssembler identifierAssembler) { @@ -147,4 +148,9 @@ public class EntityFetchDelayedInitializer extends AbstractFetchParentAccess imp } } + @Override + public String toString() { + return "EntityDelayedFetchInitializer(" + LoggingHelper.toLoggableString( navigablePath ) + ")"; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java index ddd3912b68..d181c02673 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java @@ -6,9 +6,9 @@ */ package org.hibernate.sql.results.graph.entity.internal; -import org.hibernate.LockMode; import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.DomainResult; @@ -26,18 +26,20 @@ import org.hibernate.sql.results.graph.entity.EntityInitializer; public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch { private final boolean nullable; private final DomainResult result; + private final boolean selectByUniqueKey; public EntityFetchSelectImpl( FetchParent fetchParent, ToOneAttributeMapping fetchedAttribute, - LockMode lockMode, boolean nullable, NavigablePath navigablePath, DomainResult result, + boolean selectByUniqueKey, DomainResultCreationState creationState) { super( navigablePath, fetchedAttribute, fetchParent ); this.nullable = nullable; this.result = result; + this.selectByUniqueKey = selectByUniqueKey; } @Override @@ -54,12 +56,26 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch { public DomainResultAssembler createAssembler(FetchParentAccess parentAccess, AssemblerCreationState creationState) { final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer( getNavigablePath(), - () -> new EntitySelectFetchInitializer( - getNavigablePath(), - getReferencedMappingContainer().getEntityPersister(), - result.createResultAssembler( creationState ), - nullable - ) + () -> { + + EntityPersister entityPersister = getReferencedMappingContainer().getEntityPersister(); + + if ( selectByUniqueKey ) { + return new EntitySelectFetchByUniqueKeyInitializer( + (ToOneAttributeMapping) getFetchedMapping(), + getNavigablePath(), + entityPersister, + result.createResultAssembler( creationState ), + nullable + ); + } + return new EntitySelectFetchInitializer( + getNavigablePath(), + entityPersister, + result.createResultAssembler( creationState ), + nullable + ); + } ); return new EntityAssembler( getResultJavaTypeDescriptor(), initializer ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultInitializer.java index 6a641e2701..8e313078e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultInitializer.java @@ -55,6 +55,6 @@ public class EntityResultInitializer extends AbstractEntityInitializer { @Override public String toString() { - return "EntityRootInitializer(" + getNavigablePath().getFullPath() + ")"; + return CONCRETE_NAME + "(" + getNavigablePath().getFullPath() + ")"; } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java new file mode 100644 index 0000000000..5a24e475b7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java @@ -0,0 +1,77 @@ +/* + * 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.graph.entity.internal; + +import org.hibernate.engine.spi.EntityUniqueKey; +import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.UniqueKeyLoadable; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.graph.DomainResultAssembler; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; + +/** + * @author Andrea Boriero + */ +public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchInitializer { + private final ToOneAttributeMapping fetchedAttribute; + + public EntitySelectFetchByUniqueKeyInitializer( + ToOneAttributeMapping fetchedAttribute, + NavigablePath fetchedNavigable, + EntityPersister concreteDescriptor, + DomainResultAssembler identifierAssembler, + boolean nullable) { + super( fetchedNavigable, concreteDescriptor, identifierAssembler, nullable ); + this.fetchedAttribute = fetchedAttribute; + } + + @Override + public void initializeInstance(RowProcessingState rowProcessingState) { + if ( entityInstance != null ) { + return; + } + + final Object entityIdentifier = identifierAssembler.assemble( rowProcessingState ); + if ( entityIdentifier == null ) { + return; + } + final String entityName = concreteDescriptor.getEntityName(); + String uniqueKeyPropertyName = fetchedAttribute.getMappedBy(); + + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + + EntityUniqueKey euk = new EntityUniqueKey( + entityName, + uniqueKeyPropertyName, + entityIdentifier, + concreteDescriptor.getIdentifierType(), + concreteDescriptor.getEntityMode(), + session.getFactory() + ); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + entityInstance = persistenceContext.getEntity( euk ); + if ( entityInstance == null ) { + entityInstance = ( (UniqueKeyLoadable) concreteDescriptor ).loadByUniqueKey( + uniqueKeyPropertyName, + entityIdentifier, + session + ); + + // If the entity was not in the Persistence Context, but was found now, + // add it to the Persistence Context + if ( entityInstance != null ) { + persistenceContext.addEntity( euk, entityInstance ); + } + } + if ( entityInstance != null ) { + entityInstance = persistenceContext.proxyFor( entityInstance ); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java index e1db27b7c1..c7cc3f5a38 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java @@ -10,28 +10,39 @@ import java.util.function.Consumer; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.engine.spi.EntityKey; +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.persister.entity.EntityPersister; import org.hibernate.proxy.HibernateProxy; import org.hibernate.query.NavigablePath; import org.hibernate.sql.results.graph.AbstractFetchParentAccess; import org.hibernate.sql.results.graph.DomainResultAssembler; +import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.EntityLoadingLogger; +import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; +import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import static org.hibernate.internal.log.LoggingHelper.toLoggableString; + /** * @author Andrea Boriero */ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess implements EntityInitializer { + private static final String CONCRETE_NAME = EntitySelectFetchInitializer.class.getSimpleName(); + private final NavigablePath navigablePath; - private final EntityPersister concreteDescriptor; - private final DomainResultAssembler identifierAssembler; private final boolean isEnhancedForLazyLoading; private final boolean nullable; - private Object entityInstance; + protected final EntityPersister concreteDescriptor; + protected final DomainResultAssembler identifierAssembler; + protected Object entityInstance; - protected EntitySelectFetchInitializer( + public EntitySelectFetchInitializer( NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, DomainResultAssembler identifierAssembler, @@ -55,7 +66,6 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl @Override public void resolveInstance(RowProcessingState rowProcessingState) { - } @Override @@ -64,21 +74,106 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl return; } - final Object id = identifierAssembler.assemble( rowProcessingState ); - if ( id == null ) { + final Object entityIdentifier = identifierAssembler.assemble( rowProcessingState ); + + if ( entityIdentifier == null ) { return; } - final String entityName = concreteDescriptor.getEntityName(); + if ( EntityLoadingLogger.TRACE_ENABLED ) { + EntityLoadingLogger.LOGGER.tracef( + "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", + StringHelper.collapse( this.getClass().getName() ), + getNavigablePath(), + entityIdentifier + ); + } final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final String entityName = concreteDescriptor.getEntityName(); + final EntityKey entityKey = new EntityKey( entityIdentifier, concreteDescriptor ); + + Initializer initializer = rowProcessingState.getJdbcValuesSourceProcessingState().findInitializer( + entityKey ); + + if ( initializer != null ) { + if ( EntityLoadingLogger.DEBUG_ENABLED ) { + EntityLoadingLogger.LOGGER.debugf( + "(%s) Found an initializer for entity (%s) : %s", + CONCRETE_NAME, + toLoggableString( getNavigablePath(), entityIdentifier ), + entityIdentifier + ); + } + initializer.resolveInstance( rowProcessingState ); + entityInstance = initializer.getInitializedInstance(); + return; + } + + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + entityInstance = persistenceContext.getEntity( entityKey ); + if ( entityInstance != null ) { + return; + } + + final LoadingEntityEntry existingLoadingEntry = session + .getPersistenceContext() + .getLoadContexts() + .findLoadingEntityEntry( entityKey ); + + if ( existingLoadingEntry != null ) { + if ( EntityLoadingLogger.DEBUG_ENABLED ) { + EntityLoadingLogger.LOGGER.debugf( + "(%s) Found existing loading entry [%s] - using loading instance", + CONCRETE_NAME, + toLoggableString( + getNavigablePath(), + entityIdentifier + ) + ); + } + this.entityInstance = existingLoadingEntry.getEntityInstance(); + + if ( existingLoadingEntry.getEntityInitializer() != this ) { + // the entity is already being loaded elsewhere + if ( EntityLoadingLogger.DEBUG_ENABLED ) { + EntityLoadingLogger.LOGGER.debugf( + "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", + CONCRETE_NAME, + toLoggableString( getNavigablePath(), entityIdentifier ), + existingLoadingEntry.getEntityInitializer() + ); + } + + // EARLY EXIT!!! + return; + } + } + + if ( EntityLoadingLogger.DEBUG_ENABLED ) { + EntityLoadingLogger.LOGGER.debugf( + "(%s) Invoking session#internalLoad for entity (%s) : %s", + CONCRETE_NAME, + toLoggableString( getNavigablePath(), entityIdentifier ), + entityIdentifier + ); + } entityInstance = session.internalLoad( entityName, - id, + entityIdentifier, true, nullable ); + if ( EntityLoadingLogger.DEBUG_ENABLED ) { + EntityLoadingLogger.LOGGER.debugf( + "(%s) Entity [%s] : %s has being loaded by session.internalLoad.", + CONCRETE_NAME, + toLoggableString( getNavigablePath(), entityIdentifier ), + entityIdentifier + ); + } + if ( entityInstance instanceof HibernateProxy && isEnhancedForLazyLoading ) { ( (HibernateProxy) entityInstance ).getHibernateLazyInitializer().setUnwrap( true ); } @@ -120,4 +215,9 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl super.registerResolutionListener( listener ); } } + + @Override + public String toString() { + return "EntitySelectFetchInitializer(" + LoggingHelper.toLoggableString( getNavigablePath() ) + ")"; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/RootEntityResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/RootEntityResultImpl.java new file mode 100644 index 0000000000..e3e8e78673 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/RootEntityResultImpl.java @@ -0,0 +1,213 @@ +/* + * 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.graph.entity.internal; + +import java.util.ArrayList; + +import org.hibernate.LockMode; +import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping; +import org.hibernate.metamodel.mapping.EntityIdentifierMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.EntityValuedModelPart; +import org.hibernate.metamodel.mapping.EntityVersionMapping; +import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.results.graph.AbstractFetchParent; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultAssembler; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.FetchableContainer; +import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.EntityResult; +import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * @author Andrea Boriero + */ +public class RootEntityResultImpl extends AbstractFetchParent implements EntityResultGraphNode, EntityResult { + + private final String resultVariable; + + private final EntityValuedModelPart referencedModelPart; + private final DomainResult discriminatorResult; + private final DomainResult versionResult; + private DomainResult identifierResult; + private final LockMode lockMode; + + public RootEntityResultImpl( + NavigablePath navigablePath, + EntityValuedModelPart entityValuedModelPart, + String resultVariable, + DomainResultCreationState creationState) { + this( navigablePath, entityValuedModelPart, resultVariable, null, creationState ); + } + + @SuppressWarnings("WeakerAccess") + public RootEntityResultImpl( + NavigablePath navigablePath, + EntityValuedModelPart entityValuedModelPart, + String resultVariable, + EntityMappingType targetType, + DomainResultCreationState creationState) { + super( entityValuedModelPart.getEntityMappingType(), navigablePath ); + this.resultVariable = resultVariable; + this.referencedModelPart = entityValuedModelPart; + this.lockMode = creationState.getSqlAstCreationState().determineLockMode( resultVariable ); + + final EntityMappingType entityDescriptor = referencedModelPart.getEntityMappingType(); + + final TableGroup entityTableGroup = creationState.getSqlAstCreationState().getFromClauseAccess().findTableGroup( navigablePath ); + + EntityIdentifierMapping identifierMapping = entityDescriptor.getIdentifierMapping(); + if ( identifierMapping instanceof SingleAttributeIdentifierMapping ) { + identifierMapping.createDomainResult( + navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ), + entityTableGroup, + null, + creationState + ); + } + else { + visitCompositeIdentifierMapping( navigablePath, creationState, identifierMapping, entityTableGroup ); + } + + final EntityDiscriminatorMapping discriminatorMapping = getDiscriminatorMapping( entityDescriptor, entityTableGroup ); + if ( discriminatorMapping != null ) { + discriminatorResult = discriminatorMapping.createDomainResult( + navigablePath.append( EntityDiscriminatorMapping.ROLE_NAME ), + entityTableGroup, + null, + creationState + ); + } + else { + discriminatorResult = null; + } + + final EntityVersionMapping versionDescriptor = entityDescriptor.getVersionMapping(); + if ( versionDescriptor == null ) { + versionResult = null; + } + else { + versionResult = versionDescriptor.createDomainResult( + navigablePath.append( versionDescriptor.getFetchableName() ), + entityTableGroup, + null, + creationState + ); + } + + afterInitialize( creationState ); + } + + private void visitCompositeIdentifierMapping( + NavigablePath navigablePath, + DomainResultCreationState creationState, + EntityIdentifierMapping identifierMapping, + TableGroup entityTableGroup) { + ManagedMappingType mappingType = (ManagedMappingType) identifierMapping.getPartMappingType(); + fetches = new ArrayList<>(); + mappingType.visitAttributeMappings( + attributeMapping -> { + if ( attributeMapping instanceof ToOneAttributeMapping ) { + ((ToOneAttributeMapping)attributeMapping).getForeignKeyDescriptor().createDomainResult( + navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ), + entityTableGroup, + null, + creationState + ); + } + else { + attributeMapping.createDomainResult( + navigablePath.append( EntityIdentifierMapping.ROLE_LOCAL_NAME ), + entityTableGroup, + null, + creationState + ); + } + } + ); + } + + protected EntityDiscriminatorMapping getDiscriminatorMapping( + EntityMappingType entityDescriptor, + TableGroup entityTableGroup) { + return entityDescriptor.getDiscriminatorMapping(); + } + + @Override + public EntityMappingType getReferencedMappingContainer() { + return getEntityValuedModelPart().getEntityMappingType(); + } + + @Override + public EntityValuedModelPart getEntityValuedModelPart() { + return referencedModelPart; + } + + @Override + public JavaTypeDescriptor getResultJavaTypeDescriptor() { + return getEntityValuedModelPart().getEntityMappingType().getMappedJavaTypeDescriptor(); + } + + public LockMode getLockMode() { + return lockMode; + } + + public DomainResult getDiscriminatorResult() { + return discriminatorResult; + } + + public DomainResult getVersionResult() { + return versionResult; + } + + @Override + public FetchableContainer getReferencedMappingType() { + return getReferencedMappingContainer(); + } + + @Override + public EntityValuedModelPart getReferencedModePart() { + return getEntityValuedModelPart(); + } + + @Override + public String getResultVariable() { + return resultVariable; + } + + @Override + public DomainResultAssembler createResultAssembler(AssemblerCreationState creationState) { + // todo (6.0) : seems like here is where we ought to determine the SQL selection mappings + + final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer( + getNavigablePath(), + () -> new EntityResultInitializer( + this, + getNavigablePath(), + getLockMode(), + identifierResult, + getDiscriminatorResult(), + getVersionResult(), + creationState + ) + ); + + return new EntityAssembler( getResultJavaTypeDescriptor(), initializer ); + } + + @Override + public String toString() { + return "EntityResultImpl {" + getNavigablePath() + "}"; + } +} 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 5d5127f05c..5057b6c3a3 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 @@ -138,25 +138,27 @@ public class StandardRowReader implements RowReader { // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // old - for ( int i = 0; i < initializers.size(); i++ ) { + final int numberOfInitializers = initializers.size(); + + for ( int i = 0; i < numberOfInitializers; i++ ) { final Initializer initializer = initializers.get( i ); if ( ! ( initializer instanceof CollectionInitializer ) ) { initializer.resolveKey( rowProcessingState ); } } - for ( int i = 0; i < initializers.size(); i++ ) { + for ( int i = 0; i < numberOfInitializers; i++ ) { final Initializer initializer = initializers.get( i ); if ( initializer instanceof CollectionInitializer ) { initializer.resolveKey( rowProcessingState ); } } - for ( int i = 0; i < initializers.size(); i++ ) { + for ( int i = 0; i < numberOfInitializers; i++ ) { initializers.get( i ).resolveInstance( rowProcessingState ); } - for ( int i = 0; i < initializers.size(); i++ ) { + for ( int i = 0; i < numberOfInitializers; i++ ) { initializers.get( i ).initializeInstance( rowProcessingState ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/BiDirectionalFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularBiDirectionalFetchImpl.java similarity index 81% rename from hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/BiDirectionalFetchImpl.java rename to hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularBiDirectionalFetchImpl.java index 9f073ebd39..9d5e9f80fa 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/BiDirectionalFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularBiDirectionalFetchImpl.java @@ -39,24 +39,27 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor; /** * @author Andrea Boriero */ -public class BiDirectionalFetchImpl implements BiDirectionalFetch, Association { +public class CircularBiDirectionalFetchImpl implements BiDirectionalFetch, Association { private final FetchTiming timing; private final NavigablePath navigablePath; private final Fetchable fetchable; private final FetchParent fetchParent; + private final LockMode lockMode; private final NavigablePath referencedNavigablePath; - public BiDirectionalFetchImpl( + public CircularBiDirectionalFetchImpl( FetchTiming timing, NavigablePath navigablePath, FetchParent fetchParent, Fetchable fetchable, + LockMode lockMode, NavigablePath referencedNavigablePath) { this.timing = timing; this.fetchParent = fetchParent; this.navigablePath = navigablePath; this.fetchable = fetchable; + this.lockMode = lockMode; this.referencedNavigablePath = referencedNavigablePath; } @@ -174,24 +177,25 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Association { @Override public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { - final EntityInitializer initializer = resolveCircularInitializer( rowProcessingState ); + EntityInitializer initializer = resolveCircularInitializer( rowProcessingState ); if ( initializer == null ) { + final Initializer parentInitializer = rowProcessingState.resolveInitializer( circularPath ); + if ( circularPath.getParent() != null ) { + initializer = (EntityInitializer) rowProcessingState.resolveInitializer( circularPath.getParent() ); + } + else { + assert parentInitializer instanceof CollectionInitializer; + final CollectionInitializer circ = (CollectionInitializer) parentInitializer; + final CollectionKey collectionKey = circ.resolveCollectionKey( rowProcessingState ); + final EntityKey entityKey = new EntityKey( + collectionKey.getKey(), + (EntityPersister) ( (AttributeMapping) fetchable ).getMappedTypeDescriptor() + ); - final Initializer parentInitializer = rowProcessingState.resolveInitializer( - circularPath.getParent() ); - assert parentInitializer instanceof CollectionInitializer; - final CollectionInitializer circ = (CollectionInitializer) parentInitializer; - final CollectionKey collectionKey = circ.resolveCollectionKey( rowProcessingState ); - final EntityKey entityKey = new EntityKey( - collectionKey.getKey(), - (EntityPersister) ( (AttributeMapping) fetchable ).getMappedTypeDescriptor() - ); - - final SharedSessionContractImplementor session = rowProcessingState.getJdbcValuesSourceProcessingState() - .getSession(); - return session.getPersistenceContext() - .getEntity( entityKey ); - + final SharedSessionContractImplementor session = rowProcessingState.getJdbcValuesSourceProcessingState() + .getSession(); + return session.getPersistenceContext().getEntity( entityKey ); + } } if ( initializer.getInitializedInstance() == null ) { initializer.resolveKey( rowProcessingState ); @@ -203,6 +207,12 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Association { private EntityInitializer resolveCircularInitializer(RowProcessingState rowProcessingState) { final Initializer initializer = rowProcessingState.resolveInitializer( circularPath ); + if ( initializer instanceof EntityInitializer ) { + return (EntityInitializer) initializer; + } + if ( initializer instanceof CollectionInitializer ) { + return null; + } final ModelPart initializedPart = initializer.getInitializedPart(); if ( initializedPart instanceof EntityInitializer ) { @@ -211,7 +221,7 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Association { NavigablePath path = circularPath.getParent(); Initializer parentInitializer = rowProcessingState.resolveInitializer( path ); - while ( !( parentInitializer instanceof EntityInitializer) && path.getParent() != null ) { + while ( !( parentInitializer instanceof EntityInitializer ) && path.getParent() != null ) { path = path.getParent(); parentInitializer = rowProcessingState.resolveInitializer( path ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java new file mode 100644 index 0000000000..2d3f8b35ae --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java @@ -0,0 +1,210 @@ +/* + * 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; + +import org.hibernate.LockMode; +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.Association; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EntityMappingType; +import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.MappingType; +import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.query.NavigablePath; +import org.hibernate.sql.results.graph.AssemblerCreationState; +import org.hibernate.sql.results.graph.BiDirectionalFetch; +import org.hibernate.sql.results.graph.DomainResult; +import org.hibernate.sql.results.graph.DomainResultAssembler; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchOptions; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.graph.FetchParentAccess; +import org.hibernate.sql.results.graph.Fetchable; +import org.hibernate.sql.results.graph.entity.EntityInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntityDelayedFetchInitializer; +import org.hibernate.sql.results.graph.entity.internal.EntitySelectFetchInitializer; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; +import org.hibernate.type.descriptor.java.JavaTypeDescriptor; + +/** + * @author Andrea Boriero + */ +public class CircularFetchImpl implements BiDirectionalFetch, Association { + private DomainResult keyResult; + private final EntityMappingType entityMappingType; + private final FetchTiming timing; + private final NavigablePath navigablePath; + private final ToOneAttributeMapping fetchable; + + private final FetchParent fetchParent; + private final NavigablePath referencedNavigablePath; + + public CircularFetchImpl( + EntityMappingType entityMappingType, + FetchTiming timing, + NavigablePath navigablePath, + FetchParent fetchParent, + ToOneAttributeMapping fetchable, + NavigablePath referencedNavigablePath, + DomainResult keyResult) { + this.entityMappingType = entityMappingType; + this.timing = timing; + this.fetchParent = fetchParent; + this.navigablePath = navigablePath; + this.referencedNavigablePath = referencedNavigablePath; + this.fetchable = fetchable; + this.keyResult = keyResult; + + } + + @Override + public NavigablePath getNavigablePath() { + return navigablePath; + } + + @Override + public NavigablePath getReferencedPath() { + return referencedNavigablePath; + } + + @Override + public FetchParent getFetchParent() { + return fetchParent; + } + + @Override + public Fetchable getFetchedMapping() { + return fetchable; + } + + @Override + public JavaTypeDescriptor getResultJavaTypeDescriptor() { + return fetchable.getJavaTypeDescriptor(); + } + + @Override + public DomainResultAssembler createAssembler( + FetchParentAccess parentAccess, + AssemblerCreationState creationState) { + final DomainResultAssembler resultAssembler = keyResult.createResultAssembler( creationState ); + + final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer( + getNavigablePath(), + () -> { + if ( timing == FetchTiming.IMMEDIATE ) { + return new EntitySelectFetchInitializer( + getReferencedPath(), + entityMappingType.getEntityPersister(), + resultAssembler, + fetchable.isNullable() + ); + } + else { + return new EntityDelayedFetchInitializer( + getReferencedPath(), + (EntityPersister) ( (AttributeMapping) fetchable ).getMappedTypeDescriptor(), + resultAssembler + ); + } + } + ); + + return new BiDirectionalFetchAssembler( + initializer, + fetchable.getJavaTypeDescriptor() + ); + } + + @Override + public FetchTiming getTiming() { + return timing; + } + + @Override + public boolean hasTableGroup() { + return true; + } + + @Override + public String getFetchableName() { + return fetchable.getFetchableName(); + } + + @Override + public FetchOptions getMappedFetchOptions() { + throw new UnsupportedOperationException(); + } + + @Override + public String getPartName() { + return fetchable.getFetchableName(); + } + + @Override + public NavigableRole getNavigableRole() { + return fetchable.getNavigableRole(); + } + + @Override + public EntityMappingType findContainingEntityMapping() { + return fetchable.findContainingEntityMapping(); + } + + @Override + public MappingType getPartMappingType() { + return fetchable.getPartMappingType(); + } + + @Override + public JavaTypeDescriptor getJavaTypeDescriptor() { + return fetchable.getJavaTypeDescriptor(); + } + + @Override + public ForeignKeyDescriptor getForeignKeyDescriptor() { + return ( (Association) fetchParent ).getForeignKeyDescriptor(); + } + + @Override + public Fetch generateFetch( + FetchParent fetchParent, + NavigablePath fetchablePath, + FetchTiming fetchTiming, + boolean selected, + LockMode lockMode, + String resultVariable, + DomainResultCreationState creationState) { + throw new UnsupportedOperationException(); + } + + private static class BiDirectionalFetchAssembler implements DomainResultAssembler { + private EntityInitializer initializer; + private JavaTypeDescriptor assembledJavaTypeDescriptor; + + public BiDirectionalFetchAssembler( + EntityInitializer initializer, + JavaTypeDescriptor assembledJavaTypeDescriptor) { + this.initializer = initializer; + this.assembledJavaTypeDescriptor = assembledJavaTypeDescriptor; + } + + @Override + public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) { + return initializer.getInitializedInstance(); + } + + @Override + public JavaTypeDescriptor getAssembledJavaTypeDescriptor() { + return assembledJavaTypeDescriptor; + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java index 620331e08b..5dd99a5dba 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/JdbcValuesSourceProcessingStateStandardImpl.java @@ -18,6 +18,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PreLoadEvent; +import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.collection.internal.ArrayInitializer; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.exec.spi.ExecutionContext; @@ -41,6 +42,7 @@ public class JdbcValuesSourceProcessingStateStandardImpl implements JdbcValuesSo private final BiConsumer loadingEntityEntryConsumer; private Map loadingEntityMap; + private Map initializerMap; private Map loadingCollectionMap; private List arrayInitializers; @@ -105,6 +107,22 @@ public class JdbcValuesSourceProcessingStateStandardImpl implements JdbcValuesSo loadingEntityMap.put( entityKey, loadingEntry ); } + @Override + public void registerInitilaizer( + EntityKey entityKey, + Initializer initializer) { + if ( initializerMap == null ) { + initializerMap = new HashMap<>(); + } + initializerMap.put( entityKey, initializer ); + + } + + @Override + public Initializer findInitializer(EntityKey entityKey) { + return initializerMap == null ? null : initializerMap.get( entityKey ); + } + @Override public LoadingEntityEntry findLoadingEntityLocally(EntityKey entityKey) { return loadingEntityMap == null ? null : loadingEntityMap.get( entityKey ); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesSourceProcessingState.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesSourceProcessingState.java index 9d8a93ac8e..f4bab0f2e5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesSourceProcessingState.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesSourceProcessingState.java @@ -11,6 +11,7 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.PostLoadEvent; import org.hibernate.event.spi.PreLoadEvent; +import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.spi.LoadContexts; import org.hibernate.sql.results.graph.collection.LoadingCollectionEntry; import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; @@ -55,6 +56,13 @@ public interface JdbcValuesSourceProcessingState { EntityKey entityKey, LoadingEntityEntry loadingEntry); + void registerInitilaizer( + EntityKey entityKey, + Initializer initializer); + + Initializer findInitializer(EntityKey entityKey); + + /** * Find a LoadingCollectionEntry locally to this context. *