HHH-14390 : StackOverflowError with @Fetch(FetchMode.SELECT) mapped for entity with an ID that is a bidirectional one-to-one eager association

Move fix into FetchStyleLoadPlanBuildingAssociationVisitationStrategy
This commit is contained in:
Gail Badner 2021-02-24 17:43:43 -08:00
parent 2bacaabc37
commit cb18fdb4f7
2 changed files with 49 additions and 12 deletions

View File

@ -237,18 +237,6 @@ public class OneToOneSecondPass implements SecondPass {
boolean referenceToPrimaryKey = referencesDerivedId || mappedBy == null;
value.setReferenceToPrimaryKey( referenceToPrimaryKey );
// If the other side is an entity with an ID that is derived from
// this side's owner entity, and both sides of the association are eager,
// then this side must be set to FetchMode.SELECT; otherwise,
// there will be an infinite loop attempting to load the derived ID on
// the opposite side.
if ( referencesDerivedId &&
!value.isLazy() &&
value.getFetchMode() == FetchMode.JOIN &&
!otherSideProperty.isLazy() ) {
value.setFetchMode( FetchMode.SELECT );
}
String propertyRef = value.getReferencedPropertyName();
if ( propertyRef != null ) {
buildingContext.getMetadataCollector().addUniquePropertyReference(

View File

@ -15,6 +15,7 @@ import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.loader.plan.spi.CollectionReturn;
import org.hibernate.loader.plan.spi.EntityReference;
import org.hibernate.loader.plan.spi.EntityReturn;
import org.hibernate.loader.plan.spi.FetchSource;
import org.hibernate.loader.plan.spi.LoadPlan;
@ -29,6 +30,8 @@ import org.hibernate.persister.walking.spi.WalkingException;
import org.hibernate.type.CompositeType;
import org.hibernate.type.EmbeddedComponentType;
import org.hibernate.type.EntityType;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.OneToOneType;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
@ -252,6 +255,52 @@ public class FetchStyleLoadPlanBuildingAssociationVisitationStrategy
}
}
if ( attributeType.isEntityType() &&
fetchStrategy.getTiming() == FetchTiming.IMMEDIATE &&
fetchStrategy.getStyle() == FetchStyle.JOIN ) {
final EntityType entityType = (EntityType) attributeType;
final EntityReference currentEntityReference = currentSource.resolveEntityReference();
if ( currentEntityReference != null ) {
final EntityPersister currentEntityPersister = currentEntityReference.getEntityPersister();
if ( entityType.isOneToOne() && entityType.getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) {
// attributeDefinition is the "mappedBy" (inverse) side of a
// bidirectional one-to-one association.
final OneToOneType oneToOneType = (OneToOneType) attributeType;
final String associatedUniqueKeyPropertyName = oneToOneType.getIdentifierOrUniqueKeyPropertyName(
sessionFactory()
);
if ( associatedUniqueKeyPropertyName == null ) {
// The foreign key for the other side of the association is the ID.
// Now check if the ID itself is the other side of the association.
final EntityPersister associatedEntityPersister = (EntityPersister) oneToOneType.getAssociatedJoinable(
sessionFactory()
);
final Type associatedIdentifierType = associatedEntityPersister.getIdentifierType();
if ( associatedIdentifierType.isComponentType() &&
( (CompositeType) associatedIdentifierType ).isEmbedded() ) {
final EmbeddedComponentType associatedNonEncapsulatedIdentifierType =
(EmbeddedComponentType) associatedIdentifierType;
if ( associatedNonEncapsulatedIdentifierType.getSubtypes().length == 1 &&
EntityType.class.isInstance( associatedNonEncapsulatedIdentifierType.getSubtypes()[0] ) ) {
final EntityType otherSideEntityType =
( (EntityType) associatedNonEncapsulatedIdentifierType.getSubtypes()[0] );
if ( otherSideEntityType.isLogicalOneToOne() &&
otherSideEntityType.isReferenceToPrimaryKey() &&
otherSideEntityType.getAssociatedEntityName().equals( currentEntityPersister.getEntityName() ) ) {
// The associated entity's ID is the other side of the association.
// This side must be set to FetchMode.SELECT; otherwise,
// there will be an infinite loop because the current entity
// would need to be loaded before the associated entity can be loaded,
// but the associated entity cannot be loaded until after the current
// entity is loaded (since the current entity is the associated entity's ID).
return new FetchStrategy( fetchStrategy.getTiming(), FetchStyle.SELECT );
}
}
}
}
}
}
}
return fetchStrategy;
}