Skip to content

Commit 301c38b

Browse files
committed
HHH-18932 Use target table columns for SQM joins, except for join table associations
1 parent 58d2239 commit 301c38b

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
@@ -914,6 +914,10 @@ public Cardinality getCardinality() {
914914
return cardinality;
915915
}
916916

917+
public boolean hasJoinTable() {
918+
return hasJoinTable;
919+
}
920+
917921
@Override
918922
public EntityMappingType getMappedType() {
919923
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
@@ -34,6 +34,7 @@
3434
import org.hibernate.metamodel.mapping.ModelPart;
3535
import org.hibernate.metamodel.mapping.ModelPartContainer;
3636
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
37+
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
3738
import org.hibernate.metamodel.model.domain.BasicDomainType;
3839
import org.hibernate.metamodel.model.domain.EntityDomainType;
3940
import org.hibernate.metamodel.model.domain.IdentifiableDomainType;
@@ -257,7 +258,13 @@ public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath) {
257258
* or one that has an explicit on clause predicate.
258259
*/
259260
public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath, EntityAssociationMapping associationMapping) {
260-
if ( associationMapping.isFkOptimizationAllowed() && sqmPath instanceof SqmJoin<?, ?> sqmJoin ) {
261+
// By default, never allow the FK optimization if the path is a join, unless the association has a join table
262+
// Hibernate ORM has no way for users to refer to collection/join table rows,
263+
// so referring the columns of these rows by default when requesting FK column attributes is sensible.
264+
// Users that need to refer to the actual target table columns will have to add an explicit entity join.
265+
if ( associationMapping.isFkOptimizationAllowed()
266+
&& sqmPath instanceof SqmJoin<?, ?> sqmJoin
267+
&& hasJoinTable( associationMapping ) ) {
261268
switch ( sqmJoin.getSqmJoinType() ) {
262269
case LEFT:
263270
if ( isFiltered( associationMapping ) ) {
@@ -273,6 +280,16 @@ public static boolean isFkOptimizationAllowed(SqmPath<?> sqmPath, EntityAssociat
273280
return false;
274281
}
275282

283+
private static boolean hasJoinTable(EntityAssociationMapping associationMapping) {
284+
if ( associationMapping instanceof CollectionPart collectionPart ) {
285+
return !collectionPart.getCollectionAttribute().getCollectionDescriptor().isOneToMany();
286+
}
287+
else if ( associationMapping instanceof ToOneAttributeMapping toOneAttributeMapping ) {
288+
return toOneAttributeMapping.hasJoinTable();
289+
}
290+
return false;
291+
}
292+
276293
private static boolean isFiltered(EntityAssociationMapping associationMapping) {
277294
final EntityMappingType entityMappingType = associationMapping.getAssociatedEntityMappingType();
278295
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
@@ -75,7 +75,7 @@ public void joinAndImplicitPath(SessionFactoryScope scope) {
7575
query.where(
7676
cb.and(
7777
root.get( "book" ).isNotNull(),
78-
join.isNotNull()
78+
cb.fk( root.get( "book" ) ).isNotNull()
7979
)
8080
);
8181

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
@@ -150,8 +150,8 @@ private void testGroupByNotSelected(
150150
Long.class
151151
).getSingleResult();
152152
assertThat( sum ).isEqualTo( 3L );
153-
// When not selected, group by should only use the foreign key (parent_id)
154-
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 2 );
153+
// Association is joined, so every use of the join alias will make use of target table columns
154+
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 1 );
155155
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", childPropCount );
156156
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", childPropCount );
157157
} );
@@ -234,8 +234,8 @@ private void testGroupByAndOrderByNotSelected(
234234
Long.class
235235
).getSingleResult();
236236
assertThat( sum ).isEqualTo( 3L );
237-
// When not selected, group by should only use the foreign key (parent_id)
238-
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 3 );
237+
// Association is joined, so every use of the join alias will make use of target table columns
238+
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, parentFkName, 1 );
239239
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_one_col", childPropCount );
240240
statementInspector.assertNumberOfOccurrenceInQueryNoSpace( 0, "child_two_col", childPropCount );
241241
} );

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
@@ -50,13 +50,27 @@ public void testOnlyCollectionTableJoined(SessionFactoryScope scope) {
5050
}
5151

5252
@Test
53-
public void testMapKeyJoinIsOmitted(SessionFactoryScope scope) {
53+
public void testMapKeyJoinIsNotOmitted(SessionFactoryScope scope) {
5454
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
5555
statementInspector.clear();
5656
scope.inTransaction(
5757
s -> {
5858
s.createQuery( "select c from MapOwner as o join o.contents c join c.relationship r where r.id is not null" ).list();
5959
statementInspector.assertExecutedCount( 1 );
60+
// Assert 3 joins, collection table, collection element and collection key (relationship)
61+
statementInspector.assertNumberOfJoins( 0, 3 );
62+
}
63+
);
64+
}
65+
66+
@Test
67+
public void testMapKeyJoinIsOmitted2(SessionFactoryScope scope) {
68+
SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
69+
statementInspector.clear();
70+
scope.inTransaction(
71+
s -> {
72+
s.createQuery( "select c from MapOwner as o join o.contents c where c.relationship.id is not null" ).list();
73+
statementInspector.assertExecutedCount( 1 );
6074
// Assert 2 joins, collection table and collection element. No need to join the relationship because it is not nullable
6175
statementInspector.assertNumberOfJoins( 0, 2 );
6276
}

0 commit comments

Comments
 (0)