continued work on circular fetch detection. still need to work through the cases involving embeddables "in between"

This commit is contained in:
Steve Ebersole 2020-01-27 21:15:04 -06:00 committed by Andrea Boriero
parent 00b5a700eb
commit 3222b52ab2
24 changed files with 513 additions and 205 deletions

View File

@ -113,6 +113,8 @@ Given this model, we have the following mappings from modelPart to "identifying
* `Order#lineItems#{element}` -> `lines.order_id`
Once we find a circularity we should build the `BiDirectionalFetch` reference pointing to the
Initializer for the "parent parent path". See `RowProcessingState#.resolveInitializer`
@ -123,7 +125,8 @@ Hibernate needs to handle circularity in a fetch-graph. E.g.:
select o
from Order o
join fetch o.lineItems l
join fetch l.order
join fetch l.order o2
join fetch o2.lineItems
```
Here, the join fetch of `l.order` is circular, meaning we do not want to render a join in the SQL for it
@ -131,4 +134,75 @@ because it is already part of the from-clause via `Order o`.
Recognizing circularity needs to happen in a number of mapping scenarios and I believe the conditions vary
depending on the type of mapping involved (one-to-one, many-to-one, many-to-many). Ideally we can find commonality
and handle these conditions uniformly.
and handle these conditions uniformly.
== with embeddables
```
@Entity
@Table(name="root")
class RootEntity {
...
@Embedded
IntermediateComponent intermediateComponent;
}
@Embeddable
class IntermediateComponent {
...
@OneToMany( mappedBy = "rootEntity" )
Set<LeafEntity> leaves
}
@Entity
@Table(name="leaf")
class LeafEntity {
...
@ManyToOne
@JoinColumn(name="root_id)
RootEntity rootEntity;
}
```
Given this model, we have the following mappings from modelPart to "identifying columns":
* `RootEntity#intermediateComponent#leaves -> `leaf.root_id`
* `RootEntity#intermediateComponent#leaves -> `leaf.root_id`
*
* `RootEntity#intermediateComponent#leaves#{element}
* `Order#lineItems#{element}` -> `lines.order_id`
class Order {
@OneToMany(mappedBy="order")
List<LineItem> lineItems;
}
```
------------
"orders"
------------
id INTEGER
name VARCHAR
------------
"order_items"
------------
orders_id
items_id
------------
"items"
------------
id
qty
```

View File

@ -24,6 +24,7 @@ import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.loader.ast.spi.Loadable;
import org.hibernate.loader.ast.spi.Loader;
import org.hibernate.metamodel.mapping.BasicValuedModelPart;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.ModelPart;
@ -383,9 +384,20 @@ public class LoaderSelectBuilder {
final List<Fetch> fetches = new ArrayList<>();
final Consumer<Fetchable> processor = fetchable -> {
final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
final NavigablePath fetchablePath;
final Fetchable fetchedFetchable;
if ( fetchable instanceof PluralAttributeMapping ) {
fetchablePath = fetchParent.getNavigablePath()
.append( fetchable.getFetchableName() )
.append( CollectionPart.Nature.ELEMENT.getName() );
fetchedFetchable = ( (PluralAttributeMapping) fetchable ).getElementDescriptor();
}
else {
fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
fetchedFetchable = fetchable;
}
final Fetch biDirectionalFetch = fetchable.resolveCircularFetch(
final Fetch biDirectionalFetch = fetchedFetchable.resolveCircularFetch(
fetchablePath,
fetchParent,
creationState
@ -426,7 +438,7 @@ public class LoaderSelectBuilder {
if ( ! (fetchable instanceof BasicValuedModelPart) ) {
fetchDepth++;
}
Fetch fetch = fetchable.generateFetch(
Fetch fetch = fetchedFetchable.generateFetch(
fetchParent,
fetchablePath,
fetchTiming,

View File

@ -8,8 +8,6 @@ package org.hibernate.loader.ast.internal;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
@ -18,6 +16,7 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.graph.spi.AppliedGraph;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.Limit;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.ResultListTransformer;
@ -136,6 +135,12 @@ public class LoaderSqlAstCreationState
return this;
}
@Override
public ModelPart resolveModelPart(NavigablePath navigablePath) {
// for now, let's assume that the navigable-path refers to TableGroup
return fromClauseAccess.findTableGroup( navigablePath ).getModelPart();
}
@Override
public SqlAstProcessingState getParentState() {
return null;

View File

@ -0,0 +1,22 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping;
import org.hibernate.sql.results.graph.Fetchable;
/**
* @author Steve Ebersole
*/
public interface Association extends Fetchable {
ForeignKeyDescriptor getForeignKeyDescriptor();
/**
* The column expressions that identify this association.
* Mainly used in circularity detection
*/
String[] getIdentifyingColumnExpressions();
}

View File

@ -37,8 +37,6 @@ public interface EntityValuedModelPart extends FetchableContainer {
getEntityMappingType().visitSubParts( consumer, targetType );
}
String[] getIdentifyingColumnExpressions();
@Override
default <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,

View File

@ -15,6 +15,7 @@ import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.type.ForeignKeyDirection;
/**
* @author Steve Ebersole
@ -22,6 +23,8 @@ import org.hibernate.sql.results.graph.DomainResultCreationState;
public interface ForeignKeyDescriptor extends VirtualModelPart {
String PART_NAME = "{fk}";
ForeignKeyDirection getDirection();
DomainResult createDomainResult(NavigablePath collectionPath, TableGroup tableGroup, DomainResultCreationState creationState);
Predicate generateJoinPredicate(

View File

@ -33,7 +33,6 @@ import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
@ -167,7 +166,7 @@ public class EmbeddedAttributeMapping
public Fetch resolveCircularFetch(
NavigablePath fetchablePath,
FetchParent fetchParent,
SqlAstProcessingState creationState) {
DomainResultCreationState creationState) {
// an embeddable can never be circular
return null;
}

View File

@ -6,21 +6,25 @@
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
@ -35,13 +39,15 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* @author Steve Ebersole
*/
public class EntityCollectionPart implements CollectionPart, EntityAssociationMapping, EntityValuedFetchable {
public class EntityCollectionPart
implements CollectionPart, EntityAssociationMapping, EntityValuedFetchable, Association {
private final NavigableRole navigableRole;
private final CollectionPersister collectionDescriptor;
private final Nature nature;
private final EntityMappingType entityMappingType;
private ModelPart fkTargetModelPart;
private String[] identifyingColumns;
@SuppressWarnings("WeakerAccess")
public EntityCollectionPart(
@ -69,6 +75,14 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa
else {
fkTargetModelPart = entityMappingType.findSubPart( fkTargetModelPartName, null );
}
final List<String> identifyingColumnsList = new ArrayList<>();
collectionDescriptor.getAttributeMapping().getKeyDescriptor().visitReferringColumns(
(containingTableExpression, columnExpression, jdbcMapping) -> {
identifyingColumnsList.add( containingTableExpression + "." + columnExpression );
}
);
this.identifyingColumns = identifyingColumnsList.toArray( new String[0] );
}
@ -121,7 +135,7 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa
public Fetch resolveCircularFetch(
NavigablePath fetchablePath,
FetchParent fetchParent,
SqlAstProcessingState creationState) {
DomainResultCreationState creationState) {
return null;
}
@ -192,4 +206,15 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa
public String toString() {
return "EntityCollectionPart {" + navigableRole + "}";
}
@Override
public ForeignKeyDescriptor getForeignKeyDescriptor() {
// todo (6.0) : this will not strictly work - we'd want a new ForeignKeyDescriptor that points the other direction
return collectionDescriptor.getAttributeMapping().getKeyDescriptor();
}
@Override
public String[] getIdentifyingColumnExpressions() {
return identifyingColumns;
}
}

View File

@ -9,6 +9,7 @@ package org.hibernate.metamodel.mapping.internal;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.function.Consumer;
@ -221,7 +222,22 @@ public class MappingModelCreationHelper {
String resultVariable,
DomainResultCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
final TableReference rootTableReference = tableGroup.resolveTableReference( rootTable );
final TableReference rootTableReference;
try {
rootTableReference = tableGroup.resolveTableReference( rootTable );
}
catch (Exception e) {
throw new IllegalStateException(
String.format(
Locale.ROOT,
"Could not resolve table reference `%s` relative to TableGroup `%s` related with NavigablePath `%s`",
rootTable,
tableGroup,
navigablePath
),
e
);
}
final Expression expression = expressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( rootTableReference, pkColumnName ),
@ -1282,8 +1298,7 @@ public class MappingModelCreationHelper {
CascadeStyle cascadeStyle,
MappingModelCreationProcess creationProcess) {
ToOne value = (ToOne) bootProperty.getValue();
final EntityPersister entityPersister = creationProcess.getEntityPersister(
value.getReferencedEntityName() );
final EntityPersister entityPersister = creationProcess.getEntityPersister( value.getReferencedEntityName() );
final StateArrayContributorMetadataAccess stateArrayContributorMetadataAccess = getStateArrayContributorMetadataAccess(
bootProperty,

View File

@ -515,6 +515,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
lockMode,
primaryTableReference,
sqlAliasBase,
(tableExpression) -> entityPartDescriptor.getEntityMappingType().containsTableReference( tableExpression ),
(tableExpression, tg) -> entityPartDescriptor.getEntityMappingType().createTableReferenceJoin(
tableExpression,
sqlAliasBase,
@ -585,6 +586,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
final Consumer<TableGroup> tableGroupFinalizer;
final BiFunction<String,TableGroup,TableReferenceJoin> tableReferenceJoinCreator;
final java.util.function.Predicate<String> tableReferenceJoinNameChecker;
if ( elementDescriptor instanceof EntityCollectionPart || indexDescriptor instanceof EntityCollectionPart ) {
final EntityCollectionPart entityPartDescriptor;
if ( elementDescriptor instanceof EntityCollectionPart ) {
@ -601,6 +603,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
creationContext
);
tableReferenceJoinNameChecker = mappingType::containsTableReference;
tableReferenceJoinCreator = (tableExpression, tableGroup) -> mappingType.createTableReferenceJoin(
tableExpression,
sqlAliasBase,
@ -634,6 +637,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
"element-collection cannot contain joins : " + collectionTableReference.getTableExpression() + " -> " + tableExpression
);
};
tableReferenceJoinNameChecker = s -> false;
tableGroupFinalizer = null;
}
@ -643,6 +647,7 @@ public class PluralAttributeMappingImpl extends AbstractAttributeMapping impleme
lockMode,
collectionTableReference,
sqlAliasBase,
tableReferenceJoinNameChecker,
tableReferenceJoinCreator,
creationContext.getSessionFactory()
);

View File

@ -37,11 +37,11 @@ import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.basic.BasicResult;
import org.hibernate.type.BasicType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
@ -73,6 +73,11 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
this.jdbcMapping = jdbcMapping;
}
@Override
public ForeignKeyDirection getDirection() {
return fKeyDirection;
}
@Override
public DomainResult createDomainResult(
NavigablePath collectionPath,

View File

@ -6,21 +6,23 @@
*/
package org.hibernate.metamodel.mapping.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.internal.JoinHelper;
import org.hibernate.mapping.ManyToOne;
import org.hibernate.mapping.OneToOne;
import org.hibernate.mapping.ToOne;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.EntityAssociationMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.ManagedMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.ModelPartContainer;
import org.hibernate.metamodel.mapping.StateArrayContributorMetadataAccess;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.persister.entity.EntityPersister;
@ -32,7 +34,6 @@ import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAliasStemHelper;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
import org.hibernate.sql.ast.spi.SqlExpressionResolver;
import org.hibernate.sql.ast.tree.from.StandardTableGroup;
import org.hibernate.sql.ast.tree.from.TableGroup;
@ -44,18 +45,21 @@ import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.entity.EntityFetch;
import org.hibernate.sql.results.graph.entity.EntityResultGraphNode;
import org.hibernate.sql.results.graph.entity.EntityValuedFetchable;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchDelayedImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchJoinedImpl;
import org.hibernate.sql.results.graph.entity.internal.EntityFetchSelectImpl;
import org.hibernate.sql.results.internal.domain.BiDirectionalFetchImpl;
import org.hibernate.type.ForeignKeyDirection;
/**
* @author Steve Ebersole
*/
public class SingularAssociationAttributeMapping extends AbstractSingularAttributeMapping
implements EntityValuedFetchable, EntityAssociationMapping, TableGroupJoinProducer {
implements EntityValuedFetchable, EntityAssociationMapping, Association, TableGroupJoinProducer {
public enum Cardinality {
ONE_TO_ONE,
@ -75,6 +79,11 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
private final Cardinality cardinality;
private ForeignKeyDescriptor foreignKeyDescriptor;
private String identifyingColumnsTableExpression;
private String inverseIdentifyingColumnsTableExpression;
private String[] identifyingColumns;
public SingularAssociationAttributeMapping(
@ -127,6 +136,32 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
public void setForeignKeyDescriptor(ForeignKeyDescriptor foreignKeyDescriptor) {
this.foreignKeyDescriptor = foreignKeyDescriptor;
final String identifyingColumnsTableExpression;
final String inverseColumnsTableExpression;
final List<String> identifyingColumnsList = new ArrayList<>();
if ( foreignKeyDescriptor.getDirection() == ForeignKeyDirection.TO_PARENT ) {
identifyingColumnsTableExpression = foreignKeyDescriptor.getTargetTableExpression();
inverseColumnsTableExpression = foreignKeyDescriptor.getReferringTableExpression();
foreignKeyDescriptor.visitTargetColumns(
(containingTableExpression, columnExpression, jdbcMapping) -> {
identifyingColumnsList.add( containingTableExpression + "." + columnExpression );
}
);
}
else {
identifyingColumnsTableExpression = foreignKeyDescriptor.getReferringTableExpression();
inverseColumnsTableExpression = foreignKeyDescriptor.getTargetTableExpression();
foreignKeyDescriptor.visitReferringColumns(
(containingTableExpression, columnExpression, jdbcMapping) -> {
identifyingColumnsList.add( containingTableExpression + "." + columnExpression );
}
);
}
this.identifyingColumns = identifyingColumnsList.toArray( new String[0] );
this.identifyingColumnsTableExpression = identifyingColumnsTableExpression;
this.inverseIdentifyingColumnsTableExpression = inverseColumnsTableExpression;
}
public ForeignKeyDescriptor getForeignKeyDescriptor() {
@ -152,123 +187,70 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
return navigableRole;
}
@Override
public String[] getIdentifyingColumnExpressions() {
return identifyingColumns;
}
@Override
public Fetch resolveCircularFetch(
NavigablePath fetchablePath,
FetchParent fetchParent,
SqlAstProcessingState creationState) {
// given a typical Order/LineItem model and a query like:
// select o
// from Order o
// join fetch o.lineItems l
// join fetch l.order
//
// - note : Order has a collection of LineItems which is "mapped by" LineItem#order
//
// the join-fetch for `l.order` ought point back to `o`.
//
// `o` -> Order(o)
// `l` -> Order(o).lineItems(l).{element}
// `l.order` -> Order(o).lineItems(l).{element}.order
//
// both `Order(o)` and `Order(o).lineItems(l).order` have the same identifying columns, so we know
// they are circular. So how do we resolve the columns? ...
//
// see `org.hibernate.loader.JoinWalker.isDuplicateAssociation(java.lang.String, java.lang.String[], org.hibernate.type.AssociationType)` in
// previous versions of Hibernate
//
// For `l.order` we are in SingularAssociationAttributeMapping as the Fetchable, so we have access to the FK descriptor.
// For `o` (the potential circular target reference) we need to locate the
//
//
// where `owner` is the "owner" (in the mapped-by sense) of the association. In other words it is a
// bi-directional mapping.
//
// This call is trying to generate a fetch for the NavigablePath `Person(p).address`.
// What we need to determine is whether owner is the same as address's container. This might include
// multiple parent-paths which we need to walk up to find the container (an entity of collection)
DomainResultCreationState creationState) {
// NOTE - a circular fetch reference ultimately needs 2 pieces of information:
// 1) The NavigablePath that is circular (`fetchablePath`)
// 2) The NavigablePath to the entity-valued-reference that is the "other side" of the circularity
final ModelPart parentModelPart = fetchParent.getReferencedModePart();
final NavigablePath pathToParent = fetchParent.getNavigablePath();
final NavigablePath pathToParentParent = pathToParent.getParent();
// pathToParent : org.hibernate.orm.test.annotations.embedded.EmbeddedCircularFetchTests$RootEntity(r).intermediateComponent.leaves.{element}
// pathToParentParent : org.hibernate.orm.test.annotations.embedded.EmbeddedCircularFetchTests$RootEntity(r).intermediateComponent.leaves
// attributeName : rootEntity
// referencedPropertyName : null
if ( pathToParentParent == null ) {
if ( ! Fetchable.class.isInstance( parentModelPart ) ) {
// the `fetchParent` would have to be a Fetch as well for this to be circular...
return null;
}
final TableGroup parentParentTableGroup = creationState.getSqlAstCreationState()
.getFromClauseAccess()
.findTableGroup( pathToParentParent );
final FetchParent associationParent = fetchParent.resolveContainingAssociationParent();
assert associationParent.getReferencedModePart() instanceof Association;
parentParentTableGroup.getModelPart().findContainingEntityMapping()
final Association association = (Association) associationParent.getReferencedModePart();
final ModelPartContainer parentParentPart = parentParentTableGroup.getModelPart();
final ModelPart parentPart = parentParentPart.findSubPart( pathToParent.getLocalName(), null );
if ( Arrays.equals( association.getIdentifyingColumnExpressions(), this.getIdentifyingColumnExpressions() ) ) {
// we need to determine the NavigablePath referring to the entity that the bi-dir
// fetch will "return" for its Assembler. so we walk "up" the FetchParent graph
// to find the "referenced entity" reference
if ( ! parentPart.equals( fetchParent.getReferencedModePart() ) ) {
throw new AssertionError( );
}
final EntityResultGraphNode referencedEntityReference = resolveEntityGraphNode( fetchParent );
final EntityMappingType containingEntityMapping = findContainingEntityMapping();
// find the key-columns for the `parentParentTableGroup` and see if they match the fk-target
switch ( cardinality ) {
case ONE_TO_ONE:
case LOGICAL_ONE_TO_ONE: {
if ( ! EntityValuedModelPart.class.isInstance( parentPart ) ) {
throw new IllegalStateException(
"Parent part [" + pathToParent + "] did not refer to a `EntityValuedModelPart` - " + parentPart
);
}
final EntityValuedModelPart entityValuedParentPart = (EntityValuedModelPart) parentPart;
throw new NotYetImplementedFor6Exception( getClass() );
}
case MANY_TO_ONE: {
}
default: {
throw new UnsupportedOperationException( "Unknown to-one singular attribute cardinality - " + cardinality.name() );
}
}
if ( parentPart instanceof EntityCollectionPart ) {
final EntityCollectionPart entityCollectionPart = (EntityCollectionPart) parentPart;
final String mappedBy = entityCollectionPart.getMappedBy();
if ( mappedBy.equals( getAttributeName() ) ) {
return new BiDirectionalFetchImpl(
FetchTiming.IMMEDIATE,
fetchablePath,
fetchParent,
this,
fetchParent.getNavigablePath().getParent()
if ( referencedEntityReference == null ) {
throw new HibernateException(
"Could not locate entity-valued reference for circular path `" + fetchablePath + "`"
);
}
return new BiDirectionalFetchImpl(
FetchTiming.IMMEDIATE,
fetchablePath,
fetchParent,
this,
referencedEntityReference.getNavigablePath()
);
}
else if ( parentPart instanceof EntityAssociationMapping ) {
final EntityAssociationMapping entitySubPart = (EntityAssociationMapping) parentPart;
final boolean condition1 = pathToParent.getLocalName().equals( referencedPropertyName )
&& entitySubPart.getFetchableName().equals( referencedPropertyName );
final boolean condition2 = entitySubPart.getKeyTargetMatchPart() != null
&& entitySubPart.getKeyTargetMatchPart().getPartName().equals( getAttributeName() );
return null;
}
if ( condition1 || condition2 ) {
return new BiDirectionalFetchImpl(
FetchTiming.IMMEDIATE,
fetchablePath,
fetchParent,
this,
fetchParent.getNavigablePath().getParent()
);
protected EntityResultGraphNode resolveEntityGraphNode(FetchParent fetchParent) {
FetchParent processingParent = fetchParent;
while ( processingParent != null ) {
if ( processingParent instanceof EntityResultGraphNode ) {
return (EntityResultGraphNode) processingParent;
}
if ( processingParent instanceof Fetch ) {
processingParent = ( (Fetch) processingParent ).getFetchParent();
continue;
}
processingParent = null;
}
return null;
@ -387,6 +369,7 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
lockMode,
primaryTableReference,
sqlAliasBase,
(tableExpression) -> getEntityMappingType().containsTableReference( tableExpression ),
(tableExpression, tg) -> getEntityMappingType().createTableReferenceJoin(
tableExpression,
sqlAliasBase,
@ -398,24 +381,23 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
creationContext.getSessionFactory()
);
final TableReference lhsTableReference = lhs.resolveTableReference( identifyingColumnsTableExpression );
final TableGroupJoin tableGroupJoin = new TableGroupJoin(
navigablePath,
sqlAstJoinType,
tableGroup,
null
foreignKeyDescriptor.generateJoinPredicate(
lhsTableReference,
primaryTableReference,
sqlAstJoinType,
sqlExpressionResolver,
creationContext
)
);
lhs.addTableGroupJoin( tableGroupJoin );
final Predicate predicate = foreignKeyDescriptor.generateJoinPredicate(
lhs,
tableGroup,
sqlAstJoinType,
sqlExpressionResolver,
creationContext
);
tableGroupJoin.applyPredicate( predicate );
return tableGroupJoin;
}

View File

@ -1194,6 +1194,21 @@ public abstract class AbstractEntityPersister
return sqlAliasStem;
}
@Override
public boolean containsTableReference(String tableExpression) {
if ( getRootTableName().equals( tableExpression ) ) {
return true;
}
for ( int i = 0; i < getSubclassTableSpan(); i++ ) {
if ( getSubclassTableName( i ).equals( tableExpression ) ) {
return true;
}
}
return false;
}
@Override
public String getPartName() {
return getEntityName();
@ -1251,6 +1266,7 @@ public abstract class AbstractEntityPersister
lockMode,
primaryTableReference,
sqlAliasBase,
(tableExpression) -> ArrayHelper.contains( rootTableKeyColumnNames, tableExpression ),
(tableExpression, tableGroup) -> {
for ( int i = 0; i < getSubclassTableSpan(); i++ ) {
final String subclassTableName = getSubclassTableName( i );

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.query.sqm.mutation.internal;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@ -40,6 +41,8 @@ import org.hibernate.sql.ast.tree.select.QuerySpec;
import org.hibernate.sql.ast.tree.update.Assignable;
import org.hibernate.sql.ast.tree.update.Assignment;
import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
/**
* Specialized BaseSqmToSqlAstConverter implementation used during conversion
@ -211,6 +214,11 @@ public class MultiTableSqmMutationConverter extends BaseSqmToSqlAstConverter imp
return this;
}
@Override
public List<Fetch> visitFetches(FetchParent fetchParent) {
return Collections.emptyList();
}
public void visitSelectClause(
SqmSelectClause sqmSelectClause,
QuerySpec sqlQuerySpec,

View File

@ -7,7 +7,6 @@
package org.hibernate.query.sqm.sql;
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@ -15,25 +14,29 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.dialect.function.TimestampaddFunction;
import org.hibernate.dialect.function.TimestampdiffFunction;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.mapping.AttributeMapping;
import org.hibernate.metamodel.mapping.BasicValuedMapping;
import org.hibernate.metamodel.mapping.CollectionPart;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.model.domain.AllowableFunctionReturnType;
import org.hibernate.metamodel.model.domain.AllowableParameterType;
import org.hibernate.metamodel.model.domain.EntityDomainType;
import org.hibernate.metamodel.model.domain.PluralPersistentAttribute;
import org.hibernate.metamodel.model.domain.internal.CompositeSqmPathSource;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.BinaryArithmeticOperator;
import org.hibernate.query.NavigablePath;
import org.hibernate.query.SemanticException;
import org.hibernate.query.TemporalUnit;
import org.hibernate.query.QueryLogger;
import org.hibernate.query.UnaryArithmeticOperator;
import org.hibernate.query.internal.QueryHelper;
import org.hibernate.query.spi.QueryOptions;
@ -139,6 +142,7 @@ import org.hibernate.sql.ast.tree.expression.DurationUnit;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.ExtractUnit;
import org.hibernate.sql.ast.tree.expression.Format;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.expression.Star;
import org.hibernate.sql.ast.tree.expression.TrimSpecification;
@ -162,10 +166,7 @@ import org.hibernate.sql.ast.tree.select.SelectStatement;
import org.hibernate.sql.ast.tree.select.SortSpecification;
import org.hibernate.sql.exec.internal.JdbcParameterImpl;
import org.hibernate.sql.exec.internal.JdbcParametersImpl;
import org.hibernate.sql.ast.tree.expression.JdbcParameter;
import org.hibernate.sql.exec.spi.JdbcParameters;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.logging.Logger;
@ -282,11 +283,6 @@ public abstract class BaseSqmToSqlAstConverter
return queryOptions.getLockOptions().getEffectiveLockMode( identificationVariable );
}
@Override
public List<Fetch> visitFetches(FetchParent fetchParent) {
return Collections.emptyList();
}
public QueryOptions getQueryOptions() {
return queryOptions;
}
@ -579,34 +575,131 @@ public abstract class BaseSqmToSqlAstConverter
final SqmPathSource<?> pathSource = sqmJoin.getReferencedPathSource();
final AttributeMapping attributeMapping = (AttributeMapping) lhsTableGroup.getModelPart().findSubPart(
pathSource.getPathName(),
SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this )
);
final TableGroupJoin joinedTableGroupJoin;
final TableGroup joinedTableGroup;
assert attributeMapping instanceof TableGroupJoinProducer;
final TableGroupJoin tableGroupJoin = ( (TableGroupJoinProducer) attributeMapping ).createTableGroupJoin(
sqmJoin.getNavigablePath(),
lhsTableGroup,
sqmJoin.getExplicitAlias(),
sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(),
determineLockMode( sqmJoin.getExplicitAlias() ),
sqlAliasBaseManager, getSqlExpressionResolver(),
creationContext
);
if ( pathSource instanceof PluralPersistentAttribute ) {
final ModelPart pluralPart = lhsTableGroup.getModelPart().findSubPart(
sqmJoin.getReferencedPathSource().getPathName(),
SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this )
);
lhsTableGroup.addTableGroupJoin( tableGroupJoin );
fromClauseIndex.register( sqmJoin, tableGroupJoin.getJoinedGroup() );
assert pluralPart instanceof PluralAttributeMapping;
final PluralAttributeMapping pluralAttributeMapping = (PluralAttributeMapping) pluralPart;
final NavigablePath elementPath = sqmJoin.getNavigablePath().append( CollectionPart.Nature.ELEMENT.getName() );
joinedTableGroupJoin = pluralAttributeMapping.createTableGroupJoin(
elementPath,
lhsTableGroup,
sqmJoin.getExplicitAlias(),
sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(),
determineLockMode( sqmJoin.getExplicitAlias() ),
sqlAliasBaseManager, getSqlExpressionResolver(),
creationContext
);
joinedTableGroup = joinedTableGroupJoin.getJoinedGroup();
lhsTableGroup.addTableGroupJoin( joinedTableGroupJoin );
fromClauseIndex.register( sqmJoin, joinedTableGroup );
fromClauseIndex.registerTableGroup( elementPath, joinedTableGroup );
}
else if ( pathSource instanceof EmbeddedSqmPathSource ) {
final ModelPart joinedPart = lhsTableGroup.getModelPart().findSubPart(
pathSource.getPathName(),
SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this )
);
assert joinedPart instanceof TableGroupJoinProducer;
final NavigablePath joinedPath;
final String explicitAlias = sqmJoin.getExplicitAlias();
if ( explicitAlias == null ) {
joinedPath = sqmJoin.getNavigablePath();
}
else {
joinedPath = sqmJoin.getNavigablePath().getParent().append( sqmJoin.getAttribute().getName() );
}
joinedTableGroupJoin = ( (TableGroupJoinProducer) joinedPart ).createTableGroupJoin(
joinedPath,
lhsTableGroup,
sqmJoin.getExplicitAlias(),
sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(),
determineLockMode( sqmJoin.getExplicitAlias() ),
sqlAliasBaseManager, getSqlExpressionResolver(),
creationContext
);
joinedTableGroup = joinedTableGroupJoin.getJoinedGroup();
lhsTableGroup.addTableGroupJoin( joinedTableGroupJoin );
fromClauseIndex.register( sqmJoin, joinedTableGroup );
}
else {
if ( lhsTableGroup.getModelPart() instanceof PluralAttributeMapping ) {
fromClauseIndex.register( sqmJoin, lhsTableGroup );
joinedTableGroupJoin = null;
joinedTableGroup = lhsTableGroup;
}
else {
final ModelPart joinedPart = lhsTableGroup.getModelPart().findSubPart(
pathSource.getPathName(),
SqmMappingModelHelper.resolveExplicitTreatTarget( sqmJoin, this )
);
if ( ! TableGroupJoinProducer.class.isInstance( joinedPart ) ) {
throw new HibernateException( "Expecting joined model part to implement TableGroupJoinProducer - " + joinedPart );
}
final NavigablePath joinedPath;
final String explicitAlias = sqmJoin.getExplicitAlias();
if ( explicitAlias == null ) {
joinedPath = sqmJoin.getNavigablePath();
}
else {
joinedPath = sqmJoin.getNavigablePath().getParent().append( sqmJoin.getAttribute().getName() );
}
joinedTableGroupJoin = ( (TableGroupJoinProducer) joinedPart ).createTableGroupJoin(
joinedPath,
lhsTableGroup,
sqmJoin.getExplicitAlias(),
sqmJoin.getSqmJoinType().getCorrespondingSqlJoinType(),
determineLockMode( sqmJoin.getExplicitAlias() ),
sqlAliasBaseManager,
getSqlExpressionResolver(),
creationContext
);
joinedTableGroup = joinedTableGroupJoin.getJoinedGroup();
lhsTableGroup.addTableGroupJoin( joinedTableGroupJoin );
fromClauseIndex.register( sqmJoin, joinedTableGroup );
}
}
// add any additional join restrictions
if ( sqmJoin.getJoinPredicate() != null ) {
tableGroupJoin.applyPredicate(
if ( sqmJoin.isFetched() ) {
QueryLogger.QUERY_LOGGER.debugf( "Join fetch [" + sqmJoin.getNavigablePath() + "] is restricted" );
}
if ( joinedTableGroupJoin == null ) {
throw new IllegalStateException( );
}
joinedTableGroupJoin.applyPredicate(
(Predicate) sqmJoin.getJoinPredicate().accept( this )
);
}
consumeExplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() );
consumeImplicitJoins( sqmJoin, tableGroupJoin.getJoinedGroup() );
consumeExplicitJoins( sqmJoin, joinedTableGroup );
consumeImplicitJoins( sqmJoin, joinedTableGroup );
}
private void consumeCrossJoin(SqmCrossJoin sqmJoin, TableGroup lhsTableGroup) {

View File

@ -26,6 +26,7 @@ import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.internal.util.collections.Stack;
import org.hibernate.internal.util.collections.StandardStack;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.metamodel.model.domain.EntityDomainType;
@ -221,6 +222,12 @@ public class StandardSqmSelectTranslator
return (DomainResultProducer) sqmSelection.getSelectableNode().accept( this );
}
@Override
public ModelPart resolveModelPart(NavigablePath navigablePath) {
// again, assume that the path refers to a TableGroup
return getFromClauseIndex().findTableGroup( navigablePath ).getModelPart();
}
private int fetchDepth = 0;
@Override
@ -236,7 +243,7 @@ public class StandardSqmSelectTranslator
final Fetch biDirectionalFetch = fetchable.resolveCircularFetch(
fetchablePath,
fetchParent,
getSqlAstCreationState().getCurrentProcessingState()
StandardSqmSelectTranslator.this
);
if ( biDirectionalFetch != null ) {

View File

@ -6,12 +6,7 @@
*/
package org.hibernate.sql.ast.spi;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.metamodel.mapping.ordering.OrderByFragment;
import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.FetchParent;
/**
* Access to stuff used while creating a SQL AST
@ -30,12 +25,4 @@ public interface SqlAstCreationState {
SqlAliasBaseGenerator getSqlAliasBaseGenerator();
LockMode determineLockMode(String identificationVariable);
/**
* Visit fetches for the given parent.
*
* We walk fetches via the SqlAstCreationContext because each "context"
* will define differently what should be fetched (HQL versus load)
*/
List<Fetch> visitFetches(FetchParent fetchParent);
}

View File

@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.hibernate.LockMode;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -22,6 +23,7 @@ import org.hibernate.sql.ast.spi.SqlAliasBase;
*/
public class StandardTableGroup extends AbstractTableGroup {
private final TableReference primaryTableReference;
private final Predicate<String> tableReferenceJoinNameChecker;
private final BiFunction<String,TableGroup,TableReferenceJoin> tableReferenceJoinCreator;
private List<TableReferenceJoin> tableJoins;
@ -38,6 +40,14 @@ public class StandardTableGroup extends AbstractTableGroup {
this.primaryTableReference = primaryTableReference;
this.tableJoins = tableJoins;
this.tableReferenceJoinCreator = null;
this.tableReferenceJoinNameChecker = s -> {
for ( int i = 0; i < tableJoins.size(); i++ ) {
if ( tableJoins.get( i ).getJoinedTableReference().getTableExpression().equals( s ) ) {
return true;
}
}
return false;
};
}
public StandardTableGroup(
@ -46,11 +56,13 @@ public class StandardTableGroup extends AbstractTableGroup {
LockMode lockMode,
TableReference primaryTableReference,
SqlAliasBase sqlAliasBase,
Predicate<String> tableReferenceJoinNameChecker,
BiFunction<String,TableGroup,TableReferenceJoin> tableReferenceJoinCreator,
SessionFactoryImplementor sessionFactory) {
super( navigablePath, tableGroupProducer, lockMode, sqlAliasBase, sessionFactory );
this.primaryTableReference = primaryTableReference;
this.tableJoins = null;
this.tableReferenceJoinNameChecker = tableReferenceJoinNameChecker;
this.tableReferenceJoinCreator = tableReferenceJoinCreator;
}
@ -88,24 +100,28 @@ public class StandardTableGroup extends AbstractTableGroup {
return tableReference;
}
if ( tableJoins != null ) {
for ( int i = 0; i < tableJoins.size(); i++ ) {
final TableReferenceJoin join = tableJoins.get( i );
assert join != null;
if ( join.getJoinedTableReference().getTableExpression().equals( tableExpression ) ) {
return join.getJoinedTableReference();
if ( tableReferenceJoinNameChecker.test( tableExpression ) ) {
if ( tableJoins != null ) {
for ( int i = 0; i < tableJoins.size(); i++ ) {
final TableReferenceJoin join = tableJoins.get( i );
assert join != null;
if ( join.getJoinedTableReference().getTableExpression().equals( tableExpression ) ) {
return join.getJoinedTableReference();
}
}
}
return potentiallyCreateTableReference( tableExpression );
}
for ( TableGroupJoin tableGroupJoin : getTableGroupJoins() ) {
final TableReference primaryTableReference = tableGroupJoin.getJoinedGroup().getPrimaryTableReference();
if ( primaryTableReference.getTableExpression().equals( tableExpression ) ) {
return primaryTableReference;
}
}
// for ( TableGroupJoin tableGroupJoin : getTableGroupJoins() ) {
// final TableReference primaryTableReference = tableGroupJoin.getJoinedGroup().getPrimaryTableReference();
// if ( primaryTableReference.getTableExpression().equals( tableExpression ) ) {
// return primaryTableReference;
// }
// }
return potentiallyCreateTableReference( tableExpression );
return null;
}
@SuppressWarnings("WeakerAccess")

View File

@ -26,4 +26,8 @@ public interface TableGroupProducer extends ModelPartContainer {
* @see SqlAliasBaseManager#createSqlAliasBase
*/
String getSqlAliasStem();
default boolean containsTableReference(String tableExpression) {
return false;
}
}

View File

@ -9,6 +9,9 @@ package org.hibernate.sql.results.graph;
import java.util.List;
import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.jdbc.spi.JdbcValues;
import org.hibernate.sql.ast.spi.SqlAliasBaseManager;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
@ -24,8 +27,21 @@ public interface DomainResultCreationState {
}
/**
* Resolve the ModelPart associated with a given NavigablePath. More specific ModelParts should be preferred - e.g.
* the SingularAssociationAttributeMapping rather than just the EntityTypeMapping for the associated type
*/
default ModelPart resolveModelPart(NavigablePath navigablePath) {
throw new NotYetImplementedFor6Exception( getClass() );
}
/**
* Visit fetches for the given parent.
*
* We walk fetches via the SqlAstCreationContext because each "context"
* will define differently what should be fetched (HQL versus load)
*
* todo (6.0) : centralize the implementation of this
* most of the logic in the impls of this is identical. variations (arguments) include:
* most of the logic in the impls of this is identical. variations include:
* 1) given a Fetchable, determine the FetchTiming and `selected`[1]. Tricky as functional
* interface because of the "composite return".
* 2) given a Fetchable, determine the LockMode - currently not handled very well here; should consult `#getLockOptions`
@ -47,8 +63,6 @@ public interface DomainResultCreationState {
* todo (6.0) : wrt the "trickiness" of `selected[1]`, that may no longer be an issue given how TableGroups
* are built/accessed. Comes down to how we'd know whether to join fetch or select fetch. Simply pass
* along FetchStyle?
*
*
*/
List<Fetch> visitFetches(FetchParent fetchParent);
}

View File

@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph;
import java.util.List;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.NavigablePath;
@ -27,6 +28,17 @@ public interface FetchParent extends DomainResultGraphNode {
*/
FetchableContainer getReferencedMappingType();
default FetchParent resolveContainingAssociationParent() {
final ModelPart referencedModePart = getReferencedModePart();
if ( referencedModePart instanceof Association ) {
return this;
}
if ( this instanceof Fetch ) {
( (Fetch) this ).getFetchParent().resolveContainingAssociationParent();
}
return null;
}
/**
* Whereas {@link #getReferencedMappingContainer} and {@link #getReferencedMappingType} return the
* referenced container type, this method returns the referenced part.

View File

@ -9,10 +9,8 @@ package org.hibernate.sql.results.graph;
import org.hibernate.LockMode;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
/**
* @author Steve Ebersole
@ -27,16 +25,10 @@ public interface Fetchable extends ModelPart {
// per Fetch generation is performance drain. Would be better to
// simply pass these 2 pieces of information
/**
* For an association, this would return the foreign-key's "referring columns". Would target
* the columns defined by {@link EntityValuedModelPart#getIdentifyingColumnExpressions}
*/
String[] getIdentifyingColumnExpressions();
default Fetch resolveCircularFetch(
NavigablePath fetchablePath,
FetchParent fetchParent,
SqlAstProcessingState creationState) {
DomainResultCreationState creationState) {
return null;
}

View File

@ -12,10 +12,11 @@ import org.hibernate.LockMode;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.engine.FetchStrategy;
import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.Association;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.ForeignKeyDescriptor;
import org.hibernate.metamodel.mapping.MappingType;
import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.results.graph.AssemblerCreationState;
@ -27,8 +28,6 @@ import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.Fetchable;
import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.embeddable.EmbeddableInitializer;
import org.hibernate.sql.results.graph.embeddable.EmbeddableValuedFetchable;
import org.hibernate.sql.results.graph.entity.EntityInitializer;
import org.hibernate.sql.results.jdbc.spi.JdbcValuesSourceProcessingOptions;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
@ -37,7 +36,7 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
/**
* @author Andrea Boriero
*/
public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
public class BiDirectionalFetchImpl implements BiDirectionalFetch, Association {
private final FetchTiming timing;
private final NavigablePath navigablePath;
private final Fetchable fetchable;
@ -144,6 +143,18 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
throw new UnsupportedOperationException();
}
@Override
public ForeignKeyDescriptor getForeignKeyDescriptor() {
return ( (Association) fetchParent ).getForeignKeyDescriptor();
}
@Override
public String[] getIdentifyingColumnExpressions() {
// fetch parent really always needs to be an Association, so we simply cast here
// should maybe verify this in ctor
return ( (Association) fetchParent ).getIdentifyingColumnExpressions();
}
@Override
public Fetch generateFetch(
FetchParent fetchParent,

View File

@ -154,7 +154,10 @@ public class EmbeddedCircularFetchTests {
session -> {
session.getSessionFactory().getStatistics().clear();
final RootEntity result = session.createQuery(
"from RootEntity r join fetch r.intermediateComponent.leaves",
// "from RootEntity r join fetch r.intermediateComponent.leaves",
"from RootEntity r " +
"join fetch r.intermediateComponent.leaves l " +
"join fetch l.rootEntity",
RootEntity.class
).uniqueResult();
assertTrue( Hibernate.isInitialized( result.getIntermediateComponent().getLeaves() ) );