100100import org .hibernate .type .EntityType ;
101101import org .hibernate .type .Type ;
102102
103- import static java .util .Objects .requireNonNullElse ;
104-
105103/**
106104 * @author Steve Ebersole
107105 */
@@ -1221,9 +1219,8 @@ public static class EntityB {
12211219
12221220 having the left join we don't want to add an extra implicit join that will be translated into an SQL inner join (see HHH-15342)
12231221 */
1224- if ( ( ( hasNotFoundAction () && doesNotExistATableGroupJoin ( parentTableGroup , fetchablePath ) )
1225- || ( fetchTiming == FetchTiming .IMMEDIATE && selected ) ) ) {
1226- final TableGroup tableGroup = determineTableGroup (
1222+ if ( fetchTiming == FetchTiming .IMMEDIATE && selected || hasNotFoundAction () ) {
1223+ final TableGroup tableGroup = determineTableGroupForFetch (
12271224 fetchablePath ,
12281225 fetchParent ,
12291226 parentTableGroup ,
@@ -1332,58 +1329,66 @@ public static class EntityB {
13321329 );
13331330 }
13341331
1335- private boolean doesNotExistATableGroupJoin (TableGroup parentTableGroup , NavigablePath fetchablePath ) {
1336- for ( TableGroupJoin tableGroupJoin : parentTableGroup .getTableGroupJoins () ) {
1337- if ( tableGroupJoin .getNavigablePath ().pathsMatch ( fetchablePath ) ) {
1338- return false ;
1339- }
1340- }
1341- return true ;
1342- }
1343-
1344- private TableGroup determineTableGroup (NavigablePath fetchablePath , FetchParent fetchParent , TableGroup parentTableGroup , String resultVariable , FromClauseAccess fromClauseAccess , DomainResultCreationState creationState ) {
1345- final TableGroup tableGroup ;
1332+ private TableGroup determineTableGroupForFetch (
1333+ NavigablePath fetchablePath ,
1334+ FetchParent fetchParent ,
1335+ TableGroup parentTableGroup ,
1336+ String resultVariable ,
1337+ FromClauseAccess fromClauseAccess ,
1338+ DomainResultCreationState creationState ) {
1339+ final SqlAstJoinType joinType ;
13461340 if ( fetchParent instanceof EntityResultJoinedSubclassImpl
13471341 && ( (EntityPersister ) fetchParent .getReferencedModePart () ).findDeclaredAttributeMapping ( getPartName () ) == null ) {
1348- final TableGroupJoin tableGroupJoin = createTableGroupJoin (
1349- fetchablePath ,
1350- parentTableGroup ,
1351- resultVariable ,
1352- getJoinType ( fetchablePath , parentTableGroup ),
1353- true ,
1354- false ,
1355- creationState .getSqlAstCreationState ()
1356- );
1357- if ( hasNotFoundAction () ) {
1358- tableGroupJoin .setImplicit ();
1359- }
1360-
1361- parentTableGroup .addTableGroupJoin ( tableGroupJoin );
1362- tableGroup = tableGroupJoin .getJoinedGroup ();
1363- fromClauseAccess .registerTableGroup ( fetchablePath , tableGroup );
1342+ joinType = getJoinTypeForFetch ( fetchablePath , parentTableGroup );
13641343 }
13651344 else {
1366- tableGroup = fromClauseAccess .resolveTableGroup (
1367- fetchablePath ,
1368- np -> {
1369- final TableGroupJoin tableGroupJoin = createTableGroupJoin (
1370- fetchablePath ,
1371- parentTableGroup ,
1372- resultVariable ,
1373- getDefaultSqlAstJoinType ( parentTableGroup ),
1374- true ,
1375- false ,
1376- creationState .getSqlAstCreationState ()
1377- );
1378- if ( hasNotFoundAction () ) {
1379- tableGroupJoin .setImplicit ();
1345+ joinType = null ;
1346+ }
1347+ return fromClauseAccess .resolveTableGroup (
1348+ fetchablePath ,
1349+ np -> {
1350+ // Try to reuse an existing join if possible,
1351+ // and note that we prefer reusing an inner over a left join,
1352+ // because a left join might stay uninitialized if unused
1353+ TableGroup leftJoined = null ;
1354+ for ( TableGroupJoin tableGroupJoin : parentTableGroup .getTableGroupJoins () ) {
1355+ switch ( tableGroupJoin .getJoinType () ) {
1356+ case INNER :
1357+ // If this is an inner joins, it's fine if the paths match
1358+ // Since this inner join would filter the parent row anyway,
1359+ // it makes no sense to add another left join for this association
1360+ if ( tableGroupJoin .getNavigablePath ().pathsMatch ( np ) ) {
1361+ return tableGroupJoin .getJoinedGroup ();
1362+ }
1363+ break ;
1364+ case LEFT :
1365+ // For an existing left join on the other hand which is row preserving,
1366+ // it is important to check if the predicate has user defined bits in it
1367+ // and only if it doesn't, we can reuse the join
1368+ if ( tableGroupJoin .getNavigablePath ().pathsMatch ( np )
1369+ && isSimpleJoinPredicate ( tableGroupJoin .getPredicate () ) ) {
1370+ leftJoined = tableGroupJoin .getJoinedGroup ();
1371+ }
13801372 }
1381- parentTableGroup .addTableGroupJoin ( tableGroupJoin );
1382- return tableGroupJoin .getJoinedGroup ();
13831373 }
1384- );
1385- }
1386- return tableGroup ;
1374+
1375+ if ( leftJoined != null ) {
1376+ return leftJoined ;
1377+ }
1378+
1379+ final TableGroupJoin tableGroupJoin = createTableGroupJoin (
1380+ fetchablePath ,
1381+ parentTableGroup ,
1382+ resultVariable ,
1383+ joinType ,
1384+ true ,
1385+ false ,
1386+ creationState .getSqlAstCreationState ()
1387+ );
1388+ parentTableGroup .addTableGroupJoin ( tableGroupJoin );
1389+ return tableGroupJoin .getJoinedGroup ();
1390+ }
1391+ );
13871392 }
13881393
13891394 private boolean isSelectByUniqueKey (ForeignKeyDescriptor .Nature side ) {
@@ -1531,7 +1536,9 @@ else if ( parentTableGroup.getModelPart() instanceof CollectionPart ) {
15311536
15321537 @ Override
15331538 public boolean isSimpleJoinPredicate (Predicate predicate ) {
1534- return foreignKeyDescriptor .isSimpleJoinPredicate ( predicate );
1539+ // Since the table group is lazy, the initial predicate is null,
1540+ // but if we get null here, we can safely assume this will be a simple join predicate
1541+ return predicate == null || foreignKeyDescriptor .isSimpleJoinPredicate ( predicate );
15351542 }
15361543
15371544 @ Override
@@ -1555,7 +1562,18 @@ public TableGroupJoin createTableGroupJoin(
15551562 // This is vital for the map key property check that comes next
15561563 assert !( lhs instanceof PluralTableGroup );
15571564
1558- final SqlAstJoinType joinType = requireNonNullElse ( requestedJoinType , SqlAstJoinType .INNER );
1565+ final SqlAstJoinType joinType ;
1566+ if ( requestedJoinType == null ) {
1567+ if ( fetched ) {
1568+ joinType = getDefaultSqlAstJoinType ( lhs );
1569+ }
1570+ else {
1571+ joinType = SqlAstJoinType .INNER ;
1572+ }
1573+ }
1574+ else {
1575+ joinType = requestedJoinType ;
1576+ }
15591577
15601578 // If a parent is a collection part, there is no custom predicate and the join is INNER or LEFT
15611579 // we check if this attribute is the map key property to reuse the existing index table group
@@ -1800,13 +1818,13 @@ private void initializeIfNeeded(TableGroup lhs, SqlAstJoinType sqlAstJoinType, T
18001818 }
18011819 }
18021820
1803- private SqlAstJoinType getJoinType (NavigablePath navigablePath , TableGroup tableGroup ) {
1821+ private SqlAstJoinType getJoinTypeForFetch (NavigablePath navigablePath , TableGroup tableGroup ) {
18041822 for ( TableGroupJoin tableGroupJoin : tableGroup .getTableGroupJoins () ) {
18051823 if ( tableGroupJoin .getNavigablePath ().equals ( navigablePath ) ) {
18061824 return tableGroupJoin .getJoinType ();
18071825 }
18081826 }
1809- return getDefaultSqlAstJoinType ( tableGroup ) ;
1827+ return null ;
18101828 }
18111829
18121830 public TableGroup createTableGroupInternal (
0 commit comments