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 650231cdea..0eeb693eb7 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 @@ -39,10 +39,10 @@ import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NaturalIdMapping; +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.Restrictable; import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; -import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl; import org.hibernate.metamodel.mapping.internal.SimpleForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.query.ComparisonOperator; @@ -340,7 +340,7 @@ public class LoaderSelectBuilder { } for ( ModelPart restrictedPart : restrictedParts ) { - if ( restrictedPart instanceof ForeignKeyDescriptor || restrictedPart instanceof NonAggregatedIdentifierMappingImpl ) { + if ( restrictedPart instanceof ForeignKeyDescriptor || restrictedPart instanceof NonAggregatedIdentifierMapping ) { return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatedAssociationModelPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatedAssociationModelPart.java index ea02b2114d..b54e3a8ccf 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatedAssociationModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/DiscriminatedAssociationModelPart.java @@ -7,6 +7,7 @@ package org.hibernate.metamodel.mapping; import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; +import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.sql.results.graph.FetchableContainer; @@ -26,4 +27,9 @@ public interface DiscriminatedAssociationModelPart extends Fetchable, FetchableC EntityMappingType resolveDiscriminatorValue(Object discriminatorValue); Object resolveDiscriminatorForEntityType(EntityMappingType entityMappingType); + + @Override + default boolean isSimpleJoinPredicate(Predicate predicate) { + return predicate == null; + } } 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 db3e426dd8..278777c39b 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 @@ -116,6 +116,8 @@ public interface ForeignKeyDescriptor extends VirtualModelPart, ValueMapping { SqlExpressionResolver sqlExpressionResolver, SqlAstCreationContext creationContext); + boolean isSimpleJoinPredicate(Predicate predicate); + @Override default String getPartName() { return PART_NAME; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/NonAggregatedIdentifierMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/NonAggregatedIdentifierMapping.java index b10a182cfc..82e48ad232 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/NonAggregatedIdentifierMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/NonAggregatedIdentifierMapping.java @@ -27,7 +27,7 @@ import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable; * @see jakarta.persistence.IdClass * @see jakarta.persistence.MapsId */ -public interface NonAggregatedIdentifierMapping extends CompositeIdentifierMapping, EmbeddableValuedFetchable, FetchOptions { +public interface NonAggregatedIdentifierMapping extends CompositeIdentifierMapping, EmbeddableValuedFetchable, FetchOptions, VirtualModelPart { /** * The virtual-id representation of this id mapping */ 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 cd7e3c2ef4..61b963ad17 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 @@ -89,7 +89,7 @@ public class EmbeddedAttributeMapping stateArrayPosition, tableExpression, attributeMetadataAccess, - getPropertyAccess(parentInjectionAttributeName, embeddableMappingType), + getPropertyAccess( parentInjectionAttributeName, embeddableMappingType ), mappedFetchTiming, mappedFetchStyle, embeddableMappingType, @@ -132,7 +132,7 @@ public class EmbeddedAttributeMapping } // Constructor is only used for creating the inverse attribute mapping - private EmbeddedAttributeMapping( + EmbeddedAttributeMapping( ManagedMappingType keyDeclaringType, TableGroupProducer declaringTableGroupProducer, SelectableMappings selectableMappings, @@ -161,29 +161,6 @@ public class EmbeddedAttributeMapping this.parentInjectionAttributePropertyAccess = null; } - public static EmbeddableValuedModelPart createInverseModelPart( - EmbeddableValuedModelPart modelPart, - ManagedMappingType keyDeclaringType, - TableGroupProducer declaringTableGroupProducer, - SelectableMappings selectableMappings, - MappingModelCreationProcess creationProcess) { - final EmbeddableMappingType embeddableTypeDescriptor; - if ( modelPart instanceof CompositeIdentifierMapping ) { - embeddableTypeDescriptor = ( (CompositeIdentifierMapping) modelPart ).getMappedIdEmbeddableTypeDescriptor(); - } - else { - embeddableTypeDescriptor = modelPart.getEmbeddableTypeDescriptor(); - } - return new EmbeddedAttributeMapping( - keyDeclaringType, - declaringTableGroupProducer, - selectableMappings, - modelPart, - embeddableTypeDescriptor, - creationProcess - ); - } - @Override public EmbeddableMappingType getMappedType() { return getEmbeddableTypeDescriptor(); 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 ab03f5a12b..7485c425a8 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 @@ -36,6 +36,7 @@ import org.hibernate.sql.ast.SqlAstJoinType; 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.Expression; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableGroupProducer; @@ -115,7 +116,7 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor { this.targetSide = original.targetSide; this.keySide = new EmbeddedForeignKeyDescriptorSide( Nature.KEY, - EmbeddedAttributeMapping.createInverseModelPart( + MappingModelCreationHelper.createInverseModelPart( original.targetSide.getModelPart(), keyDeclaringType, keyDeclaringTableGroupProducer, @@ -377,6 +378,84 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor { return getPredicate( targetSideReference, keySideReference, creationContext, targetSelectableMappings, keySelectableMappings ); } + @Override + public boolean isSimpleJoinPredicate(Predicate predicate) { + if ( !( predicate instanceof Junction ) ) { + return false; + } + final Junction junction = (Junction) predicate; + if ( junction.getNature() != Junction.Nature.CONJUNCTION ) { + return false; + } + final List predicates = junction.getPredicates(); + if ( predicates.size() != keySelectableMappings.getJdbcTypeCount() ) { + return false; + } + Boolean lhsIsKey = null; + for ( int i = 0; i < predicates.size(); i++ ) { + final Predicate p = predicates.get( i ); + if ( !( p instanceof ComparisonPredicate ) ) { + return false; + } + final ComparisonPredicate comparisonPredicate = (ComparisonPredicate) p; + if ( comparisonPredicate.getOperator() != ComparisonOperator.EQUAL ) { + return false; + } + final Expression lhsExpr = comparisonPredicate.getLeftHandExpression(); + final Expression rhsExpr = comparisonPredicate.getRightHandExpression(); + if ( !( lhsExpr instanceof ColumnReference ) || !( rhsExpr instanceof ColumnReference ) ) { + return false; + } + final ColumnReference lhs = (ColumnReference) lhsExpr; + final ColumnReference rhs = (ColumnReference) rhsExpr; + if ( lhsIsKey == null ) { + final String keyExpression = keySelectableMappings.getSelectable( i ).getSelectionExpression(); + final String targetExpression = targetSelectableMappings.getSelectable( i ).getSelectionExpression(); + if ( keyExpression.equals( targetExpression ) ) { + if ( !lhs.getColumnExpression().equals( keyExpression ) + || !rhs.getColumnExpression().equals( keyExpression ) ) { + return false; + } + } + else { + if ( keyExpression.equals( lhs.getColumnExpression() ) ) { + if ( !targetExpression.equals( rhs.getColumnExpression() ) ) { + return false; + } + lhsIsKey = true; + } + else if ( keyExpression.equals( rhs.getColumnExpression() ) ) { + if ( !targetExpression.equals( lhs.getColumnExpression() ) ) { + return false; + } + lhsIsKey = false; + } + else { + return false; + } + } + } + else { + final String lhsSelectionExpression; + final String rhsSelectionExpression; + if ( lhsIsKey ) { + lhsSelectionExpression = keySelectableMappings.getSelectable( i ).getSelectionExpression(); + rhsSelectionExpression = targetSelectableMappings.getSelectable( i ).getSelectionExpression(); + } + else { + lhsSelectionExpression = targetSelectableMappings.getSelectable( i ).getSelectionExpression(); + rhsSelectionExpression = keySelectableMappings.getSelectable( i ).getSelectionExpression(); + } + if ( !lhs.getColumnExpression().equals( lhsSelectionExpression ) + || !rhs.getColumnExpression().equals( rhsSelectionExpression ) ) { + return false; + } + } + } + + return true; + } + private Predicate getPredicate( TableReference lhs, TableReference rhs, 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 d01d02cf09..9f563b73b9 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 @@ -37,6 +37,7 @@ import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableMapping; +import org.hibernate.metamodel.mapping.VirtualModelPart; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -168,7 +169,14 @@ public class EntityCollectionPart final CompositeType compositeType; if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded() && compositeType.getPropertyNames().length == 1 ) { - this.targetKeyPropertyNames = Collections.singleton( compositeType.getPropertyNames()[0] ); + final Set targetKeyPropertyNames = new HashSet<>( 2 ); + ToOneAttributeMapping.addPrefixedPropertyNames( + targetKeyPropertyNames, + compositeType.getPropertyNames()[0], + compositeType.getSubtypes()[0], + creationProcess.getCreationContext().getSessionFactory() + ); + this.targetKeyPropertyNames = targetKeyPropertyNames; } else { final String mapsIdAttributeName; @@ -327,6 +335,11 @@ public class EntityCollectionPart return SqlAstJoinType.INNER; } + @Override + public boolean isSimpleJoinPredicate(Predicate predicate) { + return fkDescriptor.isSimpleJoinPredicate( predicate ); + } + @Override public Nature getNature() { return nature; @@ -391,13 +404,14 @@ public class EntityCollectionPart // This is not possible for one-to-many associations because we need to create the target table group eagerly, // to preserve the cardinality. Also, the OneToManyTableGroup has no reference to the parent table group if ( !collectionDescriptor.isOneToMany() && targetKeyPropertyNames.contains( name ) ) { - if ( fkDescriptor.getTargetPart() instanceof NonAggregatedIdentifierMappingImpl ) { - return ( (ModelPartContainer) fkDescriptor.getKeyPart() ).findSubPart( name, targetType ); - } if ( fkTargetModelPart instanceof ToOneAttributeMapping ) { return fkTargetModelPart; } - return fkDescriptor.getKeyPart(); + final ModelPart keyPart = fkDescriptor.getKeyPart(); + if ( keyPart instanceof EmbeddableValuedModelPart && keyPart instanceof VirtualModelPart ) { + return ( (ModelPartContainer) keyPart ).findSubPart( name, targetType ); + } + return keyPart; } return EntityValuedFetchable.super.findSubPart( name, targetType ); } 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 7ff5939997..ac140e1557 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 @@ -61,7 +61,6 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.GeneratedValueResolver; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; -import org.hibernate.metamodel.mapping.MappingModelCreationLogger; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.NonTransientException; @@ -71,6 +70,7 @@ import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectableMappings; import org.hibernate.metamodel.mapping.StateArrayContributorMetadata; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; +import org.hibernate.metamodel.mapping.VirtualModelPart; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; @@ -341,20 +341,40 @@ public class MappingModelCreationHelper { attrType, tableExpression, rootTableKeyColumnNames, - attributeMappingType -> new EmbeddedAttributeMapping( - attrName, - declaringType.getNavigableRole().append( attrName ), - stateArrayPosition, - tableExpression, - attributeMetadataAccess, - component.getParentProperty(), - FetchTiming.IMMEDIATE, - FetchStyle.JOIN, - attributeMappingType, - declaringType, - propertyAccess, - bootProperty.getValueGenerationStrategy() - ), + attributeMappingType -> { + if ( component.isEmbedded() ) { + return new VirtualEmbeddedAttributeMapping( + attrName, + declaringType.getNavigableRole().append( attrName ), + stateArrayPosition, + tableExpression, + attributeMetadataAccess, + component.getParentProperty(), + FetchTiming.IMMEDIATE, + FetchStyle.JOIN, + attributeMappingType, + declaringType, + propertyAccess, + bootProperty.getValueGenerationStrategy() + ); + } + else { + return new EmbeddedAttributeMapping( + attrName, + declaringType.getNavigableRole().append( attrName ), + stateArrayPosition, + tableExpression, + attributeMetadataAccess, + component.getParentProperty(), + FetchTiming.IMMEDIATE, + FetchStyle.JOIN, + attributeMappingType, + declaringType, + propertyAccess, + bootProperty.getValueGenerationStrategy() + ); + } + }, creationProcess ); @@ -1172,7 +1192,7 @@ public class MappingModelCreationHelper { if ( inverse ) { return new EmbeddedForeignKeyDescriptor( embeddableValuedModelPart, - EmbeddedAttributeMapping.createInverseModelPart( + createInverseModelPart( embeddableValuedModelPart, keyDeclaringType, keyDeclaringTableGroupProducer, @@ -1189,7 +1209,7 @@ public class MappingModelCreationHelper { } else { return new EmbeddedForeignKeyDescriptor( - EmbeddedAttributeMapping.createInverseModelPart( + createInverseModelPart( embeddableValuedModelPart, keyDeclaringType, keyDeclaringTableGroupProducer, @@ -1494,6 +1514,41 @@ public class MappingModelCreationHelper { ); } + public static EmbeddableValuedModelPart createInverseModelPart( + EmbeddableValuedModelPart modelPart, + ManagedMappingType keyDeclaringType, + TableGroupProducer declaringTableGroupProducer, + SelectableMappings selectableMappings, + MappingModelCreationProcess creationProcess) { + final EmbeddableMappingType embeddableTypeDescriptor; + if ( modelPart instanceof CompositeIdentifierMapping ) { + embeddableTypeDescriptor = ( (CompositeIdentifierMapping) modelPart ).getMappedIdEmbeddableTypeDescriptor(); + } + else { + embeddableTypeDescriptor = modelPart.getEmbeddableTypeDescriptor(); + } + if ( modelPart instanceof VirtualModelPart ) { + return new VirtualEmbeddedAttributeMapping( + keyDeclaringType, + declaringTableGroupProducer, + selectableMappings, + modelPart, + embeddableTypeDescriptor, + creationProcess + ); + } + else { + return new EmbeddedAttributeMapping( + keyDeclaringType, + declaringTableGroupProducer, + selectableMappings, + modelPart, + embeddableTypeDescriptor, + creationProcess + ); + } + } + @SuppressWarnings("rawtypes") private static class CollectionMappingTypeImpl implements CollectionMappingType { private final JavaType collectionJtd; 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 8308f94b27..3c642f9c75 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 @@ -517,6 +517,11 @@ public class PluralAttributeMappingImpl return SqlAstJoinType.LEFT; } + @Override + public boolean isSimpleJoinPredicate(Predicate predicate) { + return fkDescriptor.isSimpleJoinPredicate( predicate ); + } + @Override public TableGroupJoin createTableGroupJoin( NavigablePath navigablePath, 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 ab48864c04..1d16636212 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 @@ -37,6 +37,7 @@ import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.tree.expression.ColumnReference; +import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroupProducer; import org.hibernate.sql.ast.tree.from.TableReference; @@ -289,6 +290,28 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa ); } + @Override + public boolean isSimpleJoinPredicate(Predicate predicate) { + if ( !( predicate instanceof ComparisonPredicate ) ) { + return false; + } + final ComparisonPredicate comparisonPredicate = (ComparisonPredicate) predicate; + if ( comparisonPredicate.getOperator() != ComparisonOperator.EQUAL ) { + return false; + } + final Expression lhsExpr = comparisonPredicate.getLeftHandExpression(); + final Expression rhsExpr = comparisonPredicate.getRightHandExpression(); + if ( !( lhsExpr instanceof ColumnReference ) || !( rhsExpr instanceof ColumnReference ) ) { + return false; + } + final String lhs = ( (ColumnReference) lhsExpr ).getColumnExpression(); + final String rhs = ( (ColumnReference) rhsExpr ).getColumnExpression(); + final String keyExpression = keySide.getModelPart().getSelectionExpression(); + final String targetExpression = targetSide.getModelPart().getSelectionExpression(); + return ( lhs.equals( keyExpression ) && rhs.equals( targetExpression ) ) + || ( lhs.equals( targetExpression ) && rhs.equals( keyExpression ) ); + } + protected TableReference getTableReference(TableGroup lhs, TableGroup tableGroup, String table) { final NavigablePath navigablePath = lhs.getNavigablePath().append( getTargetPart().getFetchableName() ); if ( lhs.getPrimaryTableReference().getTableReference( navigablePath, table ) != null ) { 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 f6551d82fd..7248eed087 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 @@ -15,7 +15,6 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import org.hibernate.LockMode; -import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -30,7 +29,6 @@ import org.hibernate.mapping.OneToOne; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.Selectable; -import org.hibernate.mapping.Table; import org.hibernate.mapping.ToOne; import org.hibernate.mapping.Value; import org.hibernate.metamodel.mapping.AssociationKey; @@ -47,6 +45,7 @@ import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; +import org.hibernate.metamodel.mapping.VirtualModelPart; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.entity.AbstractEntityPersister; @@ -96,6 +95,7 @@ import org.hibernate.tuple.IdentifierProperty; import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.ComponentType; import org.hibernate.type.CompositeType; +import org.hibernate.type.EmbeddedComponentType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -440,8 +440,15 @@ public class ToOneAttributeMapping final CompositeType compositeType; if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded() && compositeType.getPropertyNames().length == 1 ) { + final Set targetKeyPropertyNames = new HashSet<>( 2 ); this.targetKeyPropertyName = compositeType.getPropertyNames()[0]; - this.targetKeyPropertyNames = Collections.singleton( targetKeyPropertyName ); + addPrefixedPropertyNames( + targetKeyPropertyNames, + targetKeyPropertyName, + compositeType.getSubtypes()[0], + declaringEntityPersister.getFactory() + ); + this.targetKeyPropertyNames = targetKeyPropertyNames; } else { this.targetKeyPropertyName = referencedPropertyName; @@ -551,6 +558,9 @@ public class ToOneAttributeMapping if ( entityType.isReferenceToPrimaryKey() ) { propertyName = entityType.getAssociatedEntityPersister( factory ).getIdentifierPropertyName(); } + else if ( identifierOrUniqueKeyType instanceof EmbeddedComponentType ) { + propertyName = null; + } else { propertyName = entityType.getRHSUniqueKeyPropertyName(); } @@ -651,20 +661,17 @@ public class ToOneAttributeMapping // Prefer resolving the key part of the foreign key rather than the target part if possible // This way, we don't have to register table groups the target entity type if ( canUseParentTableGroup && targetKeyPropertyNames.contains( name ) ) { - final ModelPart fkSideModelPart; - final ModelPart fkTargetModelPart; + final ModelPart fkPart; if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) { - fkTargetModelPart = foreignKeyDescriptor.getTargetPart(); - fkSideModelPart = foreignKeyDescriptor.getKeyPart(); + fkPart = foreignKeyDescriptor.getKeyPart(); } else { - fkTargetModelPart = foreignKeyDescriptor.getKeyPart(); - fkSideModelPart = foreignKeyDescriptor.getTargetPart(); + fkPart = foreignKeyDescriptor.getTargetPart(); } - if ( fkTargetModelPart instanceof NonAggregatedIdentifierMappingImpl ) { - return ( (ModelPartContainer) fkSideModelPart ).findSubPart( name, targetType ); + if ( fkPart instanceof EmbeddableValuedModelPart && fkPart instanceof VirtualModelPart ) { + return ( (ModelPartContainer) fkPart ).findSubPart( name, targetType ); } - return fkSideModelPart; + return fkPart; } return EntityValuedFetchable.super.findSubPart( name, targetType ); } @@ -1223,6 +1230,11 @@ public class ToOneAttributeMapping } } + @Override + public boolean isSimpleJoinPredicate(Predicate predicate) { + return foreignKeyDescriptor.isSimpleJoinPredicate( predicate ); + } + @Override public int getNumberOfFetchables() { return getEntityMappingType().getNumberOfFetchables(); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualEmbeddedAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualEmbeddedAttributeMapping.java new file mode 100644 index 0000000000..3fb34410cd --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/VirtualEmbeddedAttributeMapping.java @@ -0,0 +1,119 @@ +/* + * 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.internal; + +import org.hibernate.engine.FetchStyle; +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.AttributeMapping; +import org.hibernate.metamodel.mapping.EmbeddableMappingType; +import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; +import org.hibernate.metamodel.mapping.ManagedMappingType; +import org.hibernate.metamodel.mapping.SelectableMappings; +import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; +import org.hibernate.metamodel.mapping.VirtualModelPart; +import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.property.access.spi.PropertyAccess; +import org.hibernate.sql.ast.tree.from.TableGroupProducer; +import org.hibernate.tuple.ValueGeneration; + +/** + * @author Christian Beikov + */ +public class VirtualEmbeddedAttributeMapping extends EmbeddedAttributeMapping implements VirtualModelPart { + + public VirtualEmbeddedAttributeMapping( + String name, + NavigableRole navigableRole, + int stateArrayPosition, + String tableExpression, + StateArrayContributorMetadataAccess attributeMetadataAccess, + String parentInjectionAttributeName, + FetchTiming mappedFetchTiming, + FetchStyle mappedFetchStyle, + EmbeddableMappingType embeddableMappingType, + ManagedMappingType declaringType, + PropertyAccess propertyAccess, ValueGeneration valueGeneration) { + super( + name, + navigableRole, + stateArrayPosition, + tableExpression, + attributeMetadataAccess, + parentInjectionAttributeName, + mappedFetchTiming, + mappedFetchStyle, + embeddableMappingType, + declaringType, + propertyAccess, + valueGeneration + ); + } + + public VirtualEmbeddedAttributeMapping( + String name, + NavigableRole navigableRole, + int stateArrayPosition, + String tableExpression, + StateArrayContributorMetadataAccess attributeMetadataAccess, + PropertyAccess parentInjectionAttributePropertyAccess, + FetchTiming mappedFetchTiming, + FetchStyle mappedFetchStyle, + EmbeddableMappingType embeddableMappingType, + ManagedMappingType declaringType, + PropertyAccess propertyAccess, ValueGeneration valueGeneration) { + super( + name, + navigableRole, + stateArrayPosition, + tableExpression, + attributeMetadataAccess, + parentInjectionAttributePropertyAccess, + mappedFetchTiming, + mappedFetchStyle, + embeddableMappingType, + declaringType, + propertyAccess, + valueGeneration + ); + } + + // Constructor is only used for creating the inverse attribute mapping + VirtualEmbeddedAttributeMapping( + ManagedMappingType keyDeclaringType, + TableGroupProducer declaringTableGroupProducer, + SelectableMappings selectableMappings, + EmbeddableValuedModelPart inverseModelPart, + EmbeddableMappingType embeddableTypeDescriptor, + MappingModelCreationProcess creationProcess) { + super( + keyDeclaringType, + declaringTableGroupProducer, + selectableMappings, + inverseModelPart, + embeddableTypeDescriptor, + creationProcess + ); + } + + @Override + public AttributeMapping copy(ManagedMappingType declaringType) { + return new VirtualEmbeddedAttributeMapping( + getAttributeName(), + getNavigableRole(), + getStateArrayPosition(), + getContainingTableExpression(), + getAttributeMetadataAccess(), + getParentInjectionAttributePropertyAccess(), + getTiming(), + getStyle(), + getEmbeddableTypeDescriptor(), + declaringType, + getPropertyAccess(), + getValueGeneration() + ); + } +} 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 91410d156b..a543ec349d 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 @@ -167,6 +167,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NaturalIdMapping; +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.Queryable; import org.hibernate.metamodel.mapping.SelectableConsumer; @@ -184,7 +185,6 @@ import org.hibernate.metamodel.mapping.internal.GeneratedValuesProcessor; import org.hibernate.metamodel.mapping.internal.InFlightEntityMappingType; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; -import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl; import org.hibernate.metamodel.mapping.internal.SimpleNaturalIdMapping; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.spi.EntityInstantiator; @@ -5023,7 +5023,7 @@ public abstract class AbstractEntityPersister baseValueType = (ManagedMappingType) attributeMapping.getMappedType(); } } - else if ( identifierMapping instanceof NonAggregatedIdentifierMappingImpl ) { + else if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) { final EmbeddedAttributeMapping embeddedAttributeMapping = (EmbeddedAttributeMapping) findAttributeMapping( NavigableRole.IDENTIFIER_MAPPER_PROPERTY ); final AttributeMapping mapping = embeddedAttributeMapping.getMappedType() .findAttributeMapping( basePropertyName ); @@ -6386,8 +6386,8 @@ public abstract class AbstractEntityPersister } private ModelPart getIdentifierModelPart(String name, EntityMappingType treatTargetType) { - if ( identifierMapping instanceof NonAggregatedIdentifierMappingImpl ) { - final ModelPart subPart = ( (NonAggregatedIdentifierMappingImpl) identifierMapping ).findSubPart( + if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) { + final ModelPart subPart = ( (NonAggregatedIdentifierMapping) identifierMapping ).findSubPart( name, treatTargetType ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/DomainResultCreationStateImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/DomainResultCreationStateImpl.java index f077c0495e..3ef2fe2244 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/DomainResultCreationStateImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/DomainResultCreationStateImpl.java @@ -28,8 +28,8 @@ import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ModelPart; +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; import org.hibernate.metamodel.mapping.internal.BasicValuedCollectionPart; -import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl; import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.query.EntityIdentifierNavigablePath; import org.hibernate.query.NavigablePath; @@ -428,7 +428,7 @@ public class DomainResultCreationStateImpl if ( fetchableContainer instanceof EntityValuedModelPart ) { final EntityValuedModelPart entityValuedFetchable = (EntityValuedModelPart) fetchableContainer; final EntityIdentifierMapping identifierMapping = entityValuedFetchable.getEntityMappingType().getIdentifierMapping(); - final boolean idClass = identifierMapping instanceof NonAggregatedIdentifierMappingImpl; + final boolean idClass = identifierMapping instanceof NonAggregatedIdentifierMapping; final String identifierAttributeName = attributeName( identifierMapping ); if ( idClass ) { final Map.Entry oldEntry = relativePathStack.getCurrent(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java index 7a878738ee..9ce1dc6956 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/BaseSqmToSqlAstConverter.java @@ -2842,28 +2842,39 @@ public abstract class BaseSqmToSqlAstConverter extends Base querySpec.getFromClause().addRoot( tableGroup ); } else { - final TableGroupJoin tableGroupJoin = joinProducer.createTableGroupJoin( - joinedPath.getNavigablePath(), + // Check if we can reuse a table group join of the parent + final TableGroup compatibleTableGroup = findCompatibleJoinedGroup( actualParentTableGroup, - null, - defaultSqlAstJoinType, - false, - false, - this + joinProducer, + defaultSqlAstJoinType ); - // Implicit joins in the ON clause of attribute joins need to be added as nested table group joins - // We don't have to do that for entity joins etc. as these do not have an inherent dependency on the lhs. - // We can just add the implicit join before the currently processing join - // See consumeEntityJoin for details - final boolean nested = currentClauseStack.getCurrent() == Clause.FROM - && currentlyProcessingJoin instanceof SqmAttributeJoin; - if ( nested ) { - actualParentTableGroup.addNestedTableGroupJoin( tableGroupJoin ); + if ( compatibleTableGroup == null ) { + final TableGroupJoin tableGroupJoin = joinProducer.createTableGroupJoin( + joinedPath.getNavigablePath(), + actualParentTableGroup, + null, + defaultSqlAstJoinType, + false, + false, + this + ); + // Implicit joins in the ON clause of attribute joins need to be added as nested table group joins + // We don't have to do that for entity joins etc. as these do not have an inherent dependency on the lhs. + // We can just add the implicit join before the currently processing join + // See consumeEntityJoin for details + final boolean nested = currentClauseStack.getCurrent() == Clause.FROM + && currentlyProcessingJoin instanceof SqmAttributeJoin; + if ( nested ) { + actualParentTableGroup.addNestedTableGroupJoin( tableGroupJoin ); + } + else { + actualParentTableGroup.addTableGroupJoin( tableGroupJoin ); + } + tableGroup = tableGroupJoin.getJoinedGroup(); } else { - actualParentTableGroup.addTableGroupJoin( tableGroupJoin ); + tableGroup = compatibleTableGroup; } - tableGroup = tableGroupJoin.getJoinedGroup(); } fromClauseIndex.register( joinedPath, tableGroup ); @@ -2875,6 +2886,31 @@ public abstract class BaseSqmToSqlAstConverter extends Base return tableGroup; } + private TableGroup findCompatibleJoinedGroup( + TableGroup parentTableGroup, + TableGroupJoinProducer joinProducer, + SqlAstJoinType requestedJoinType) { + // We don't look into nested table group joins as that wouldn't be "compatible" + for ( TableGroupJoin join : parentTableGroup.getTableGroupJoins() ) { + // Compatibility obviously requires the same model part but also join type compatibility + // Note that if the requested join type is left, we can also use an existing inner join + // The other case, when the requested join type is inner and there is an existing left join, + // is not compatible though because the cardinality is different. + // We could reuse the join though if we alter the join type to INNER, but that's an optimization for later + final SqlAstJoinType joinType = join.getJoinType(); + if ( join.getJoinedGroup().getModelPart() == joinProducer + && ( requestedJoinType == joinType || requestedJoinType == SqlAstJoinType.LEFT && joinType == SqlAstJoinType.INNER ) ) { + // If there is an existing inner join, we can always use that as a new join can never produce results + // regardless of the join type or predicate since the LHS is the same table group + // If this is a left join though, we have to check if the predicate is simply the association predicate + if ( joinType == SqlAstJoinType.INNER || joinProducer.isSimpleJoinPredicate( join.getPredicate() ) ) { + return join.getJoinedGroup(); + } + } + } + return null; + } + private void registerPluralTableGroupParts(TableGroup tableGroup) { if ( tableGroup instanceof PluralTableGroup ) { final PluralTableGroup pluralTableGroup = (PluralTableGroup) tableGroup; diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/NonAggregatedCompositeValuedPathInterpretation.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/NonAggregatedCompositeValuedPathInterpretation.java index e37caae8ae..a2c8f1284b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/NonAggregatedCompositeValuedPathInterpretation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/NonAggregatedCompositeValuedPathInterpretation.java @@ -7,11 +7,10 @@ package org.hibernate.query.sqm.sql.internal; import org.hibernate.metamodel.mapping.ModelPart; -import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl; +import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping; import org.hibernate.query.NavigablePath; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath; -import org.hibernate.query.sqm.tree.domain.SqmPath; import org.hibernate.sql.ast.SqlAstWalker; import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.expression.SqlTupleContainer; @@ -31,7 +30,7 @@ public class NonAggregatedCompositeValuedPathInterpretation final TableGroup tableGroup = sqlAstCreationState .getFromClauseAccess() .findTableGroup( sqmPath.getLhs().getNavigablePath() ); - final NonAggregatedIdentifierMappingImpl mapping = (NonAggregatedIdentifierMappingImpl) tableGroup.getModelPart() + final NonAggregatedIdentifierMapping mapping = (NonAggregatedIdentifierMapping) tableGroup.getModelPart() .findSubPart( sqmPath.getReferencedPathSource().getPathName(), null ); return new NonAggregatedCompositeValuedPathInterpretation<>( diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index bb2647a531..ae86d4bfce 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -3912,6 +3912,16 @@ public abstract class AbstractSqlAstTranslator implemen tableGroupJoinCollector ); } + // A lazy table group, even if uninitialized, might contain table group joins + else if ( joinedGroup instanceof LazyTableGroup ) { + processNestedTableGroupJoins( joinedGroup, tableGroupJoinCollector ); + if ( tableGroupJoinCollector != null ) { + tableGroupJoinCollector.addAll( joinedGroup.getTableGroupJoins() ); + } + else { + processTableGroupJoins( joinedGroup ); + } + } } protected void renderTableGroupJoin(TableGroupJoin tableGroupJoin, List tableGroupJoinCollector) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/LazyTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/LazyTableGroup.java index 9b472a2062..f8069db578 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/LazyTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/LazyTableGroup.java @@ -6,6 +6,7 @@ */ package org.hibernate.sql.ast.tree.from; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.function.BiPredicate; @@ -36,6 +37,8 @@ public class LazyTableGroup extends DelegatingTableGroup { private final Supplier tableGroupSupplier; private final TableGroup parentTableGroup; private final BiPredicate navigablePathChecker; + private List tableGroupJoins; + private List nestedTableGroupJoins; private Consumer tableGroupConsumer; private TableGroup tableGroup; @@ -72,6 +75,18 @@ public class LazyTableGroup extends DelegatingTableGroup { } tableGroup = tableGroupSupplier.get(); + if ( tableGroupJoins != null ) { + for ( TableGroupJoin tableGroupJoin : tableGroupJoins ) { + tableGroup.addTableGroupJoin( tableGroupJoin ); + } + tableGroupJoins = null; + } + if ( nestedTableGroupJoins != null ) { + for ( TableGroupJoin tableGroupJoin : nestedTableGroupJoins ) { + tableGroup.addNestedTableGroupJoin( tableGroupJoin ); + } + nestedTableGroupJoins = null; + } if ( tableGroupConsumer != null ) { tableGroupConsumer.accept( tableGroup ); tableGroupConsumer = null; @@ -102,24 +117,70 @@ public class LazyTableGroup extends DelegatingTableGroup { @Override public List getTableGroupJoins() { - return tableGroup == null ? Collections.emptyList() : tableGroup.getTableGroupJoins(); + if ( tableGroup == null ) { + return nestedTableGroupJoins == null ? Collections.emptyList() : nestedTableGroupJoins; + } + else { + return tableGroup.getTableGroupJoins(); + } } @Override public List getNestedTableGroupJoins() { - return tableGroup == null ? Collections.emptyList() : tableGroup.getNestedTableGroupJoins(); + if ( tableGroup == null ) { + return tableGroupJoins == null ? Collections.emptyList() : tableGroupJoins; + } + else { + return tableGroup.getNestedTableGroupJoins(); + } + } + + @Override + public void addTableGroupJoin(TableGroupJoin join) { + if ( tableGroup == null ) { + if ( tableGroupJoins == null ) { + tableGroupJoins = new ArrayList<>(); + } + tableGroupJoins.add( join ); + } + else { + getTableGroup().addTableGroupJoin( join ); + } + } + + @Override + public void addNestedTableGroupJoin(TableGroupJoin join) { + if ( tableGroup == null ) { + if ( nestedTableGroupJoins == null ) { + nestedTableGroupJoins = new ArrayList<>(); + } + nestedTableGroupJoins.add( join ); + } + else { + getTableGroup().addNestedTableGroupJoin( join ); + } } @Override public void visitTableGroupJoins(Consumer consumer) { - if ( tableGroup != null ) { + if ( tableGroup == null ) { + if ( tableGroupJoins != null ) { + tableGroupJoins.forEach( consumer ); + } + } + else { tableGroup.visitTableGroupJoins( consumer ); } } @Override public void visitNestedTableGroupJoins(Consumer consumer) { - if ( tableGroup != null ) { + if ( tableGroup == null ) { + if ( nestedTableGroupJoins != null ) { + nestedTableGroupJoins.forEach( consumer ); + } + } + else { tableGroup.visitNestedTableGroupJoins( consumer ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java index e3e24b2e8d..846a0a60d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/TableGroupJoinProducer.java @@ -24,6 +24,8 @@ public interface TableGroupJoinProducer extends TableGroupProducer { SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup); + boolean isSimpleJoinPredicate(Predicate predicate); + /** * Create a TableGroupJoin as defined for this producer */ diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java index 6d52cacd6e..05308b45a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/AbstractEmbeddableInitializer.java @@ -19,6 +19,7 @@ import org.hibernate.metamodel.mapping.EmbeddableMappingType; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; +import org.hibernate.metamodel.mapping.VirtualModelPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.EmbeddableRepresentationStrategy; import org.hibernate.property.access.spi.PropertyAccess; @@ -34,8 +35,6 @@ import org.hibernate.sql.results.graph.collection.CollectionInitializer; import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.internal.NullValueAssembler; import org.hibernate.sql.results.jdbc.spi.RowProcessingState; -import org.hibernate.type.descriptor.java.JavaType; -import org.hibernate.type.descriptor.java.spi.EntityJavaTypeDescriptor; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; @@ -231,17 +230,12 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA return; } - // Special handling for non-aggregated attributes which use the actual entity instance as container, - // which we access through the fetch parent access. - // If this model part is an identifier, we must construct the instance as this is called during resolveKey - final EmbeddableMappingType embeddableTypeDescriptor = embedded.getEmbeddableTypeDescriptor(); - final JavaType embeddableJtd = embeddableTypeDescriptor.getMappedJavaTypeDescriptor(); - - if ( fetchParentAccess != null && - embeddableJtd.getJavaTypeClass().isAssignableFrom( fetchParentAccess.getInitializedPart().getJavaTypeDescriptor().getJavaTypeClass() ) - && embeddableJtd instanceof EntityJavaTypeDescriptor - && !( embedded instanceof CompositeIdentifierMapping ) - && !EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( embedded.getFetchableName() ) ) { + // Virtual model parts use the owning entity as container which the fetch parent access provides. + // For an identifier or foreign key this is called during the resolveKey phase of the fetch parent, + // so we can't use the fetch parent access in that case. + if ( fetchParentAccess != null && embedded instanceof VirtualModelPart + && !EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( embedded.getFetchableName() ) + && !ForeignKeyDescriptor.PART_NAME.equals( navigablePath.getUnaliasedLocalName() ) ) { fetchParentAccess.resolveInstance( processingState ); compositeInstance = fetchParentAccess.getInitializedInstance(); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/EmbeddableValuedFetchable.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/EmbeddableValuedFetchable.java index c99402e1c4..1670f0dab5 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/EmbeddableValuedFetchable.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/EmbeddableValuedFetchable.java @@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.embeddable; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.tree.from.TableGroup; +import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.results.graph.Fetchable; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; @@ -19,4 +20,9 @@ public interface EmbeddableValuedFetchable extends EmbeddableValuedModelPart, Fe default SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) { return SqlAstJoinType.LEFT; } + + @Override + default boolean isSimpleJoinPredicate(Predicate predicate) { + return predicate == null; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java index 1b668d7fee..d7c4bd7e0e 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/embeddable/internal/EmbeddableFetchImpl.java @@ -112,8 +112,11 @@ public class EmbeddableFetchImpl extends AbstractFetchParent implements Embeddab public NavigablePath resolveNavigablePath(Fetchable fetchable) { if ( fetchable instanceof TableGroupProducer ) { for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) { - if ( tableGroupJoin.getJoinedGroup().isFetched() && tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) { - return tableGroupJoin.getNavigablePath(); + final NavigablePath navigablePath = tableGroupJoin.getNavigablePath(); + if ( tableGroupJoin.getJoinedGroup().isFetched() + && fetchable.getFetchableName().equals( navigablePath.getUnaliasedLocalName() ) + && tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) { + return navigablePath; } } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultImpl.java index fb993df04c..c917c0b22f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityResultImpl.java @@ -59,8 +59,11 @@ public class EntityResultImpl extends AbstractEntityResultGraphNode implements E if ( fetchable instanceof TableGroupProducer && !getNavigablePath().getUnaliasedLocalName().equals( getNavigablePath().getLocalName() ) ) { for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) { - if ( tableGroupJoin.getJoinedGroup().isFetched() && tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) { - return tableGroupJoin.getNavigablePath(); + final NavigablePath navigablePath = tableGroupJoin.getNavigablePath(); + if ( tableGroupJoin.getJoinedGroup().isFetched() + && fetchable.getFetchableName().equals( navigablePath.getUnaliasedLocalName() ) + && tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) { + return navigablePath; } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/NaturalIdDereferenceTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/NaturalIdDereferenceTest.java similarity index 93% rename from hibernate-core/src/test/java/org/hibernate/test/hql/NaturalIdDereferenceTest.java rename to hibernate-core/src/test/java/org/hibernate/orm/test/hql/NaturalIdDereferenceTest.java index 251c55a787..11571788eb 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/hql/NaturalIdDereferenceTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/hql/NaturalIdDereferenceTest.java @@ -5,7 +5,7 @@ * See the lgpl.txt file in the root directory or . */ -package org.hibernate.test.hql; +package org.hibernate.orm.test.hql; import org.hibernate.annotations.NaturalId; import org.hibernate.query.Query; @@ -15,6 +15,7 @@ import org.hibernate.testing.jdbc.SQLStatementInspector; 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; @@ -66,9 +67,21 @@ public class NaturalIdDereferenceTest { ); } + @AfterEach + public void deleteData(SessionFactoryScope scope) { + scope.inTransaction( + session -> { + session.createQuery( "delete from BookRefRef" ).executeUpdate(); + session.createQuery( "delete from BookRef" ).executeUpdate(); + session.createQuery( "delete from Book" ).executeUpdate(); + } + ); + } + @Test public void naturalIdDereferenceTest(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r.normalBook.isbn FROM BookRef r" ); @@ -83,6 +96,7 @@ public class NaturalIdDereferenceTest { @Test public void normalIdDereferenceFromAlias(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r.normalBook.id FROM BookRef r" ); @@ -97,6 +111,7 @@ public class NaturalIdDereferenceTest { @Test public void naturalIdDereferenceFromAlias(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r.naturalBook.isbn FROM BookRef r" ); @@ -111,6 +126,7 @@ public class NaturalIdDereferenceTest { @Test public void normalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook.id FROM BookRefRef r2" ); @@ -124,6 +140,7 @@ public class NaturalIdDereferenceTest { @Test public void naturalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r2.normalBookRef.naturalBook.isbn FROM BookRefRef r2" ); @@ -142,6 +159,7 @@ public class NaturalIdDereferenceTest { @Test public void nestedNaturalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook.isbn FROM BookRefRef r2" ); @@ -159,6 +177,7 @@ public class NaturalIdDereferenceTest { @Test public void nestedNaturalIdDereferenceFromImplicitJoin2(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook.id FROM BookRefRef r2" ); @@ -172,6 +191,7 @@ public class NaturalIdDereferenceTest { @Test public void doNotDereferenceNaturalIdIfIsReferenceToPrimaryKey(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook.isbn FROM BookRefRef r2" ); @@ -185,6 +205,7 @@ public class NaturalIdDereferenceTest { @Test public void selectedEntityIsNotDereferencedForPrimaryKey(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook FROM BookRefRef r2" ); @@ -209,6 +230,7 @@ public class NaturalIdDereferenceTest { @Test public void selectedEntityIsNotDereferencedForNaturalId(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook FROM BookRefRef r2" ); @@ -228,6 +250,7 @@ public class NaturalIdDereferenceTest { @Test public void dereferenceNaturalIdInJoin(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( @@ -251,6 +274,7 @@ public class NaturalIdDereferenceTest { @Test public void dereferenceNaturalIdInJoin2(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( "SELECT b.normalBook FROM BookRefRef a " + @@ -271,6 +295,7 @@ public class NaturalIdDereferenceTest { @Test public void dereferenceNaturalIdInJoin3(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery( @@ -292,6 +317,7 @@ public class NaturalIdDereferenceTest { @Test public void dereferenceNaturalIdInJoin4(SessionFactoryScope scope) { SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); + sqlStatementInterceptor.clear(); scope.inTransaction( session -> { Query query = session.createQuery(