Cleanup support for virtual embeddable model parts and reuse existing compatible joins for implicit joins

This commit is contained in:
Christian Beikov 2021-12-21 10:47:56 +01:00
parent d5d350e5e7
commit 39484b160d
24 changed files with 543 additions and 111 deletions

View File

@ -39,10 +39,10 @@ import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.Restrictable; import org.hibernate.metamodel.mapping.Restrictable;
import org.hibernate.metamodel.mapping.internal.EmbeddedAttributeMapping; 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.internal.SimpleForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment; import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.query.ComparisonOperator; import org.hibernate.query.ComparisonOperator;
@ -340,7 +340,7 @@ public class LoaderSelectBuilder {
} }
for ( ModelPart restrictedPart : restrictedParts ) { for ( ModelPart restrictedPart : restrictedParts ) {
if ( restrictedPart instanceof ForeignKeyDescriptor || restrictedPart instanceof NonAggregatedIdentifierMappingImpl ) { if ( restrictedPart instanceof ForeignKeyDescriptor || restrictedPart instanceof NonAggregatedIdentifierMapping ) {
return true; return true;
} }
} }

View File

@ -7,6 +7,7 @@
package org.hibernate.metamodel.mapping; package org.hibernate.metamodel.mapping;
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer; 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.Fetchable;
import org.hibernate.sql.results.graph.FetchableContainer; import org.hibernate.sql.results.graph.FetchableContainer;
@ -26,4 +27,9 @@ public interface DiscriminatedAssociationModelPart extends Fetchable, FetchableC
EntityMappingType resolveDiscriminatorValue(Object discriminatorValue); EntityMappingType resolveDiscriminatorValue(Object discriminatorValue);
Object resolveDiscriminatorForEntityType(EntityMappingType entityMappingType); Object resolveDiscriminatorForEntityType(EntityMappingType entityMappingType);
@Override
default boolean isSimpleJoinPredicate(Predicate predicate) {
return predicate == null;
}
} }

View File

