Skip to content

Commit 3f6145b

Browse files
committed
HHH-18932 Use target table columns for SQM joins, except for join table associations
1 parent 7d071f5 commit 3f6145b

File tree

5 files changed

+42
-7
lines changed

5 files changed

+42
-7
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,10 @@ public Cardinality getCardinality() {
918918
return cardinality;
919919
}
920920

921+
public boolean hasJoinTable() {
922+
return hasJoinTable;
923+
}
924+
921925
@Override
922926
public EntityMappingType getMappedType() {
923927
return getEntityMappingType();

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.hibernate.metamodel.mapping.ModelPart;
3939
import org.hibernate.metamodel.mapping.ModelPartContainer;
4040
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
41+
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
4142
import org.hibernate.metamodel.model.domain.BasicDomainType;
4243
import org.hibernate.metamodel.model.domain.DomainType;
4344
import org.hibernate.metamodel.model.domain.EntityDomainType;
@@ -286,7 +287,13 @@ public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
286287
* or one that has an explicit on clause predicate.
287288
*/
288289
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath, EntityAssociationMapping associationMapping) {
289-
if ( associationMapping.isFkOptimizationAllowed() && sqmPath instanceof SqmJoin<?, ?> ) {
290+
// By default, never allow the FK optimization if the path is a join, unless the association has a join table
291+
// Hibernate ORM has no way for users to refer to collection/join table rows,
292+
// so referring the columns of these rows by default when requesting FK column attributes is sensible.
293+
// Users that need to refer to the actual target table columns will have to add an explicit entity join.
294+
if ( associationMapping.isFkOptimizationAllowed()
295+
&& sqmPath instanceof SqmJoin<?, ?>
296+
&& hasJoinTable( associationMapping ) ) {
290297
final SqmJoin<?, ?> sqmJoin = (SqmJoin<?, ?>) sqmPath;
291298
switch ( sqmJoin.getSqmJoinType() ) {
292299
case LEFT:
@@ -304,6 +311,16 @@ public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath, EntityAssociat
304311
return false;
305312
}
306313

314+
private static boolean hasJoinTable(EntityAssociationMapping associationMapping) {
315+
if ( associationMapping instanceof CollectionPart ) {
316+
return !( (CollectionPart) associationMapping ).getCollectionAttribute().getCollectionDescriptor().isOneToMany();
317+
}
318+
else if ( associationMapping instanceof ToOneAttributeMapping ) {
319+
return ( (ToOneAttributeMapping) associationMapping ).hasJoinTable();
320+
}
321+
return false;
322+
}
323+
307324
private static boolean isFiltered(EntityAssociationMapping associationMapping) {
308325
final EntityMappingType entityMappingType = associationMapping.getAssociatedEntityMappingType();
309326
return !associationMapping.isFkOptimizationAllowed()

hibernate-core/src/test/java/org/hibernate/orm/test/hql/ManyToOneJoinReuseTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void joinAndImplicitPath(SessionFactoryScope scope) {
7878
query.where(
7979
cb.and(
8080
root.get( "book" ).isNotNull(),
81-
join.isNotNull()
81+
cb.fk( root.get( "book" ) ).isNotNull()
8282
)
8383
);
8484

hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/InheritanceQueryGroupByTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ private void testGroupByNotSelected(
152152
Long.class
153153
).getSingleResult();
154154
assertThat( sum ).isEqualTo( 3L );
155-
// When not selected, group by should only use the foreign key (parent_id)
156-
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 2 );
155+
// Association is joined, so every use of the join alias will make use of target table columns
156+
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 1 );
157157
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", childPropCount );
158158
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", childPropCount );
159159
} );
@@ -236,8 +236,8 @@ private void testGroupByAndOrderByNotSelected(
236236
Long.class
237237
).getSingleResult();
238238
assertThat( sum ).isEqualTo( 3L );
239-
// When not selected, group by should only use the foreign key (parent_id)
240-
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 3 );
239+
// Association is joined, so every use of the join alias will make use of target table columns
240+
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 1 );
241241
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", childPropCount );
242242
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", childPropCount );
243243
} );

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

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

5454
@Test
55-
public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) {
55+
public void testMapKeyJoinIsNotOmitted(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 collection key (relationship)
63+
statementInspector.assertNumberOfJoins( 0, 3 );
64+
}
65+
);
66+
}
67+
68+
@Test
69+
public void testMapKeyJoinIsOmitted2(SessionFactoryScope scope) {
70+
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
71+
statementInspector.clear();
72+
scope.inTransaction(
73+
s -> {
74+
s.createQuery( "select c from MapOwner as o join o.contents c where c.relationship.id is not null" ).list();
75+
statementInspector.assertExecutedCount( 1 );
6276
// Assert 2 joins, collection table and collection element. No need to join the relationship because it is not nullable
6377
statementInspector.assertNumberOfJoins( 0, 2 );
6478
}

0 commit comments

Comments
 (0)