Skip to content

Commit b16b891

Browse files
committed
HHH-17837 Render target-side key for explicit plural joins when needed
Also, change how we determine whether we need to use the target-side to only the strictly needed cases (non-optimizable joins, `group by` or `order by` clauses)
1 parent 21bfc5c commit b16b891

File tree

11 files changed

+92
-52
lines changed

11 files changed

+92
-52
lines changed

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/EntityAssociationMapping.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
*/
77
package org.hibernate.metamodel.mapping;
88

9+
import java.util.Set;
10+
911
import org.hibernate.sql.ast.tree.from.TableGroupJoinProducer;
1012

1113
/**
@@ -21,6 +23,8 @@ default String getFetchableName() {
2123

2224
EntityMappingType getAssociatedEntityMappingType();
2325

26+
Set<String> getTargetKeyPropertyNames();
27+
2428
/**
2529
* The model sub-part relative to the associated entity type that is the target
2630
* of this association's foreign-key

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/AbstractEntityCollectionPart.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public abstract class AbstractEntityCollectionPart implements EntityCollectionPa
6060
private final EntityMappingType associatedEntityTypeDescriptor;
6161
private final NotFoundAction notFoundAction;
6262

63-
private final Set<String> targetKeyPropertyNames;
63+
protected final Set<String> targetKeyPropertyNames;
6464

6565
public AbstractEntityCollectionPart(
6666
Nature nature,
@@ -110,10 +110,6 @@ public EntityMappingType getMappedType() {
110110
return getAssociatedEntityMappingType();
111111
}
112112

113-
protected Set<String> getTargetKeyPropertyNames() {
114-
return targetKeyPropertyNames;
115-
}
116-
117113
@Override
118114
public NavigableRole getNavigableRole() {
119115
return navigableRole;

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ManyToManyCollectionPart.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.hibernate.metamodel.mapping.internal;
88

99
import java.util.Locale;
10+
import java.util.Set;
1011
import java.util.function.Consumer;
1112

1213
import org.hibernate.annotations.NotFoundAction;
@@ -135,6 +136,11 @@ public ModelPart findSubPart(String name, EntityMappingType targetType) {
135136
return super.findSubPart( name, targetType );
136137
}
137138

139+
@Override
140+
public Set<String> getTargetKeyPropertyNames() {
141+
return targetKeyPropertyNames;
142+
}
143+
138144
@Override
139145
public <X, Y> int breakDownJdbcValues(
140146
Object domainValue,

hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,7 @@ public String getTargetKeyPropertyName() {
899899
return targetKeyPropertyName;
900900
}
901901

902+
@Override
902903
public Set<String> getTargetKeyPropertyNames() {
903904
return targetKeyPropertyNames;
904905
}

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@
2525
import org.hibernate.metamodel.MappingMetamodel;
2626
import org.hibernate.metamodel.mapping.BasicValuedMapping;
2727
import org.hibernate.metamodel.mapping.Bindable;
28+
import org.hibernate.metamodel.mapping.CollectionPart;
2829
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
2930
import org.hibernate.metamodel.mapping.EntityIdentifierMapping;
3031
import org.hibernate.metamodel.mapping.EntityMappingType;
3132
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
3233
import org.hibernate.metamodel.mapping.JdbcMapping;
33-
import org.hibernate.metamodel.mapping.ManagedMappingType;
3434
import org.hibernate.metamodel.mapping.MappingModelExpressible;
35+
import org.hibernate.metamodel.mapping.ModelPart;
3536
import org.hibernate.metamodel.mapping.ModelPartContainer;
3637
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
3738
import org.hibernate.query.IllegalQueryOperationException;
@@ -46,6 +47,7 @@
4647
import org.hibernate.query.sqm.SqmQuerySource;
4748
import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess;
4849
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
50+
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
4951
import org.hibernate.query.sqm.tree.SqmDmlStatement;
5052
import org.hibernate.query.sqm.tree.SqmJoinType;
5153
import org.hibernate.query.sqm.tree.SqmStatement;
@@ -60,11 +62,13 @@
6062
import org.hibernate.query.sqm.tree.from.SqmJoin;
6163
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
6264
import org.hibernate.query.sqm.tree.from.SqmRoot;
65+
import org.hibernate.query.sqm.tree.select.SqmQueryPart;
6366
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
6467
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
6568
import org.hibernate.query.sqm.tree.select.SqmSelection;
6669
import org.hibernate.query.sqm.tree.select.SqmSortSpecification;
6770
import org.hibernate.spi.NavigablePath;
71+
import org.hibernate.sql.ast.Clause;
6872
import org.hibernate.sql.ast.SqlTreeCreationException;
6973
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
7074
import org.hibernate.sql.ast.tree.from.TableGroup;
@@ -79,6 +83,7 @@
7983
import org.hibernate.type.internal.ConvertedBasicTypeImpl;
8084
import org.hibernate.type.spi.TypeConfiguration;
8185

86+
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
8287
import static org.hibernate.query.sqm.tree.jpa.ParameterCollector.collectParameters;
8388

8489
/**
@@ -132,13 +137,56 @@ public static IllegalQueryOperationException expectingNonSelect(SqmStatement<?>
132137
}
133138

134139
/**
135-
* Utility that returns {@code true} if the specified {@link SqmPath sqmPath} should be
136-
* dereferenced using the target table mapping, i.e. when the path's lhs is an explicit join.
140+
* Utility that returns the entity association target's mapping type if the specified {@code sqmPath} should
141+
* be dereferenced using the target table, i.e. when the path's lhs is an explicit join that is used in the
142+
* group by clause, or defaults to the provided {@code modelPartContainer} otherwise.
137143
*/
138-
public static boolean needsTargetTableMapping(SqmPath<?> sqmPath, ModelPartContainer modelPartContainer) {
139-
return modelPartContainer.getPartMappingType() != modelPartContainer
140-
&& sqmPath.getLhs() instanceof SqmFrom<?, ?>
141-
&& modelPartContainer.getPartMappingType() instanceof ManagedMappingType;
144+
public static ModelPartContainer getTargetMappingIfNeeded(
145+
SqmPath<?> sqmPath,
146+
ModelPartContainer modelPartContainer,
147+
SqmToSqlAstConverter sqlAstCreationState) {
148+
final SqmQueryPart<?> queryPart = sqlAstCreationState.getCurrentSqmQueryPart();
149+
if ( queryPart != null ) {
150+
// We only need to do this for queries
151+
final Clause clause = sqlAstCreationState.getCurrentClauseStack().getCurrent();
152+
if ( clause != Clause.FROM && modelPartContainer.getPartMappingType() != modelPartContainer && sqmPath.getLhs() instanceof SqmFrom<?, ?> ) {
153+
final ModelPart modelPart;
154+
if ( modelPartContainer instanceof PluralAttributeMapping ) {
155+
modelPart = getCollectionPart(
156+
(PluralAttributeMapping) modelPartContainer,
157+
castNonNull( sqmPath.getNavigablePath().getParent() )
158+
);
159+
}
160+
else {
161+
modelPart = modelPartContainer;
162+
}
163+
if ( modelPart instanceof EntityAssociationMapping ) {
164+
final EntityAssociationMapping association = (EntityAssociationMapping) modelPart;
165+
// If the path is one of the association's target key properties,
166+
// we need to render the target side if in group/order by
167+
if ( association.getTargetKeyPropertyNames().contains( sqmPath.getReferencedPathSource().getPathName() )
168+
&& ( clause == Clause.GROUP || clause == Clause.ORDER
169+
|| !isFkOptimizationAllowed( sqmPath.getLhs() )
170+
|| queryPart.getFirstQuerySpec().groupByClauseContains( sqmPath.getNavigablePath() ) ) ) {
171+
return association.getAssociatedEntityMappingType();
172+
}
173+
}
174+
}
175+
}
176+
return modelPartContainer;
177+
}
178+
179+
private static CollectionPart getCollectionPart(PluralAttributeMapping attribute, NavigablePath path) {
180+
final CollectionPart.Nature nature = CollectionPart.Nature.fromNameExact( path.getLocalName() );
181+
if ( nature != null ) {
182+
switch ( nature ) {
183+
case ELEMENT:
184+
return attribute.getElementDescriptor();
185+
case INDEX:
186+
return attribute.getIndexDescriptor();
187+
}
188+
}
189+
return null;
142190
}
143191

144192
/**

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/BasicValuedPathInterpretation.java

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.hibernate.metamodel.MappingMetamodel;
1414
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
1515
import org.hibernate.metamodel.mapping.EntityMappingType;
16-
import org.hibernate.metamodel.mapping.ManagedMappingType;
1716
import org.hibernate.metamodel.mapping.MappingType;
1817
import org.hibernate.metamodel.mapping.ModelPart;
1918
import org.hibernate.metamodel.mapping.ModelPartContainer;
@@ -35,7 +34,7 @@
3534
import org.hibernate.sql.ast.tree.update.Assignable;
3635

3736
import static org.hibernate.internal.util.NullnessUtil.castNonNull;
38-
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
37+
import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded;
3938

4039
/**
4140
* @author Steve Ebersole
@@ -82,20 +81,12 @@ public static <T> BasicValuedPathInterpretation<T> from(
8281
}
8382
}
8483

85-
final ModelPart modelPart;
86-
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
87-
// We have to make sure we render the column of the target table
88-
modelPart = ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
89-
sqmPath.getReferencedPathSource().getPathName(),
90-
treatTarget
91-
);
92-
}
93-
else {
94-
modelPart = modelPartContainer.findSubPart(
95-
sqmPath.getReferencedPathSource().getPathName(),
96-
treatTarget
97-
);
98-
}
84+
// Use the target type to find the sub part if needed, otherwise just use the container
85+
final ModelPart modelPart = getTargetMappingIfNeeded(
86+
sqmPath,
87+
modelPartContainer,
88+
sqlAstCreationState
89+
).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget );
9990

10091
if ( modelPart == null ) {
10192
if ( jpaQueryComplianceEnabled ) {

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EmbeddableValuedPathInterpretation.java

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import org.hibernate.metamodel.MappingMetamodel;
1414
import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart;
1515
import org.hibernate.metamodel.mapping.EntityMappingType;
16-
import org.hibernate.metamodel.mapping.ManagedMappingType;
1716
import org.hibernate.metamodel.mapping.ModelPartContainer;
1817
import org.hibernate.metamodel.model.domain.EntityDomainType;
1918
import org.hibernate.query.sqm.sql.SqmToSqlAstConverter;
@@ -29,7 +28,7 @@
2928
import org.hibernate.sql.ast.tree.from.TableGroup;
3029
import org.hibernate.sql.ast.tree.update.Assignable;
3130

32-
import static org.hibernate.query.sqm.internal.SqmUtil.needsTargetTableMapping;
31+
import static org.hibernate.query.sqm.internal.SqmUtil.getTargetMappingIfNeeded;
3332

3433
/**
3534
* @author Steve Ebersole
@@ -65,20 +64,12 @@ else if ( lhs.getNodeType() instanceof EntityDomainType ) {
6564
}
6665

6766
final ModelPartContainer modelPartContainer = tableGroup.getModelPart();
68-
final EmbeddableValuedModelPart mapping;
69-
if ( needsTargetTableMapping( sqmPath, modelPartContainer ) ) {
70-
// We have to make sure we render the column of the target table
71-
mapping = (EmbeddableValuedModelPart) ( (ManagedMappingType) modelPartContainer.getPartMappingType() ).findSubPart(
72-
sqmPath.getReferencedPathSource().getPathName(),
73-
treatTarget
74-
);
75-
}
76-
else {
77-
mapping = (EmbeddableValuedModelPart) modelPartContainer.findSubPart(
78-
sqmPath.getReferencedPathSource().getPathName(),
79-
treatTarget
80-
);
81-
}
67+
// Use the target type to find the sub part if needed, otherwise just use the container
68+
final EmbeddableValuedModelPart mapping = (EmbeddableValuedModelPart) getTargetMappingIfNeeded(
69+
sqmPath,
70+
modelPartContainer,
71+
sqlAstCreationState
72+
).findSubPart( sqmPath.getReferencedPathSource().getPathName(), treatTarget );
8273

8374
return new EmbeddableValuedPathInterpretation<>(
8475
mapping.toSqlExpression(

hibernate-core/src/main/java/org/hibernate/query/sqm/sql/internal/EntityValuedPathInterpretation.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ private static <T> EntityValuedPathInterpretation<T> from(
162162
// we try to make use of it and the FK model part if possible based on the inferred mapping
163163
if ( mapping instanceof EntityAssociationMapping ) {
164164
final EntityAssociationMapping associationMapping = (EntityAssociationMapping) mapping;
165-
final ModelPart keyTargetMatchPart = associationMapping.getKeyTargetMatchPart();
165+
final ModelPart keyTargetMatchPart = associationMapping.getForeignKeyDescriptor().getPart(
166+
associationMapping.getSideNature()
167+
);
166168

167169
if ( associationMapping.isFkOptimizationAllowed() ) {
168170
final boolean forceUsingForeignKeyAssociationSidePart;

hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmQuerySpec.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,8 +628,8 @@ public void appendHqlString(StringBuilder sb) {
628628
}
629629

630630
public boolean groupByClauseContains(NavigablePath path) {
631-
for ( SqmExpression<?> expression : groupByClauseExpressions ) {
632-
if ( expression instanceof SqmPath && ( (SqmPath<?>) expression ).getNavigablePath() == path ) {
631+
for ( final SqmExpression<?> expression : groupByClauseExpressions ) {
632+
if ( expression instanceof SqmPath && ( (SqmPath<?>) expression ).getNavigablePath().isParentOrEqual( path ) ) {
633633
return true;
634634
}
635635
}

hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ql/MapIssueTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
5252
}
5353

5454
@Test
55-
public void testMapKeyJoinIsIncluded(SessionFactoryScope scope) {
55+
public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) {
5656
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
5757
statementInspector.clear();
5858
scope.inTransaction(
5959
s -> {
6060
s.createQuery( "select c from MapOwner as o join o.contents c join c.relationship r where r.id is not null" ).list();
6161
statementInspector.assertExecutedCount( 1 );
62-
// Assert 3 joins, collection table, collection element and relationship
63-
statementInspector.assertNumberOfJoins( 0, 3 );
62+
// Assert 2 joins, collection table and collection element. No need to join the relationship because it is not nullable
63+
statementInspector.assertNumberOfJoins( 0, 2 );
6464
}
6565
);
6666
}

hibernate-core/src/test/java/org/hibernate/orm/test/sql/exec/onetoone/bidirectional/EntityWithBidirectionalAssociationsOneOfWhichIsAJoinTableTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,9 @@ public void testHqlSelectSon(SessionFactoryScope scope) {
152152
.getSingleResult();
153153

154154
statementInspector.assertExecutedCount( 2 );
155-
// The join to the target table PARENT for Male#parent is added since it's explicitly joined in HQL
156-
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 2 );
155+
// The join to the target table PARENT for Male#parent is avoided,
156+
// because the FK in the collection table is not-null and data from the target table is not needed
157+
statementInspector.assertNumberOfOccurrenceInQuery( 0, "join", 1 );
157158
statementInspector.assertNumberOfOccurrenceInQuery( 1, "join", 3 );
158159
assertThat( son.getParent(), CoreMatchers.notNullValue() );
159160

0 commit comments

Comments
 (0)