@ -116,6 +116,8 @@ public interface ForeignKeyDescriptor extends VirtualModelPart, ValueMapping {
SqlExpressionResolver sqlExpressionResolver, SqlExpressionResolver sqlExpressionResolver,
SqlAstCreationContext creationContext); SqlAstCreationContext creationContext);
boolean isSimpleJoinPredicate(Predicate predicate);
@Override @Override
default String getPartName() { default String getPartName() {
return PART_NAME; return PART_NAME;

View File

@ -27,7 +27,7 @@ import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
* @see jakarta.persistence.IdClass * @see jakarta.persistence.IdClass
* @see jakarta.persistence.MapsId * @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 * The virtual-id representation of this id mapping
*/ */

View File

@ -89,7 +89,7 @@ public class EmbeddedAttributeMapping
stateArrayPosition, stateArrayPosition,
tableExpression, tableExpression,
attributeMetadataAccess, attributeMetadataAccess,
getPropertyAccess(parentInjectionAttributeName, embeddableMappingType), getPropertyAccess( parentInjectionAttributeName, embeddableMappingType ),
mappedFetchTiming, mappedFetchTiming,
mappedFetchStyle, mappedFetchStyle,
embeddableMappingType, embeddableMappingType,
@ -132,7 +132,7 @@ public class EmbeddedAttributeMapping
} }
// Constructor is only used for creating the inverse attribute mapping // Constructor is only used for creating the inverse attribute mapping
private EmbeddedAttributeMapping( EmbeddedAttributeMapping(
ManagedMappingType keyDeclaringType, ManagedMappingType keyDeclaringType,
TableGroupProducer declaringTableGroupProducer, TableGroupProducer declaringTableGroupProducer,
SelectableMappings selectableMappings, SelectableMappings selectableMappings,
@ -161,29 +161,6 @@ public class EmbeddedAttributeMapping
this.parentInjectionAttributePropertyAccess = null; 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 @Override
public EmbeddableMappingType getMappedType() { public EmbeddableMappingType getMappedType() {
return getEmbeddableTypeDescriptor(); return getEmbeddableTypeDescriptor();

View File

@ -36,6 +36,7 @@ import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlAstCreationContext; import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference; 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.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupJoin; import org.hibernate.sql.ast.tree.from.TableGroupJoin;
import org.hibernate.sql.ast.tree.from.TableGroupProducer; import org.hibernate.sql.ast.tree.from.TableGroupProducer;
@ -115,7 +116,7 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
this.targetSide = original.targetSide; this.targetSide = original.targetSide;
this.keySide = new EmbeddedForeignKeyDescriptorSide( this.keySide = new EmbeddedForeignKeyDescriptorSide(
Nature.KEY, Nature.KEY,
EmbeddedAttributeMapping.createInverseModelPart( MappingModelCreationHelper.createInverseModelPart(
original.targetSide.getModelPart(), original.targetSide.getModelPart(),
keyDeclaringType, keyDeclaringType,
keyDeclaringTableGroupProducer, keyDeclaringTableGroupProducer,
@ -377,6 +378,84 @@ public class EmbeddedForeignKeyDescriptor implements ForeignKeyDescriptor {
return getPredicate( targetSideReference, keySideReference, creationContext, targetSelectableMappings, keySelectableMappings ); 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<Predicate> 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( private Predicate getPredicate(
TableReference lhs, TableReference lhs,
TableReference rhs, TableReference rhs,

View File

@ -37,6 +37,7 @@ import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectableMapping;
import org.hibernate.metamodel.mapping.VirtualModelPart;
import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -168,7 +169,14 @@ public class EntityCollectionPart
final CompositeType compositeType; final CompositeType compositeType;
if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded() if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded()
&& compositeType.getPropertyNames().length == 1 ) { && compositeType.getPropertyNames().length == 1 ) {
this.targetKeyPropertyNames = Collections.singleton( compositeType.getPropertyNames()[0] ); final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
ToOneAttributeMapping.addPrefixedPropertyNames(
targetKeyPropertyNames,
compositeType.getPropertyNames()[0],
compositeType.getSubtypes()[0],
creationProcess.getCreationContext().getSessionFactory()
);
this.targetKeyPropertyNames = targetKeyPropertyNames;
} }
else { else {
final String mapsIdAttributeName; final String mapsIdAttributeName;
@ -327,6 +335,11 @@ public class EntityCollectionPart
return SqlAstJoinType.INNER; return SqlAstJoinType.INNER;
} }
@Override
public boolean isSimpleJoinPredicate(Predicate predicate) {
return fkDescriptor.isSimpleJoinPredicate( predicate );
}
@Override @Override
public Nature getNature() { public Nature getNature() {
return nature; 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, // 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 // to preserve the cardinality. Also, the OneToManyTableGroup has no reference to the parent table group
if ( !collectionDescriptor.isOneToMany() && targetKeyPropertyNames.contains( name ) ) { if ( !collectionDescriptor.isOneToMany() && targetKeyPropertyNames.contains( name ) ) {
if ( fkDescriptor.getTargetPart() instanceof NonAggregatedIdentifierMappingImpl ) {
return ( (ModelPartContainer) fkDescriptor.getKeyPart() ).findSubPart( name, targetType );
}
if ( fkTargetModelPart instanceof ToOneAttributeMapping ) { if ( fkTargetModelPart instanceof ToOneAttributeMapping ) {
return fkTargetModelPart; 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 ); return EntityValuedFetchable.super.findSubPart( name, targetType );
} }

View File

@ -61,7 +61,6 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.GeneratedValueResolver; import org.hibernate.metamodel.mapping.GeneratedValueResolver;
import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.MappingModelCreationLogger;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer; import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.NonTransientException; 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.SelectableMappings;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadata; import org.hibernate.metamodel.mapping.StateArrayContributorMetadata;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; 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.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.metamodel.spi.RuntimeModelCreationContext;
@ -341,20 +341,40 @@ public class MappingModelCreationHelper {
attrType, attrType,
tableExpression, tableExpression,
rootTableKeyColumnNames, rootTableKeyColumnNames,
attributeMappingType -> new EmbeddedAttributeMapping( attributeMappingType -> {
attrName, if ( component.isEmbedded() ) {
declaringType.getNavigableRole().append( attrName ), return new VirtualEmbeddedAttributeMapping(
stateArrayPosition, attrName,
tableExpression, declaringType.getNavigableRole().append( attrName ),
attributeMetadataAccess, stateArrayPosition,
component.getParentProperty(), tableExpression,
FetchTiming.IMMEDIATE, attributeMetadataAccess,
FetchStyle.JOIN, component.getParentProperty(),
attributeMappingType, FetchTiming.IMMEDIATE,
declaringType, FetchStyle.JOIN,
propertyAccess, attributeMappingType,
bootProperty.getValueGenerationStrategy() 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 creationProcess
); );
@ -1172,7 +1192,7 @@ public class MappingModelCreationHelper {
if ( inverse ) { if ( inverse ) {
return new EmbeddedForeignKeyDescriptor( return new EmbeddedForeignKeyDescriptor(
embeddableValuedModelPart, embeddableValuedModelPart,
EmbeddedAttributeMapping.createInverseModelPart( createInverseModelPart(
embeddableValuedModelPart, embeddableValuedModelPart,
keyDeclaringType, keyDeclaringType,
keyDeclaringTableGroupProducer, keyDeclaringTableGroupProducer,
@ -1189,7 +1209,7 @@ public class MappingModelCreationHelper {
} }
else { else {
return new EmbeddedForeignKeyDescriptor( return new EmbeddedForeignKeyDescriptor(
EmbeddedAttributeMapping.createInverseModelPart( createInverseModelPart(
embeddableValuedModelPart, embeddableValuedModelPart,
keyDeclaringType, keyDeclaringType,
keyDeclaringTableGroupProducer, 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") @SuppressWarnings("rawtypes")
private static class CollectionMappingTypeImpl implements CollectionMappingType { private static class CollectionMappingTypeImpl implements CollectionMappingType {
private final JavaType collectionJtd; private final JavaType collectionJtd;

View File

@ -517,6 +517,11 @@ public class PluralAttributeMappingImpl
return SqlAstJoinType.LEFT; return SqlAstJoinType.LEFT;
} }
@Override
public boolean isSimpleJoinPredicate(Predicate predicate) {
return fkDescriptor.isSimpleJoinPredicate( predicate );
}
@Override @Override
public TableGroupJoin createTableGroupJoin( public TableGroupJoin createTableGroupJoin(
NavigablePath navigablePath, NavigablePath navigablePath,

View File

@ -37,6 +37,7 @@ import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection; import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.ColumnReference; 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.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupProducer; import org.hibernate.sql.ast.tree.from.TableGroupProducer;
import org.hibernate.sql.ast.tree.from.TableReference; 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) { protected TableReference getTableReference(TableGroup lhs, TableGroup tableGroup, String table) {
final NavigablePath navigablePath = lhs.getNavigablePath().append( getTargetPart().getFetchableName() ); final NavigablePath navigablePath = lhs.getNavigablePath().append( getTargetPart().getFetchableName() );
if ( lhs.getPrimaryTableReference().getTableReference( navigablePath, table ) != null ) { if ( lhs.getPrimaryTableReference().getTableReference( navigablePath, table ) != null ) {

View File

@ -15,7 +15,6 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -30,7 +29,6 @@ import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property; import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.ToOne; import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value; import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.AssociationKey; 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.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.SelectableConsumer; import org.hibernate.metamodel.mapping.SelectableConsumer;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess; import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.mapping.VirtualModelPart;
import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.AbstractEntityPersister;
@ -96,6 +95,7 @@ import org.hibernate.tuple.IdentifierProperty;
import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.tuple.entity.EntityMetamodel;
import org.hibernate.type.ComponentType; import org.hibernate.type.ComponentType;
import org.hibernate.type.CompositeType; import org.hibernate.type.CompositeType;
import org.hibernate.type.EmbeddedComponentType;
import org.hibernate.type.EntityType; import org.hibernate.type.EntityType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -440,8 +440,15 @@ public class ToOneAttributeMapping
final CompositeType compositeType; final CompositeType compositeType;
if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded() if ( propertyType.isComponentType() && ( compositeType = (CompositeType) propertyType ).isEmbedded()
&& compositeType.getPropertyNames().length == 1 ) { && compositeType.getPropertyNames().length == 1 ) {
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
this.targetKeyPropertyName = compositeType.getPropertyNames()[0]; this.targetKeyPropertyName = compositeType.getPropertyNames()[0];
this.targetKeyPropertyNames = Collections.singleton( targetKeyPropertyName ); addPrefixedPropertyNames(
targetKeyPropertyNames,
targetKeyPropertyName,
compositeType.getSubtypes()[0],
declaringEntityPersister.getFactory()
);
this.targetKeyPropertyNames = targetKeyPropertyNames;
} }
else { else {
this.targetKeyPropertyName = referencedPropertyName; this.targetKeyPropertyName = referencedPropertyName;
@ -551,6 +558,9 @@ public class ToOneAttributeMapping
if ( entityType.isReferenceToPrimaryKey() ) { if ( entityType.isReferenceToPrimaryKey() ) {
propertyName = entityType.getAssociatedEntityPersister( factory ).getIdentifierPropertyName(); propertyName = entityType.getAssociatedEntityPersister( factory ).getIdentifierPropertyName();
} }
else if ( identifierOrUniqueKeyType instanceof EmbeddedComponentType ) {
propertyName = null;
}
else { else {
propertyName = entityType.getRHSUniqueKeyPropertyName(); 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 // 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 // This way, we don't have to register table groups the target entity type
if ( canUseParentTableGroup && targetKeyPropertyNames.contains( name ) ) { if ( canUseParentTableGroup && targetKeyPropertyNames.contains( name ) ) {
final ModelPart fkSideModelPart; final ModelPart fkPart;
final ModelPart fkTargetModelPart;
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) { if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
fkTargetModelPart = foreignKeyDescriptor.getTargetPart(); fkPart = foreignKeyDescriptor.getKeyPart();
fkSideModelPart = foreignKeyDescriptor.getKeyPart();
} }
else { else {
fkTargetModelPart = foreignKeyDescriptor.getKeyPart(); fkPart = foreignKeyDescriptor.getTargetPart();
fkSideModelPart = foreignKeyDescriptor.getTargetPart();
} }
if ( fkTargetModelPart instanceof NonAggregatedIdentifierMappingImpl ) { if ( fkPart instanceof EmbeddableValuedModelPart && fkPart instanceof VirtualModelPart ) {
return ( (ModelPartContainer) fkSideModelPart ).findSubPart( name, targetType ); return ( (ModelPartContainer) fkPart ).findSubPart( name, targetType );
} }
return fkSideModelPart; return fkPart;
} }
return EntityValuedFetchable.super.findSubPart( name, targetType ); return EntityValuedFetchable.super.findSubPart( name, targetType );
} }
@ -1223,6 +1230,11 @@ public class ToOneAttributeMapping
} }
} }
@Override
public boolean isSimpleJoinPredicate(Predicate predicate) {
return foreignKeyDescriptor.isSimpleJoinPredicate( predicate );
}
@Override @Override
public int getNumberOfFetchables() { public int getNumberOfFetchables() {
return getEntityMappingType().getNumberOfFetchables(); return getEntityMappingType().getNumberOfFetchables();

View File

@ -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()
);
}
}

View File

@ -167,6 +167,7 @@ import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ManagedMappingType; import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.NaturalIdMapping; import org.hibernate.metamodel.mapping.NaturalIdMapping;
import org.hibernate.metamodel.mapping.NonAggregatedIdentifierMapping;
import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.Queryable; import org.hibernate.metamodel.mapping.Queryable;
import org.hibernate.metamodel.mapping.SelectableConsumer; 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.InFlightEntityMappingType;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper; import org.hibernate.metamodel.mapping.internal.MappingModelCreationHelper;
import org.hibernate.metamodel.mapping.internal.MappingModelCreationProcess; 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.mapping.internal.SimpleNaturalIdMapping;
import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.metamodel.spi.EntityInstantiator; import org.hibernate.metamodel.spi.EntityInstantiator;
@ -5023,7 +5023,7 @@ public abstract class AbstractEntityPersister
baseValueType = (ManagedMappingType) attributeMapping.getMappedType(); baseValueType = (ManagedMappingType) attributeMapping.getMappedType();
} }
} }
else if ( identifierMapping instanceof NonAggregatedIdentifierMappingImpl ) { else if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) {
final EmbeddedAttributeMapping embeddedAttributeMapping = (EmbeddedAttributeMapping) findAttributeMapping( NavigableRole.IDENTIFIER_MAPPER_PROPERTY ); final EmbeddedAttributeMapping embeddedAttributeMapping = (EmbeddedAttributeMapping) findAttributeMapping( NavigableRole.IDENTIFIER_MAPPER_PROPERTY );
final AttributeMapping mapping = embeddedAttributeMapping.getMappedType() final AttributeMapping mapping = embeddedAttributeMapping.getMappedType()
.findAttributeMapping( basePropertyName ); .findAttributeMapping( basePropertyName );
@ -6386,8 +6386,8 @@ public abstract class AbstractEntityPersister
} }
private ModelPart getIdentifierModelPart(String name, EntityMappingType treatTargetType) { private ModelPart getIdentifierModelPart(String name, EntityMappingType treatTargetType) {
if ( identifierMapping instanceof NonAggregatedIdentifierMappingImpl ) { if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) {
final ModelPart subPart = ( (NonAggregatedIdentifierMappingImpl) identifierMapping ).findSubPart( final ModelPart subPart = ( (NonAggregatedIdentifierMapping) identifierMapping ).findSubPart(
name, name,
treatTargetType treatTargetType
); );

View File

@ -28,8 +28,8 @@ import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ModelPart; 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.BasicValuedCollectionPart;
import org.hibernate.metamodel.mapping.internal.NonAggregatedIdentifierMappingImpl;
import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping; import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping;
import org.hibernate.query.EntityIdentifierNavigablePath; import org.hibernate.query.EntityIdentifierNavigablePath;
import org.hibernate.query.NavigablePath; import org.hibernate.query.NavigablePath;
@ -428,7 +428,7 @@ public class DomainResultCreationStateImpl
if ( fetchableContainer instanceof EntityValuedModelPart ) { if ( fetchableContainer instanceof EntityValuedModelPart ) {
final EntityValuedModelPart entityValuedFetchable = (EntityValuedModelPart) fetchableContainer; final EntityValuedModelPart entityValuedFetchable = (EntityValuedModelPart) fetchableContainer;
final EntityIdentifierMapping identifierMapping = entityValuedFetchable.getEntityMappingType().getIdentifierMapping(); final EntityIdentifierMapping identifierMapping = entityValuedFetchable.getEntityMappingType().getIdentifierMapping();
final boolean idClass = identifierMapping instanceof NonAggregatedIdentifierMappingImpl; final boolean idClass = identifierMapping instanceof NonAggregatedIdentifierMapping;
final String identifierAttributeName = attributeName( identifierMapping ); final String identifierAttributeName = attributeName( identifierMapping );
if ( idClass ) { if ( idClass ) {
final Map.Entry<String, NavigablePath> oldEntry = relativePathStack.getCurrent(); final Map.Entry<String, NavigablePath> oldEntry = relativePathStack.getCurrent();

View File

@ -2842,28 +2842,39 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
querySpec.getFromClause().addRoot( tableGroup ); querySpec.getFromClause().addRoot( tableGroup );
} }
else { else {
final TableGroupJoin tableGroupJoin = joinProducer.createTableGroupJoin( // Check if we can reuse a table group join of the parent
joinedPath.getNavigablePath(), final TableGroup compatibleTableGroup = findCompatibleJoinedGroup(
actualParentTableGroup, actualParentTableGroup,
null, joinProducer,
defaultSqlAstJoinType, defaultSqlAstJoinType
false,
false,
this
); );
// Implicit joins in the ON clause of attribute joins need to be added as nested table group joins if ( compatibleTableGroup == null ) {
// We don't have to do that for entity joins etc. as these do not have an inherent dependency on the lhs. final TableGroupJoin tableGroupJoin = joinProducer.createTableGroupJoin(
// We can just add the implicit join before the currently processing join joinedPath.getNavigablePath(),
// See consumeEntityJoin for details actualParentTableGroup,
final boolean nested = currentClauseStack.getCurrent() == Clause.FROM null,
&& currentlyProcessingJoin instanceof SqmAttributeJoin<?, ?>; defaultSqlAstJoinType,
if ( nested ) { false,
actualParentTableGroup.addNestedTableGroupJoin( tableGroupJoin ); 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 { else {
actualParentTableGroup.addTableGroupJoin( tableGroupJoin ); tableGroup = compatibleTableGroup;
} }
tableGroup = tableGroupJoin.getJoinedGroup();
} }
fromClauseIndex.register( joinedPath, tableGroup ); fromClauseIndex.register( joinedPath, tableGroup );
@ -2875,6 +2886,31 @@ public abstract class BaseSqmToSqlAstConverter<T extends Statement> extends Base
return tableGroup; 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) { private void registerPluralTableGroupParts(TableGroup tableGroup) {
if ( tableGroup instanceof PluralTableGroup ) { if ( tableGroup instanceof PluralTableGroup ) {
final PluralTableGroup pluralTableGroup = (PluralTableGroup) tableGroup; final PluralTableGroup pluralTableGroup = (PluralTableGroup) tableGroup;

View File

@ -7,11 +7,10 @@
package org.hibernate.query.sqm.sql.internal; package org.hibernate.query.sqm.sql.internal;
import org.hibernate.metamodel.mapping.ModelPart; 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.NavigablePath;
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter; import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
import org.hibernate.query.sqm.tree.domain.NonAggregatedCompositeSimplePath; 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.SqlAstWalker;
import org.hibernate.sql.ast.tree.expression.SqlTuple; import org.hibernate.sql.ast.tree.expression.SqlTuple;
import org.hibernate.sql.ast.tree.expression.SqlTupleContainer; import org.hibernate.sql.ast.tree.expression.SqlTupleContainer;
@ -31,7 +30,7 @@ public class NonAggregatedCompositeValuedPathInterpretation<T>
final TableGroup tableGroup = sqlAstCreationState final TableGroup tableGroup = sqlAstCreationState
.getFromClauseAccess() .getFromClauseAccess()
.findTableGroup( sqmPath.getLhs().getNavigablePath() ); .findTableGroup( sqmPath.getLhs().getNavigablePath() );
final NonAggregatedIdentifierMappingImpl mapping = (NonAggregatedIdentifierMappingImpl) tableGroup.getModelPart() final NonAggregatedIdentifierMapping mapping = (NonAggregatedIdentifierMapping) tableGroup.getModelPart()
.findSubPart( sqmPath.getReferencedPathSource().getPathName(), null ); .findSubPart( sqmPath.getReferencedPathSource().getPathName(), null );
return new NonAggregatedCompositeValuedPathInterpretation<>( return new NonAggregatedCompositeValuedPathInterpretation<>(

View File

@ -3912,6 +3912,16 @@ public abstract class AbstractSqlAstTranslator<T extends JdbcOperation> implemen
tableGroupJoinCollector 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<TableGroupJoin> tableGroupJoinCollector) { protected void renderTableGroupJoin(TableGroupJoin tableGroupJoin, List<TableGroupJoin> tableGroupJoinCollector) {

View File

@ -6,6 +6,7 @@
*/ */
package org.hibernate.sql.ast.tree.from; package org.hibernate.sql.ast.tree.from;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
@ -36,6 +37,8 @@ public class LazyTableGroup extends DelegatingTableGroup {
private final Supplier<TableGroup> tableGroupSupplier; private final Supplier<TableGroup> tableGroupSupplier;
private final TableGroup parentTableGroup; private final TableGroup parentTableGroup;
private final BiPredicate<NavigablePath, String> navigablePathChecker; private final BiPredicate<NavigablePath, String> navigablePathChecker;
private List<TableGroupJoin> tableGroupJoins;
private List<TableGroupJoin> nestedTableGroupJoins;
private Consumer<TableGroup> tableGroupConsumer; private Consumer<TableGroup> tableGroupConsumer;
private TableGroup tableGroup; private TableGroup tableGroup;
@ -72,6 +75,18 @@ public class LazyTableGroup extends DelegatingTableGroup {
} }
tableGroup = tableGroupSupplier.get(); 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 ) { if ( tableGroupConsumer != null ) {
tableGroupConsumer.accept( tableGroup ); tableGroupConsumer.accept( tableGroup );
tableGroupConsumer = null; tableGroupConsumer = null;
@ -102,24 +117,70 @@ public class LazyTableGroup extends DelegatingTableGroup {
@Override @Override
public List<TableGroupJoin> getTableGroupJoins() { public List<TableGroupJoin> getTableGroupJoins() {
return tableGroup == null ? Collections.emptyList() : tableGroup.getTableGroupJoins(); if ( tableGroup == null ) {
return nestedTableGroupJoins == null ? Collections.emptyList() : nestedTableGroupJoins;
}
else {
return tableGroup.getTableGroupJoins();
}
} }
@Override @Override
public List<TableGroupJoin> getNestedTableGroupJoins() { public List<TableGroupJoin> 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 @Override
public void visitTableGroupJoins(Consumer<TableGroupJoin> consumer) { public void visitTableGroupJoins(Consumer<TableGroupJoin> consumer) {
if ( tableGroup != null ) { if ( tableGroup == null ) {
if ( tableGroupJoins != null ) {
tableGroupJoins.forEach( consumer );
}
}
else {
tableGroup.visitTableGroupJoins( consumer ); tableGroup.visitTableGroupJoins( consumer );
} }
} }
@Override @Override
public void visitNestedTableGroupJoins(Consumer<TableGroupJoin> consumer) { public void visitNestedTableGroupJoins(Consumer<TableGroupJoin> consumer) {
if ( tableGroup != null ) { if ( tableGroup == null ) {
if ( nestedTableGroupJoins != null ) {
nestedTableGroupJoins.forEach( consumer );
}
}
else {
tableGroup.visitNestedTableGroupJoins( consumer ); tableGroup.visitNestedTableGroupJoins( consumer );
} }
} }

View File

@ -24,6 +24,8 @@ public interface TableGroupJoinProducer extends TableGroupProducer {
SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup); SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup);
boolean isSimpleJoinPredicate(Predicate predicate);
/** /**
* Create a TableGroupJoin as defined for this producer * Create a TableGroupJoin as defined for this producer
*/ */

View File

@ -19,6 +19,7 @@ import org.hibernate.metamodel.mapping.EmbeddableMappingType;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
import org.hibernate.metamodel.mapping.EntityIdentifierMapping; import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.VirtualModelPart;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.metamodel.EmbeddableRepresentationStrategy; import org.hibernate.metamodel.EmbeddableRepresentationStrategy;
import org.hibernate.property.access.spi.PropertyAccess; 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.graph.entity.EntityInitializer;
import org.hibernate.sql.results.internal.NullValueAssembler; import org.hibernate.sql.results.internal.NullValueAssembler;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState; 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.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
@ -231,17 +230,12 @@ public abstract class AbstractEmbeddableInitializer extends AbstractFetchParentA
return; return;
} }
// Special handling for non-aggregated attributes which use the actual entity instance as container, // Virtual model parts use the owning entity as container which the fetch parent access provides.
// which we access through the fetch parent access. // For an identifier or foreign key this is called during the resolveKey phase of the fetch parent,
// If this model part is an identifier, we must construct the instance as this is called during resolveKey // so we can't use the fetch parent access in that case.
final EmbeddableMappingType embeddableTypeDescriptor = embedded.getEmbeddableTypeDescriptor(); if ( fetchParentAccess != null && embedded instanceof VirtualModelPart
final JavaType<?> embeddableJtd = embeddableTypeDescriptor.getMappedJavaTypeDescriptor(); && !EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( embedded.getFetchableName() )
&& !ForeignKeyDescriptor.PART_NAME.equals( navigablePath.getUnaliasedLocalName() ) ) {
if ( fetchParentAccess != null &&
embeddableJtd.getJavaTypeClass().isAssignableFrom( fetchParentAccess.getInitializedPart().getJavaTypeDescriptor().getJavaTypeClass() )
&& embeddableJtd instanceof EntityJavaTypeDescriptor<?>
&& !( embedded instanceof CompositeIdentifierMapping )
&& !EntityIdentifierMapping.ROLE_LOCAL_NAME.equals( embedded.getFetchableName() ) ) {
fetchParentAccess.resolveInstance( processingState ); fetchParentAccess.resolveInstance( processingState );
compositeInstance = fetchParentAccess.getInitializedInstance(); compositeInstance = fetchParentAccess.getInitializedInstance();
} }

View File

@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.embeddable;
import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.tree.from.TableGroup; 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.sql.results.graph.Fetchable;
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
@ -19,4 +20,9 @@ public interface EmbeddableValuedFetchable extends EmbeddableValuedModelPart, Fe
default SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) { default SqlAstJoinType getDefaultSqlAstJoinType(TableGroup parentTableGroup) {
return SqlAstJoinType.LEFT; return SqlAstJoinType.LEFT;
} }
@Override
default boolean isSimpleJoinPredicate(Predicate predicate) {
return predicate == null;
}
} }

View File

@ -112,8 +112,11 @@ public class EmbeddableFetchImpl extends AbstractFetchParent implements Embeddab
public NavigablePath resolveNavigablePath(Fetchable fetchable) { public NavigablePath resolveNavigablePath(Fetchable fetchable) {
if ( fetchable instanceof TableGroupProducer ) { if ( fetchable instanceof TableGroupProducer ) {
for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) { for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) {
if ( tableGroupJoin.getJoinedGroup().isFetched() && tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) { final NavigablePath navigablePath = tableGroupJoin.getNavigablePath();
return tableGroupJoin.getNavigablePath(); if ( tableGroupJoin.getJoinedGroup().isFetched()
&& fetchable.getFetchableName().equals( navigablePath.getUnaliasedLocalName() )
&& tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) {
return navigablePath;
} }
} }
} }

View File

@ -59,8 +59,11 @@ public class EntityResultImpl extends AbstractEntityResultGraphNode implements E
if ( fetchable instanceof TableGroupProducer && if ( fetchable instanceof TableGroupProducer &&
!getNavigablePath().getUnaliasedLocalName().equals( getNavigablePath().getLocalName() ) ) { !getNavigablePath().getUnaliasedLocalName().equals( getNavigablePath().getLocalName() ) ) {
for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) { for ( TableGroupJoin tableGroupJoin : tableGroup.getTableGroupJoins() ) {
if ( tableGroupJoin.getJoinedGroup().isFetched() && tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) { final NavigablePath navigablePath = tableGroupJoin.getNavigablePath();
return tableGroupJoin.getNavigablePath(); if ( tableGroupJoin.getJoinedGroup().isFetched()
&& fetchable.getFetchableName().equals( navigablePath.getUnaliasedLocalName() )
&& tableGroupJoin.getJoinedGroup().getModelPart() == fetchable ) {
return navigablePath;
} }
} }
} }

View File

@ -5,7 +5,7 @@
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/ */
package org.hibernate.test.hql; package org.hibernate.orm.test.hql;
import org.hibernate.annotations.NaturalId; import org.hibernate.annotations.NaturalId;
import org.hibernate.query.Query; 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.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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 @Test
public void naturalIdDereferenceTest(SessionFactoryScope scope) { public void naturalIdDereferenceTest(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r.normalBook.isbn FROM BookRef r" ); Query query = session.createQuery( "SELECT r.normalBook.isbn FROM BookRef r" );
@ -83,6 +96,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void normalIdDereferenceFromAlias(SessionFactoryScope scope) { public void normalIdDereferenceFromAlias(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r.normalBook.id FROM BookRef r" ); Query query = session.createQuery( "SELECT r.normalBook.id FROM BookRef r" );
@ -97,6 +111,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void naturalIdDereferenceFromAlias(SessionFactoryScope scope) { public void naturalIdDereferenceFromAlias(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r.naturalBook.isbn FROM BookRef r" ); Query query = session.createQuery( "SELECT r.naturalBook.isbn FROM BookRef r" );
@ -111,6 +126,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void normalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) { public void normalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook.id FROM BookRefRef r2" ); Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook.id FROM BookRefRef r2" );
@ -124,6 +140,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void naturalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) { public void naturalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r2.normalBookRef.naturalBook.isbn FROM BookRefRef r2" ); Query query = session.createQuery( "SELECT r2.normalBookRef.naturalBook.isbn FROM BookRefRef r2" );
@ -142,6 +159,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void nestedNaturalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) { public void nestedNaturalIdDereferenceFromImplicitJoin(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook.isbn FROM BookRefRef r2" ); Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook.isbn FROM BookRefRef r2" );
@ -159,6 +177,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void nestedNaturalIdDereferenceFromImplicitJoin2(SessionFactoryScope scope) { public void nestedNaturalIdDereferenceFromImplicitJoin2(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook.id FROM BookRefRef r2" ); Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook.id FROM BookRefRef r2" );
@ -172,6 +191,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void doNotDereferenceNaturalIdIfIsReferenceToPrimaryKey(SessionFactoryScope scope) { public void doNotDereferenceNaturalIdIfIsReferenceToPrimaryKey(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook.isbn FROM BookRefRef r2" ); Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook.isbn FROM BookRefRef r2" );
@ -185,6 +205,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void selectedEntityIsNotDereferencedForPrimaryKey(SessionFactoryScope scope) { public void selectedEntityIsNotDereferencedForPrimaryKey(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook FROM BookRefRef r2" ); Query query = session.createQuery( "SELECT r2.normalBookRef.normalBook FROM BookRefRef r2" );
@ -209,6 +230,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void selectedEntityIsNotDereferencedForNaturalId(SessionFactoryScope scope) { public void selectedEntityIsNotDereferencedForNaturalId(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook FROM BookRefRef r2" ); Query query = session.createQuery( "SELECT r2.naturalBookRef.naturalBook FROM BookRefRef r2" );
@ -228,6 +250,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void dereferenceNaturalIdInJoin(SessionFactoryScope scope) { public void dereferenceNaturalIdInJoin(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( Query query = session.createQuery(
@ -251,6 +274,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void dereferenceNaturalIdInJoin2(SessionFactoryScope scope) { public void dereferenceNaturalIdInJoin2(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( "SELECT b.normalBook FROM BookRefRef a " + Query query = session.createQuery( "SELECT b.normalBook FROM BookRefRef a " +
@ -271,6 +295,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void dereferenceNaturalIdInJoin3(SessionFactoryScope scope) { public void dereferenceNaturalIdInJoin3(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( Query query = session.createQuery(
@ -292,6 +317,7 @@ public class NaturalIdDereferenceTest {
@Test @Test
public void dereferenceNaturalIdInJoin4(SessionFactoryScope scope) { public void dereferenceNaturalIdInJoin4(SessionFactoryScope scope) {
SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector(); SQLStatementInspector sqlStatementInterceptor = (SQLStatementInspector) scope.getStatementInspector();
sqlStatementInterceptor.clear();
scope.inTransaction( scope.inTransaction(
session -> { session -> {
Query query = session.createQuery( Query query = session.createQuery(