continued work on circular fetch detection. still need to work through the cases involving embeddables "in between"
This commit is contained in:
parent
4f750cdc55
commit
00b5a700eb
|
@ -0,0 +1,134 @@
|
|||
= Circular fetching
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
We need to make sure that any association is only join-fetched once. E.g.
|
||||
|
||||
```
|
||||
from LineItem l
|
||||
join fetch l.order o
|
||||
join fetch l.order o2
|
||||
```
|
||||
|
||||
This should be illegal. It is possible to handle it specially, but that would be very complicated.
|
||||
|
||||
`FromClauseIndex#findJoinFetch(NavigablePath parentPath, String fetchableName)` - if this does not return null we
|
||||
know that the association is already join fetched and we should throw and exception
|
||||
====
|
||||
|
||||
== one-to-one
|
||||
|
||||
```
|
||||
@Entity
|
||||
class Root {
|
||||
...
|
||||
|
||||
@OneToOne(mappedBy="root1")
|
||||
Leaf leaf1;
|
||||
|
||||
@OneToOne(mappedBy="root2")
|
||||
Leaf leaf2;
|
||||
}
|
||||
|
||||
@Entity
|
||||
class Leaf {
|
||||
...
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name="root_id_1")
|
||||
Root root1;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name="root_id_2")
|
||||
Root root2;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Given this model, we have the following mappings from modelPart to "identifying columns":
|
||||
|
||||
* `Root#leaf1` -> `leaf.root_id_1`
|
||||
* `Root#leaf2` -> `leaf.root_id_2`
|
||||
* `Leaf#root1` -> `leaf.root_id_1`
|
||||
* `Leaf#root2` -> `leaf.root_id_2`
|
||||
|
||||
So given a query like:
|
||||
|
||||
```
|
||||
from Root r
|
||||
join fetch r.leaf1 l
|
||||
join fetch l.root1
|
||||
join fetch l.root2
|
||||
```
|
||||
|
||||
`l.root1` is circular whereas `l.root2` is not. We'd know this by looking at the "identifying columns".
|
||||
|
||||
Specifically, `l.root1` is considered circular **not** because it refers back to `Root(r)` but because it maps to the
|
||||
same column(s) as its parent: `leaf.root_id_1`
|
||||
|
||||
|
||||
// we need to be able to ultimately be able to resolve the "identifying columns" for a given path. E.g.
|
||||
|
||||
```
|
||||
interface DomainResultCreationState {
|
||||
...
|
||||
|
||||
Fetchable resolveFetchable(NavigablePath navigablePath) {
|
||||
// the path passed in here would be `pathToParent` (i.e. `Root(r).leaf1(l)`)
|
||||
// we'd used that to determine the lhs's identifying columns via
|
||||
// `Fetchable#getIdentifyingColumnExpressions` and check them against the
|
||||
// identifying columns for the Fetchable we are processing
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
== many-to-one
|
||||
|
||||
```
|
||||
@Entity
|
||||
@Table(name="orders")
|
||||
class Order {
|
||||
...
|
||||
|
||||
@OneToMany(mappedBy="order")
|
||||
List<LineItem> lineItems;
|
||||
}
|
||||
|
||||
@Entity
|
||||
@Table(name="lines")
|
||||
class LineItem {
|
||||
...
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name="order_id")
|
||||
Order order;
|
||||
}
|
||||
```
|
||||
|
||||
Given this model, we have the following mappings from modelPart to "identifying columns":
|
||||
|
||||
* `LineItem#order` -> `lines.order_id`
|
||||
* `Order#lineItems#{element}` -> `lines.order_id`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Here, the join fetch of `l.order` is circular, meaning we do not want to render a join in the SQL for it
|
||||
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.
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -36,7 +38,7 @@ public class PropertySetterAccessException extends PropertyAccessException {
|
|||
propertyName,
|
||||
expectedType.getName(),
|
||||
target,
|
||||
value
|
||||
loggablePropertyValueString( value )
|
||||
),
|
||||
true,
|
||||
persistentClass,
|
||||
|
@ -44,6 +46,13 @@ public class PropertySetterAccessException extends PropertyAccessException {
|
|||
);
|
||||
}
|
||||
|
||||
public static String loggablePropertyValueString(Object value) {
|
||||
if ( value instanceof Collection ) {
|
||||
return value.getClass().getSimpleName();
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.originalMessage();
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.internal.log;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.spi.CollectionKey;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
|
|
|
@ -55,7 +55,6 @@ import org.hibernate.sql.results.graph.Fetchable;
|
|||
import org.hibernate.sql.results.graph.FetchableContainer;
|
||||
import org.hibernate.sql.results.graph.collection.internal.CollectionDomainResult;
|
||||
import org.hibernate.sql.results.internal.SqlSelectionImpl;
|
||||
import org.hibernate.sql.results.spi.CircularFetchDetector;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
|
@ -376,7 +375,6 @@ public class LoaderSelectBuilder {
|
|||
orderByFragments.put( orderByFragment, tableGroup );
|
||||
}
|
||||
|
||||
private final CircularFetchDetector circularFetchDetector = new CircularFetchDetector();
|
||||
private int fetchDepth = 0;
|
||||
|
||||
private List<Fetch> visitFetches(FetchParent fetchParent, QuerySpec querySpec, LoaderSqlAstCreationState creationState) {
|
||||
|
@ -387,9 +385,9 @@ public class LoaderSelectBuilder {
|
|||
final Consumer<Fetchable> processor = fetchable -> {
|
||||
final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
|
||||
|
||||
final Fetch biDirectionalFetch = circularFetchDetector.findBiDirectionalFetch(
|
||||
final Fetch biDirectionalFetch = fetchable.resolveCircularFetch(
|
||||
fetchablePath,
|
||||
fetchParent,
|
||||
fetchable,
|
||||
creationState
|
||||
);
|
||||
|
||||
|
@ -426,7 +424,7 @@ public class LoaderSelectBuilder {
|
|||
|
||||
try {
|
||||
if ( ! (fetchable instanceof BasicValuedModelPart) ) {
|
||||
fetchDepth--;
|
||||
fetchDepth++;
|
||||
}
|
||||
Fetch fetch = fetchable.generateFetch(
|
||||
fetchParent,
|
||||
|
|
|
@ -37,6 +37,8 @@ public interface EntityValuedModelPart extends FetchableContainer {
|
|||
getEntityMappingType().visitSubParts( consumer, targetType );
|
||||
}
|
||||
|
||||
String[] getIdentifyingColumnExpressions();
|
||||
|
||||
@Override
|
||||
default <T> DomainResult<T> createDomainResult(
|
||||
NavigablePath navigablePath,
|
||||
|
|
|
@ -163,14 +163,15 @@ public class EmbeddedAttributeMapping
|
|||
return navigableRole;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isCircular(FetchParent fetchParent, SqlAstProcessingState creationState) {
|
||||
public Fetch resolveCircularFetch(
|
||||
NavigablePath fetchablePath,
|
||||
FetchParent fetchParent,
|
||||
SqlAstProcessingState creationState) {
|
||||
// an embeddable can never be circular
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Fetch generateFetch(
|
||||
FetchParent fetchParent,
|
||||
|
@ -279,4 +280,9 @@ public class EmbeddedAttributeMapping
|
|||
public int getNumberOfFetchables() {
|
||||
return getEmbeddableTypeDescriptor().getNumberOfAttributeMappings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EmbeddedAttributeMapping {" + navigableRole + "}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,11 @@ 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;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.collection.internal.EntityCollectionPartTableGroup;
|
||||
import org.hibernate.sql.results.graph.entity.EntityFetch;
|
||||
|
@ -115,6 +117,14 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa
|
|||
return FetchStrategy.IMMEDIATE_JOIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fetch resolveCircularFetch(
|
||||
NavigablePath fetchablePath,
|
||||
FetchParent fetchParent,
|
||||
SqlAstProcessingState creationState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityFetch generateFetch(
|
||||
FetchParent fetchParent,
|
||||
|
@ -173,4 +183,13 @@ public class EntityCollectionPart implements CollectionPart, EntityAssociationMa
|
|||
public int getNumberOfFetchables() {
|
||||
return entityMappingType.getNumberOfFetchables();
|
||||
}
|
||||
|
||||
public String getMappedBy() {
|
||||
return collectionDescriptor.getMappedByProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EntityCollectionPart {" + navigableRole + "}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,16 @@
|
|||
package org.hibernate.metamodel.mapping.internal;
|
||||
|
||||
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.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;
|
||||
|
@ -37,26 +42,37 @@ 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.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.entity.EntityFetch;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class SingularAssociationAttributeMapping extends AbstractSingularAttributeMapping
|
||||
implements EntityValuedFetchable, EntityAssociationMapping, TableGroupJoinProducer {
|
||||
|
||||
public enum Cardinality {
|
||||
ONE_TO_ONE,
|
||||
MANY_TO_ONE,
|
||||
LOGICAL_ONE_TO_ONE
|
||||
}
|
||||
|
||||
private final NavigableRole navigableRole;
|
||||
private final String subRole;
|
||||
|
||||
private final String sqlAliasStem;
|
||||
private final boolean isNullable;
|
||||
private final boolean referringPrimaryKey;
|
||||
private final boolean unwrapProxy;
|
||||
|
||||
private final String referencedPropertyName;
|
||||
private final boolean referringPrimaryKey;
|
||||
|
||||
private final Cardinality cardinality;
|
||||
|
||||
private ForeignKeyDescriptor foreignKeyDescriptor;
|
||||
|
||||
|
@ -64,7 +80,7 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
|
|||
public SingularAssociationAttributeMapping(
|
||||
String name,
|
||||
int stateArrayPosition,
|
||||
ToOne value,
|
||||
ToOne bootValue,
|
||||
StateArrayContributorMetadataAccess attributeMetadataAccess,
|
||||
FetchStrategy mappedFetchStrategy,
|
||||
EntityMappingType type,
|
||||
|
@ -80,19 +96,33 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
|
|||
propertyAccess
|
||||
);
|
||||
this.sqlAliasStem = SqlAliasStemHelper.INSTANCE.generateStemFromAttributeName( name );
|
||||
this.isNullable = value.isNullable();
|
||||
referencedPropertyName = value.getReferencedPropertyName();
|
||||
referringPrimaryKey = value.isReferenceToPrimaryKey();
|
||||
unwrapProxy = value.isUnwrapProxy();
|
||||
this.isNullable = bootValue.isNullable();
|
||||
this.referencedPropertyName = bootValue.getReferencedPropertyName();
|
||||
this.referringPrimaryKey = bootValue.isReferenceToPrimaryKey();
|
||||
this.unwrapProxy = bootValue.isUnwrapProxy();
|
||||
|
||||
this.navigableRole = declaringType.getNavigableRole().appendContainer( name );
|
||||
final int containerMarkerPosition = navigableRole.getFullPath().lastIndexOf( '#' );
|
||||
if ( containerMarkerPosition < 0 ) {
|
||||
subRole = name;
|
||||
if ( referringPrimaryKey ) {
|
||||
assert referencedPropertyName == null;
|
||||
}
|
||||
else {
|
||||
subRole = navigableRole.getFullPath().substring( containerMarkerPosition + 1 );
|
||||
assert referencedPropertyName != null;
|
||||
}
|
||||
|
||||
if ( bootValue instanceof ManyToOne ) {
|
||||
final ManyToOne manyToOne = (ManyToOne) bootValue;
|
||||
if ( manyToOne.isLogicalOneToOne() ) {
|
||||
cardinality = Cardinality.LOGICAL_ONE_TO_ONE;
|
||||
}
|
||||
else {
|
||||
cardinality = Cardinality.MANY_TO_ONE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert bootValue instanceof OneToOne;
|
||||
cardinality = Cardinality.ONE_TO_ONE;
|
||||
}
|
||||
|
||||
this.navigableRole = declaringType.getNavigableRole().appendContainer( name );
|
||||
}
|
||||
|
||||
public void setForeignKeyDescriptor(ForeignKeyDescriptor foreignKeyDescriptor) {
|
||||
|
@ -123,12 +153,33 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isCircular(FetchParent fetchParent, SqlAstProcessingState creationState) {
|
||||
// E.g. say we have a query like:
|
||||
// select p
|
||||
// from Person p
|
||||
// join fetch p.address a
|
||||
// join fetch a.owner
|
||||
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.
|
||||
|
@ -137,39 +188,90 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
|
|||
// 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)
|
||||
|
||||
final NavigablePath parentNavigablePath = fetchParent.getNavigablePath();
|
||||
NavigablePath pathToParentParent = parentNavigablePath.getParent();
|
||||
|
||||
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 ) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
for ( int i = 0; i < numberOfPathElementsToContainer; i++ ) {
|
||||
pathToParentParent = pathToParentParent.getParent();
|
||||
}
|
||||
|
||||
assert pathToParentParent != null;
|
||||
|
||||
final ModelPartContainer modelPart = creationState.getSqlAstCreationState()
|
||||
final TableGroup parentParentTableGroup = creationState.getSqlAstCreationState()
|
||||
.getFromClauseAccess()
|
||||
.findTableGroup( pathToParentParent )
|
||||
.getModelPart();
|
||||
.findTableGroup( pathToParentParent );
|
||||
|
||||
parentParentTableGroup.getModelPart().findContainingEntityMapping()
|
||||
|
||||
final ModelPartContainer parentParentPart = parentParentTableGroup.getModelPart();
|
||||
final ModelPart parentPart = parentParentPart.findSubPart( pathToParent.getLocalName(), null );
|
||||
|
||||
if ( ! parentPart.equals( fetchParent.getReferencedModePart() ) ) {
|
||||
throw new AssertionError( );
|
||||
}
|
||||
|
||||
|
||||
final ModelPart subPart = modelPart.findSubPart( parentNavigablePath.getLocalName(), null );
|
||||
if ( subPart instanceof EntityAssociationMapping ) {
|
||||
final EntityAssociationMapping part = (EntityAssociationMapping) subPart;
|
||||
|
||||
if ( parentNavigablePath.getLocalName().equals( referencedPropertyName )
|
||||
&& part.getFetchableName().equals( referencedPropertyName ) ) {
|
||||
return true;
|
||||
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() );
|
||||
}
|
||||
else if ( part.getKeyTargetMatchPart() != null
|
||||
&& part.getKeyTargetMatchPart().getPartName().equals( getAttributeName() ) ) {
|
||||
return true;
|
||||
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()
|
||||
);
|
||||
}
|
||||
}
|
||||
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() );
|
||||
|
||||
if ( condition1 || condition2 ) {
|
||||
return new BiDirectionalFetchImpl(
|
||||
FetchTiming.IMMEDIATE,
|
||||
fetchablePath,
|
||||
fetchParent,
|
||||
this,
|
||||
fetchParent.getNavigablePath().getParent()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -339,4 +441,9 @@ public class SingularAssociationAttributeMapping extends AbstractSingularAttribu
|
|||
public ModelPart getKeyTargetMatchPart() {
|
||||
return foreignKeyDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SingularAssociationAttributeMapping {" + navigableRole + "}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,6 @@ import org.hibernate.sql.results.graph.FetchParent;
|
|||
import org.hibernate.sql.results.graph.Fetchable;
|
||||
import org.hibernate.sql.results.graph.entity.EntityResultGraphNode;
|
||||
import org.hibernate.sql.results.graph.instantiation.internal.DynamicInstantiation;
|
||||
import org.hibernate.sql.results.spi.CircularFetchDetector;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
|
||||
/**
|
||||
|
@ -82,7 +81,6 @@ public class StandardSqmSelectTranslator
|
|||
extends BaseSqmToSqlAstConverter
|
||||
implements DomainResultCreationState, SqmSelectTranslator {
|
||||
private final LoadQueryInfluencers fetchInfluencers;
|
||||
private final CircularFetchDetector circularFetchDetector = new CircularFetchDetector();
|
||||
|
||||
// prepare for 10 root selections to avoid list growth in most cases
|
||||
private final List<DomainResult> domainResults = CollectionHelper.arrayList( 10 );
|
||||
|
@ -233,9 +231,11 @@ public class StandardSqmSelectTranslator
|
|||
final Consumer<Fetchable> fetchableConsumer = new Consumer<Fetchable>() {
|
||||
@Override
|
||||
public void accept(Fetchable fetchable) {
|
||||
final Fetch biDirectionalFetch = circularFetchDetector.findBiDirectionalFetch(
|
||||
final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
|
||||
|
||||
final Fetch biDirectionalFetch = fetchable.resolveCircularFetch(
|
||||
fetchablePath,
|
||||
fetchParent,
|
||||
fetchable,
|
||||
getSqlAstCreationState().getCurrentProcessingState()
|
||||
);
|
||||
|
||||
|
@ -246,7 +246,7 @@ public class StandardSqmSelectTranslator
|
|||
|
||||
try {
|
||||
fetchDepth++;
|
||||
final Fetch fetch = buildFetch( fetchParent, fetchable );
|
||||
final Fetch fetch = buildFetch( fetchablePath, fetchParent, fetchable );
|
||||
|
||||
if ( fetch != null ) {
|
||||
fetches.add( fetch );
|
||||
|
@ -267,15 +267,13 @@ public class StandardSqmSelectTranslator
|
|||
return fetches;
|
||||
}
|
||||
|
||||
private Fetch buildFetch(FetchParent fetchParent, Fetchable fetchable) {
|
||||
private Fetch buildFetch(NavigablePath fetchablePath, FetchParent fetchParent, Fetchable fetchable) {
|
||||
// fetch has access to its parent in addition to the parent having its fetches.
|
||||
//
|
||||
// we could sever the parent -> fetch link ... it would not be "seen" while walking
|
||||
// but it would still have access to its parent info - and be able to access its
|
||||
// "initializing" state as part of AfterLoadAction
|
||||
|
||||
final NavigablePath fetchablePath = fetchParent.getNavigablePath().append( fetchable.getFetchableName() );
|
||||
|
||||
final GraphImplementor<?> previousGraphNode = currentJpaGraphNode;
|
||||
|
||||
final String alias;
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
|
||||
/**
|
||||
|
@ -16,13 +17,29 @@ import org.hibernate.query.NavigablePath;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public interface FetchParent extends DomainResultGraphNode {
|
||||
/**
|
||||
* This parent's mapping type
|
||||
*/
|
||||
FetchableContainer getReferencedMappingContainer();
|
||||
|
||||
/**
|
||||
* This parent's type
|
||||
* This parent's mapping type
|
||||
*/
|
||||
FetchableContainer getReferencedMappingType();
|
||||
|
||||
/**
|
||||
* Whereas {@link #getReferencedMappingContainer} and {@link #getReferencedMappingType} return the
|
||||
* referenced container type, this method returns the referenced part.
|
||||
*
|
||||
* E.g. for a many-to-one this methods returns the
|
||||
* {@link org.hibernate.metamodel.mapping.internal.SingularAssociationAttributeMapping} while
|
||||
* {@link #getReferencedMappingContainer} and {@link #getReferencedMappingType} return the referenced
|
||||
* {@link org.hibernate.metamodel.mapping.EntityMappingType}.
|
||||
*/
|
||||
default ModelPart getReferencedModePart() {
|
||||
return getReferencedMappingContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the property path to this parent
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,7 @@ 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;
|
||||
|
@ -26,6 +27,19 @@ 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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Fetch generateFetch(
|
||||
FetchParent fetchParent,
|
||||
NavigablePath fetchablePath,
|
||||
|
@ -34,9 +48,4 @@ public interface Fetchable extends ModelPart {
|
|||
LockMode lockMode,
|
||||
String resultVariable,
|
||||
DomainResultCreationState creationState);
|
||||
|
||||
default boolean isCircular(FetchParent fetchParent, SqlAstProcessingState creationState){
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ package org.hibernate.sql.results.graph.entity.internal;
|
|||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.hibernate.metamodel.mapping.ModelPart;
|
||||
import org.hibernate.sql.results.graph.FetchableContainer;
|
||||
import org.hibernate.sql.results.graph.entity.AbstractEntityResultGraphNode;
|
||||
import org.hibernate.sql.results.graph.entity.EntityResult;
|
||||
|
@ -57,6 +58,11 @@ public class EntityResultImpl extends AbstractEntityResultGraphNode implements E
|
|||
return getReferencedMappingContainer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityValuedModelPart getReferencedModePart() {
|
||||
return getEntityValuedModelPart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResultVariable() {
|
||||
return resultVariable;
|
||||
|
@ -82,4 +88,8 @@ public class EntityResultImpl extends AbstractEntityResultGraphNode implements E
|
|||
return new EntityAssembler( getResultJavaTypeDescriptor(), initializer );
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EntityResultImpl {" + getNavigablePath() + "}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,10 +56,7 @@ public class Helper {
|
|||
};
|
||||
}
|
||||
else {
|
||||
return initializer -> {
|
||||
// noinspection Convert2MethodRef
|
||||
initializers.add( initializer );
|
||||
};
|
||||
return initializers::add;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ import org.hibernate.engine.FetchStrategy;
|
|||
import org.hibernate.engine.FetchTiming;
|
||||
import org.hibernate.metamodel.mapping.EntityMappingType;
|
||||
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;
|
||||
|
@ -25,6 +27,9 @@ 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;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
|
@ -164,7 +169,7 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
|
|||
|
||||
@Override
|
||||
public Object assemble(RowProcessingState rowProcessingState, JdbcValuesSourceProcessingOptions options) {
|
||||
Initializer initializer = rowProcessingState.resolveInitializer( circularPath );
|
||||
final EntityInitializer initializer = resolveCircularInitializer( rowProcessingState );
|
||||
if ( initializer.getInitializedInstance() == null ) {
|
||||
initializer.resolveKey( rowProcessingState );
|
||||
initializer.resolveInstance( rowProcessingState );
|
||||
|
@ -173,6 +178,24 @@ public class BiDirectionalFetchImpl implements BiDirectionalFetch, Fetchable {
|
|||
return initializer.getInitializedInstance();
|
||||
}
|
||||
|
||||
private EntityInitializer resolveCircularInitializer(RowProcessingState rowProcessingState) {
|
||||
final Initializer initializer = rowProcessingState.resolveInitializer( circularPath );
|
||||
final ModelPart initializedPart = initializer.getInitializedPart();
|
||||
|
||||
if ( initializedPart instanceof EntityInitializer ) {
|
||||
return (EntityInitializer) initializedPart;
|
||||
}
|
||||
|
||||
NavigablePath path = circularPath.getParent();
|
||||
Initializer parentInitializer = rowProcessingState.resolveInitializer( path );
|
||||
while ( ! ( parentInitializer instanceof EntityInitializer ) ) {
|
||||
path = path.getParent();
|
||||
parentInitializer = rowProcessingState.resolveInitializer( path );
|
||||
}
|
||||
|
||||
return (EntityInitializer) parentInitializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaTypeDescriptor getAssembledJavaTypeDescriptor() {
|
||||
return javaTypeDescriptor;
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.hibernate.query.NavigablePath;
|
|||
import org.hibernate.sql.ast.spi.SqlSelection;
|
||||
import org.hibernate.sql.exec.spi.ExecutionContext;
|
||||
import org.hibernate.sql.results.graph.entity.EntityFetch;
|
||||
import org.hibernate.sql.results.graph.entity.EntityInitializer;
|
||||
|
||||
/**
|
||||
* State pertaining to the processing of a single "row" of a JdbcValuesSource
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* 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.sql.results.spi;
|
||||
|
||||
|
||||
import org.hibernate.engine.FetchTiming;
|
||||
import org.hibernate.sql.results.graph.Fetch;
|
||||
import org.hibernate.sql.results.graph.FetchParent;
|
||||
import org.hibernate.sql.results.graph.Fetchable;
|
||||
import org.hibernate.query.NavigablePath;
|
||||
import org.hibernate.sql.ast.spi.SqlAstProcessingState;
|
||||
import org.hibernate.sql.results.internal.domain.BiDirectionalFetchImpl;
|
||||
|
||||
/**
|
||||
* Maintains state while processing a Fetch graph to be able to detect
|
||||
* and handle circular bi-directional references
|
||||
*
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class CircularFetchDetector {
|
||||
|
||||
public Fetch findBiDirectionalFetch(
|
||||
FetchParent fetchParent,
|
||||
Fetchable fetchable,
|
||||
SqlAstProcessingState creationState) {
|
||||
if ( !fetchable.isCircular( fetchParent, creationState ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final NavigablePath navigablePath = fetchParent.getNavigablePath();
|
||||
if ( navigablePath.getParent().getParent() == null ) {
|
||||
return new BiDirectionalFetchImpl(
|
||||
FetchTiming.IMMEDIATE,
|
||||
navigablePath,
|
||||
fetchParent,
|
||||
fetchable,
|
||||
fetchParent.getNavigablePath().getParent()
|
||||
);
|
||||
}
|
||||
else {
|
||||
return new BiDirectionalFetchImpl(
|
||||
// todo (6.0) : needs to be the parent-parent's fetch timing
|
||||
// atm we do not have the ability to get this
|
||||
null,
|
||||
navigablePath.append( fetchable.getFetchableName() ),
|
||||
fetchParent,
|
||||
fetchable,
|
||||
navigablePath.getParent()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@
|
|||
package org.hibernate.orm.test.annotations.embedded;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Embedded;
|
||||
|
@ -28,11 +27,8 @@ import org.hibernate.testing.orm.junit.SessionFactory;
|
|||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.hamcrest.CoreMatchers;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -50,6 +46,12 @@ import static org.junit.Assert.assertTrue;
|
|||
@ServiceRegistry.Setting(name = AvailableSettings.GENERATE_STATISTICS, value = "true")
|
||||
})
|
||||
public class EmbeddedCircularFetchTests {
|
||||
// todo (6.0 : this (along with the `org.hibernate.orm.test.sql.exec.onetoone.bidirectional` package)
|
||||
// probably makes better sense in a dedicated `org.hibernate.orm.test.fetch.circular` package:
|
||||
// - `org.hibernate.orm.test.fetch.circular.embedded`
|
||||
// - `org.hibernate.orm.test.fetch.circular.onetoone`
|
||||
// - `org.hibernate.orm.test.fetch.circular.manytoone`
|
||||
|
||||
// @Test
|
||||
// @TestForIssue(jiraKey = "HHH-9642")
|
||||
// public void testEmbeddedAndOneToManyHql(SessionFactoryScope scope) {
|
||||
|
@ -156,9 +158,9 @@ public class EmbeddedCircularFetchTests {
|
|||
RootEntity.class
|
||||
).uniqueResult();
|
||||
assertTrue( Hibernate.isInitialized( result.getIntermediateComponent().getLeaves() ) );
|
||||
assertThat( result.getIntermediateComponent().getLeaves().size(), is( 1 ) );
|
||||
assertThat( result.getIntermediateComponent().getLeaves().size(), is( 2 ) );
|
||||
|
||||
assertThat( session.getSessionFactory().getStatistics().getPrepareStatementCount(), is( 1 ) );
|
||||
assertThat( session.getSessionFactory().getStatistics().getPrepareStatementCount(), is( 1L ) );
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -66,24 +66,24 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
public void testGetParent() {
|
||||
inTransaction( session -> {
|
||||
final Parent parent = session.get( Parent.class, 1 );
|
||||
Child child = parent.getChild();
|
||||
Child child = parent.getOwnedBidirectionalChild();
|
||||
assertThat( child, notNullValue() );
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( child ),
|
||||
"The child eager OneToOne association is not initialized"
|
||||
);
|
||||
assertThat( child.getName(), notNullValue() );
|
||||
assertThat( child.getParent(), notNullValue() );
|
||||
assertThat( child.getParent(), notNullValue() );
|
||||
assertThat( child.getParentMappedByChild(), notNullValue() );
|
||||
assertThat( child.getParentMappedByChild(), notNullValue() );
|
||||
|
||||
Child2 child2 = parent.getChild2();
|
||||
Child2 child2 = parent.getChildMappedByParent1();
|
||||
assertThat( child2, notNullValue() );
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( child2 ),
|
||||
"The child2 eager OneToOne association is not initialized"
|
||||
);
|
||||
assertThat( child2.getName(), equalTo( "Fab" ) );
|
||||
assertThat( child2.getParent1(), notNullValue() );
|
||||
assertThat( child2.getOwnedBidirectionalParent(), notNullValue() );
|
||||
|
||||
} );
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
Child2 child2 = new Child2( 6, parent );
|
||||
child2.setName( "Fab2" );
|
||||
|
||||
child2.setParent2( parent );
|
||||
child2.setUnidirectionalParent( parent );
|
||||
|
||||
session.save( parent );
|
||||
session.save( child );
|
||||
|
@ -107,29 +107,29 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
|
||||
inTransaction( session -> {
|
||||
final Parent parent = session.get( Parent.class, 4 );
|
||||
Child child = parent.getChild();
|
||||
Child child = parent.getOwnedBidirectionalChild();
|
||||
assertThat( child, notNullValue() );
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( child ),
|
||||
"The child eager OneToOne association is not initialized"
|
||||
);
|
||||
assertThat( child.getName(), notNullValue() );
|
||||
assertThat( child.getParent(), notNullValue() );
|
||||
assertThat( child.getParentMappedByChild(), notNullValue() );
|
||||
|
||||
Child2 child2 = parent.getChild2();
|
||||
Child2 child2 = parent.getChildMappedByParent1();
|
||||
assertThat( child2, notNullValue() );
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( child2 ),
|
||||
"The child2 eager OneToOne association is not initialized"
|
||||
);
|
||||
assertThat( child2.getName(), equalTo( "Fab2" ) );
|
||||
assertThat( child2.getParent1(), notNullValue() );
|
||||
assertThat( child2.getParent1().getDescription(), equalTo( "Hibernate OGM" ) );
|
||||
assertThat( child2.getOwnedBidirectionalParent(), notNullValue() );
|
||||
assertThat( child2.getOwnedBidirectionalParent().getDescription(), equalTo( "Hibernate OGM" ) );
|
||||
|
||||
Parent parent2 = child2.getParent2();
|
||||
Parent parent2 = child2.getUnidirectionalParent();
|
||||
assertThat( parent2, notNullValue() );
|
||||
assertThat( parent2.getDescription(), equalTo( "Hibernate OGM" ) );
|
||||
assertThat( parent2.getChild(), notNullValue() );
|
||||
assertThat( parent2.getOwnedBidirectionalChild(), notNullValue() );
|
||||
|
||||
} );
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
child2.setName( "Fab2" );
|
||||
|
||||
Parent parent2 = new Parent( 6, "Hibernate OGM" );
|
||||
child2.setParent2( parent2 );
|
||||
child2.setUnidirectionalParent( parent2 );
|
||||
|
||||
Child child1 = new Child( 8, parent2 );
|
||||
|
||||
|
@ -160,29 +160,29 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
final Parent parent = session.get( Parent.class, 4 );
|
||||
assertThat( parent.getDescription(), equalTo( "Hibernate Search" ) );
|
||||
|
||||
Child child = parent.getChild();
|
||||
Child child = parent.getOwnedBidirectionalChild();
|
||||
assertThat( child, notNullValue() );
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( child ),
|
||||
"The child eager OneToOne association is not initialized"
|
||||
);
|
||||
assertThat( child.getName(), notNullValue() );
|
||||
assertThat( child.getParent(), notNullValue() );
|
||||
assertThat( child.getParentMappedByChild(), notNullValue() );
|
||||
|
||||
Child2 child2 = parent.getChild2();
|
||||
Child2 child2 = parent.getChildMappedByParent1();
|
||||
assertThat( child2, notNullValue() );
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( child2 ),
|
||||
"The child2 eager OneToOne association is not initialized"
|
||||
);
|
||||
assertThat( child2.getName(), equalTo( "Fab2" ) );
|
||||
assertThat( child2.getParent1(), notNullValue() );
|
||||
assertThat( child2.getParent1().getDescription(), equalTo( "Hibernate Search" ) );
|
||||
assertThat( child2.getOwnedBidirectionalParent(), notNullValue() );
|
||||
assertThat( child2.getOwnedBidirectionalParent().getDescription(), equalTo( "Hibernate Search" ) );
|
||||
|
||||
Parent parent2 = child2.getParent2();
|
||||
Parent parent2 = child2.getUnidirectionalParent();
|
||||
assertThat( parent2, notNullValue() );
|
||||
assertThat( parent2.getDescription(), equalTo( "Hibernate OGM" ) );
|
||||
assertThat( parent2.getChild(), notNullValue() );
|
||||
assertThat( parent2.getOwnedBidirectionalChild(), notNullValue() );
|
||||
|
||||
} );
|
||||
}
|
||||
|
@ -191,27 +191,27 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
public void testGetChild() {
|
||||
inTransaction( session -> {
|
||||
final Child child = session.get( Child.class, 2 );
|
||||
Parent parent = child.getParent();
|
||||
Parent parent = child.getParentMappedByChild();
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( parent ),
|
||||
"The parent eager OneToOne association is not initialized"
|
||||
);
|
||||
assertThat( parent, notNullValue() );
|
||||
assertThat( parent.getDescription(), notNullValue() );
|
||||
Child child1 = parent.getChild();
|
||||
Child child1 = parent.getOwnedBidirectionalChild();
|
||||
assertThat( child1, notNullValue() );
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( child1 ),
|
||||
"The child eager OneToOne association is not initialized"
|
||||
);
|
||||
Child2 child2 = parent.getChild2();
|
||||
Child2 child2 = parent.getChildMappedByParent1();
|
||||
assertThat( child2, notNullValue() );
|
||||
assertTrue(
|
||||
Hibernate.isInitialized( child2 ),
|
||||
"The child2 eager OneToOne association is not initialized"
|
||||
);
|
||||
assertThat( child2.getParent1(), notNullValue() );
|
||||
assertThat( child2.getParent2(), nullValue() );
|
||||
assertThat( child2.getOwnedBidirectionalParent(), notNullValue() );
|
||||
assertThat( child2.getUnidirectionalParent(), nullValue() );
|
||||
} );
|
||||
}
|
||||
|
||||
|
@ -220,14 +220,14 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
inTransaction(
|
||||
session -> {
|
||||
final Parent parent = session.createQuery(
|
||||
"SELECT p FROM Parent p JOIN p.child WHERE p.id = :id",
|
||||
"SELECT p FROM Parent p JOIN p.ownedBidirectionalChild WHERE p.id = :id",
|
||||
Parent.class
|
||||
)
|
||||
.setParameter( "id", 1 )
|
||||
.getSingleResult();
|
||||
|
||||
assertThat( parent.getChild(), notNullValue() );
|
||||
String name = parent.getChild().getName();
|
||||
assertThat( parent.getOwnedBidirectionalChild(), notNullValue() );
|
||||
String name = parent.getOwnedBidirectionalChild().getName();
|
||||
assertThat( name, notNullValue() );
|
||||
}
|
||||
);
|
||||
|
@ -238,14 +238,14 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
public void testHqlSelectChild() {
|
||||
inTransaction(
|
||||
session -> {
|
||||
final String queryString = "SELECT c FROM Child c JOIN c.parent d WHERE d.id = :id";
|
||||
final String queryString = "SELECT c FROM Child c JOIN c.parentMappedByChild d WHERE d.id = :id";
|
||||
final Child child = session.createQuery( queryString, Child.class )
|
||||
.setParameter( "id", 1 )
|
||||
.getSingleResult();
|
||||
|
||||
assertThat( child.getParent(), notNullValue() );
|
||||
assertThat( child.getParentMappedByChild(), notNullValue() );
|
||||
|
||||
String description = child.getParent().getDescription();
|
||||
String description = child.getParentMappedByChild().getDescription();
|
||||
assertThat( description, notNullValue() );
|
||||
}
|
||||
);
|
||||
|
@ -256,10 +256,12 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
@Id
|
||||
private Integer id;
|
||||
private String description;
|
||||
|
||||
@OneToOne
|
||||
private Child child;
|
||||
@OneToOne(mappedBy = "parent1")
|
||||
private Child2 child2;
|
||||
private Child ownedBidirectionalChild;
|
||||
|
||||
@OneToOne(mappedBy = "ownedBidirectionalParent")
|
||||
private Child2 childMappedByParent1;
|
||||
|
||||
Parent() {
|
||||
}
|
||||
|
@ -289,20 +291,20 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
this.description = description;
|
||||
}
|
||||
|
||||
public Child getChild() {
|
||||
return child;
|
||||
public Child getOwnedBidirectionalChild() {
|
||||
return ownedBidirectionalChild;
|
||||
}
|
||||
|
||||
public void setChild(Child child) {
|
||||
this.child = child;
|
||||
public void setOwnedBidirectionalChild(Child ownedBidirectionalChild) {
|
||||
this.ownedBidirectionalChild = ownedBidirectionalChild;
|
||||
}
|
||||
|
||||
public Child2 getChild2() {
|
||||
return child2;
|
||||
public Child2 getChildMappedByParent1() {
|
||||
return childMappedByParent1;
|
||||
}
|
||||
|
||||
public void setChild2(Child2 child2) {
|
||||
this.child2 = child2;
|
||||
public void setChildMappedByParent1(Child2 childMappedByParent1) {
|
||||
this.childMappedByParent1 = childMappedByParent1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -313,17 +315,17 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
private Integer id;
|
||||
private String name;
|
||||
|
||||
@OneToOne(mappedBy = "child")
|
||||
private Parent parent;
|
||||
@OneToOne(mappedBy = "ownedBidirectionalChild")
|
||||
private Parent parentMappedByChild;
|
||||
|
||||
Child() {
|
||||
|
||||
}
|
||||
|
||||
Child(Integer id, Parent parent) {
|
||||
Child(Integer id, Parent parentMappedByChild) {
|
||||
this.id = id;
|
||||
this.parent = parent;
|
||||
this.parent.setChild( this );
|
||||
this.parentMappedByChild = parentMappedByChild;
|
||||
this.parentMappedByChild.setOwnedBidirectionalChild( this );
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
|
@ -342,12 +344,12 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public Parent getParent() {
|
||||
return parent;
|
||||
public Parent getParentMappedByChild() {
|
||||
return parentMappedByChild;
|
||||
}
|
||||
|
||||
public void setParent(Parent parent) {
|
||||
this.parent = parent;
|
||||
public void setParentMappedByChild(Parent parentMappedByChild) {
|
||||
this.parentMappedByChild = parentMappedByChild;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -360,18 +362,18 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
private String name;
|
||||
|
||||
@OneToOne
|
||||
private Parent parent1;
|
||||
private Parent ownedBidirectionalParent;
|
||||
|
||||
@OneToOne
|
||||
private Parent parent2;
|
||||
private Parent unidirectionalParent;
|
||||
|
||||
Child2() {
|
||||
}
|
||||
|
||||
Child2(Integer id, Parent parent1) {
|
||||
Child2(Integer id, Parent ownedBidirectionalParent) {
|
||||
this.id = id;
|
||||
this.parent1 = parent1;
|
||||
this.parent1.setChild2( this );
|
||||
this.ownedBidirectionalParent = ownedBidirectionalParent;
|
||||
this.ownedBidirectionalParent.setChildMappedByParent1( this );
|
||||
}
|
||||
|
||||
|
||||
|
@ -391,20 +393,20 @@ public class EntityWithBidirectionalOneToOneTest extends SessionFactoryBasedFunc
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public Parent getParent1() {
|
||||
return parent1;
|
||||
public Parent getOwnedBidirectionalParent() {
|
||||
return ownedBidirectionalParent;
|
||||
}
|
||||
|
||||
public void setParent1(Parent parent1) {
|
||||
this.parent1 = parent1;
|
||||
public void setOwnedBidirectionalParent(Parent ownedBidirectionalParent) {
|
||||
this.ownedBidirectionalParent = ownedBidirectionalParent;
|
||||
}
|
||||
|
||||
public Parent getParent2() {
|
||||
return parent2;
|
||||
public Parent getUnidirectionalParent() {
|
||||
return unidirectionalParent;
|
||||
}
|
||||
|
||||
public void setParent2(Parent parent) {
|
||||
this.parent2 = parent;
|
||||
public void setUnidirectionalParent(Parent parent) {
|
||||
this.unidirectionalParent = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue