HHH-17777 Reuse existing fetch-joins if requesting the same join type

This commit is contained in:
Marco Belladelli 2024-03-01 13:12:24 +01:00 committed by Christian Beikov
parent b3955d7d75
commit 3ce10df785
4 changed files with 66 additions and 14 deletions

View File

@ -30,6 +30,8 @@ import org.hibernate.query.sqm.tree.from.SqmRoot;
import org.jboss.logging.Logger;
import static org.hibernate.query.sqm.internal.SqmUtil.findCompatibleFetchJoin;
/**
* Specialized "intermediate" SemanticPathPart for processing domain model paths.
*
@ -191,15 +193,26 @@ public class QualifiedJoinPathConsumer implements DotIdentifierConsumer {
name,
creationState.getCreationContext().getJpaMetamodel()
);
if ( allowReuse && !isTerminal ) {
for ( SqmJoin<?, ?> sqmJoin : lhs.getSqmJoins() ) {
if ( sqmJoin.getAlias() == null && sqmJoin.getReferencedPathSource() == subPathSource ) {
return sqmJoin;
if ( allowReuse ) {
if ( !isTerminal ) {
for ( SqmJoin<?, ?> sqmJoin : lhs.getSqmJoins() ) {
if ( sqmJoin.getAlias() == null && sqmJoin.getReferencedPathSource() == subPathSource ) {
return sqmJoin;
}
}
}
else if ( fetch ) {
final SqmAttributeJoin<U, ?> compatibleFetchJoin = findCompatibleFetchJoin( lhs, subPathSource, joinType );
if ( compatibleFetchJoin != null ) {
if ( alias != null ) {
throw new IllegalStateException( "Cannot fetch the same association twice with a different alias" );
}
return compatibleFetchJoin;
}
}
}
@SuppressWarnings("unchecked")
SqmJoinable<U, ?> joinSource = (SqmJoinable<U, ?>) subPathSource;
final SqmJoinable<U, ?> joinSource = (SqmJoinable<U, ?>) subPathSource;
return createJoin( lhs, joinType, alias, fetch, isTerminal, allowReuse, creationState, joinSource );
}

View File

@ -41,6 +41,7 @@ import org.hibernate.query.spi.QueryParameterBinding;
import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.query.spi.QueryParameterImplementor;
import org.hibernate.query.sqm.NodeBuilder;
import org.hibernate.query.sqm.SqmPathSource;
import org.hibernate.query.sqm.SqmQuerySource;
import org.hibernate.query.sqm.spi.JdbcParameterBySqmParameterAccess;
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
@ -53,6 +54,7 @@ import org.hibernate.query.sqm.tree.expression.SqmAliasedNodeRef;
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
import org.hibernate.query.sqm.tree.expression.SqmTuple;
import org.hibernate.query.sqm.tree.from.SqmAttributeJoin;
import org.hibernate.query.sqm.tree.from.SqmFrom;
import org.hibernate.query.sqm.tree.from.SqmJoin;
import org.hibernate.query.sqm.tree.from.SqmQualifiedJoin;
@ -159,6 +161,32 @@ public class SqmUtil {
return false;
}
public static <T, A> SqmAttributeJoin<T, A> findCompatibleFetchJoin(
SqmFrom<?, T> sqmFrom,
SqmPathSource<A> pathSource,
SqmJoinType requestedJoinType) {
for ( final SqmJoin<T, ?> join : sqmFrom.getSqmJoins() ) {
if ( join.getReferencedPathSource() == pathSource ) {
final SqmAttributeJoin<T, ?> attributeJoin = (SqmAttributeJoin<T, ?>) join;
if ( attributeJoin.isFetched() ) {
final SqmJoinType joinType = join.getSqmJoinType();
if ( joinType != requestedJoinType ) {
throw new IllegalStateException( String.format(
"Requested join fetch with association [%s] with '%s' join type, " +
"but found existing join fetch with '%s' join type.",
pathSource.getPathName(),
requestedJoinType,
joinType
) );
}
//noinspection unchecked
return (SqmAttributeJoin<T, A>) attributeJoin;
}
}
}
return null;
}
public static Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> generateJdbcParamsXref(
DomainParameterXref domainParameterXref,
JdbcParameterBySqmParameterAccess jdbcParameterBySqmParameterAccess) {

View File

@ -61,6 +61,8 @@ import jakarta.persistence.metamodel.PluralAttribute;
import jakarta.persistence.metamodel.SetAttribute;
import jakarta.persistence.metamodel.SingularAttribute;
import static org.hibernate.query.sqm.internal.SqmUtil.findCompatibleFetchJoin;
/**
* Convenience base class for SqmFrom implementations
*
@ -670,8 +672,18 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
@Override
public <A> SqmSingularJoin<T, A> fetch(SingularAttribute<? super T, A> attribute, JoinType jt) {
final SingularPersistentAttribute<? super T, A> persistentAttribute = (SingularPersistentAttribute<? super T, A>) attribute;
final SqmAttributeJoin<T, A> compatibleFetchJoin = findCompatibleFetchJoin(
this,
persistentAttribute,
SqmJoinType.from( jt )
);
if ( compatibleFetchJoin != null ) {
return (SqmSingularJoin<T, A>) compatibleFetchJoin;
}
final SqmSingularJoin<T, A> join = buildSingularJoin(
(SingularPersistentAttribute<? super T, A>) attribute,
persistentAttribute,
SqmJoinType.from( jt ),
true
);
@ -714,6 +726,11 @@ public abstract class AbstractSqmFrom<O,T> extends AbstractSqmPath<T> implements
SqmPathSource<A> joinedPathSource,
SqmJoinType joinType,
boolean fetched) {
final SqmAttributeJoin<T, A> compatibleFetchJoin = findCompatibleFetchJoin( this, joinedPathSource, joinType );
if ( compatibleFetchJoin != null ) {
return compatibleFetchJoin;
}
final SqmAttributeJoin<T, A> sqmJoin;
if ( joinedPathSource instanceof SingularPersistentAttribute<?, ?> ) {
sqmJoin = buildSingularJoin(

View File

@ -93,15 +93,9 @@ public class OneToManyBidirectionalTest {
List<Item> items = session.createQuery(
"from Item i" +
" join fetch i.order o" +
" join fetch i.order o2", Item.class ).list();
/*
select i1_0.id, o21_0.id, o21_0.name
from Item as i1_0
inner join "Order" as o21_0 on i1_0."order_id" = o21_0.id
inner join "Order" as o1_0 on i1_0."order_id" = o1_0.id
*/
" join fetch i.order", Item.class ).list();
sqlStatementInterceptor.assertNumberOfJoins( 0, SqlAstJoinType.INNER, 2 );
sqlStatementInterceptor.assertNumberOfJoins( 0, SqlAstJoinType.INNER, 1 );
sqlStatementInterceptor.clear();
assertThat( items.size(), is( 2 ) );