HHH-15099 - Improve handling of associations marked with @NotFound

- Keep track of NotFoundAction into mapping model
- Fix tests with erroneous assertions about `@NotFound` associations allowed to be lazy
This commit is contained in:
Steve Ebersole 2022-02-11 12:49:50 -06:00
parent de97e8e1a4
commit d52ebfb41d
49 changed files with 719 additions and 308 deletions

View File

@ -432,17 +432,16 @@ is accomplished using the `org.hibernate.annotation.NotFound` annotation with a
==== ====
Not enforcing physical foreign-keys is very discouraged. Not enforcing physical foreign-keys is very discouraged.
`@ManyToOne` and `@OneToOne` associations annotated with `@NotFound(action = NotFoundAction.IGNORE)` are `@ManyToOne` and `@OneToOne` associations annotated with `@NotFound` are
always fetched eagerly even if the `fetch` strategy is set to `FetchType.LAZY`. always fetched eagerly even if the `fetch` strategy is set to `FetchType.LAZY`.
It also affects how the association are treated as "implicit joins" in HQL. Normally It also affects how the association are treated as "implicit joins" in HQL. Normally
Hibernate would use INNER joins. With `NotFoundAction.IGNORE`, a LEFT join is Hibernate would use INNER joins. With `@NotFound`, a LEFT join is used instead.
used instead.
It also forces a join when Hibernate normally would not. Consider the HQL `.. where root.notFoundAssoc.id`. It also forces a join when Hibernate normally would not. Consider the HQL `.. where root.notFoundAssoc.id`.
Hibernate will normally use the foreign-key "referring" column(s) which does not require Hibernate will normally use the foreign-key "referring" column(s) which does not require
a join. It does this because a physical foreign-key would prevent this column to refer to a non-existent a join. It does this because a physical foreign-key would prevent this column to refer to a non-existent
associated primary-key. With `NotFoundAction.IGNORE`, we need to look at the foreign-key "target" column(s) associated primary-key. With `@NotFound`, we need to look at the foreign-key "target" column(s)
which requires the join. which requires the join.
==== ====

View File

@ -0,0 +1,43 @@
/*
* 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;
import java.util.Locale;
import jakarta.persistence.EntityNotFoundException;
/**
* Exception for {@link org.hibernate.annotations.NotFoundAction#EXCEPTION}
*
* @see org.hibernate.annotations.NotFound
*
* @author Steve Ebersole
*/
public class FetchNotFoundException extends EntityNotFoundException {
private final String entityName;
private final Object identifier;
public FetchNotFoundException(String entityName, Object identifier) {
super(
String.format(
Locale.ROOT,
"Entity `%s` with identifier value `%s` does not exist",
entityName,
identifier
)
);
this.entityName = entityName;
this.identifier = identifier;
}
public String getEntityName() {
return entityName;
}
public Object getIdentifier() {
return identifier;
}
}

View File

@ -7,27 +7,38 @@
package org.hibernate.annotations; package org.hibernate.annotations;
/** /**
* Enumerates the association fetching strategies available in Hibernate. * How the association should be fetched.
* <p>
* Whereas the JPA {@link jakarta.persistence.FetchType} enumeration provides a way to
* specify <em>when</em> an association should be fetched, this enumeration provides a
* way to express <em>how</em> it should be fetched.
* *
* Defines the "how", compared to {@link jakarta.persistence.FetchType} which defines "when"
*
* @author Steve Ebersole
* @author Emmanuel Bernard * @author Emmanuel Bernard
*/ */
public enum FetchMode { public enum FetchMode {
/** /**
* The association or collection is fetched with a separate subsequent SQL select. * Use a secondary select for each individual entity, collection, or join load.
*/ */
SELECT, SELECT( org.hibernate.FetchMode.SELECT ),
/** /**
* The association or collection is fetched using an outer join clause added to * Use an outer join to load the related entities, collections or joins.
* the initial SQL select.
*/ */
JOIN, JOIN( org.hibernate.FetchMode.JOIN ),
/** /**
* For collections and many-valued associations only. After the initial SQL select, * Available for collections only.
* all associated collections are fetched together in a single subsequent select. *
* When accessing a non-initialized collection, this fetch mode will trigger
* loading all elements of all collections of the same role for all owners
* associated with the persistence context using a single secondary select.
*/ */
SUBSELECT SUBSELECT( org.hibernate.FetchMode.SELECT );
private final org.hibernate.FetchMode hibernateFetchMode;
FetchMode(org.hibernate.FetchMode hibernateFetchMode) {
this.hibernateFetchMode = hibernateFetchMode;
}
public org.hibernate.FetchMode getHibernateFetchMode() {
return hibernateFetchMode;
}
} }

View File

@ -2415,7 +2415,7 @@ public final class AnnotationBinder {
collectionBinder.setPropertyHolder(propertyHolder); collectionBinder.setPropertyHolder(propertyHolder);
Cascade hibernateCascade = property.getAnnotation( Cascade.class ); Cascade hibernateCascade = property.getAnnotation( Cascade.class );
NotFound notFound = property.getAnnotation( NotFound.class ); NotFound notFound = property.getAnnotation( NotFound.class );
collectionBinder.setIgnoreNotFound( notFound != null && notFound.action() == NotFoundAction.IGNORE ); collectionBinder.setNotFoundAction( notFound == null ? null : notFound.action() );
collectionBinder.setCollectionType( inferredData.getProperty().getElementClass() ); collectionBinder.setCollectionType( inferredData.getProperty().getElementClass() );
collectionBinder.setAccessType( inferredData.getDefaultAccess() ); collectionBinder.setAccessType( inferredData.getDefaultAccess() );
@ -2652,7 +2652,8 @@ public final class AnnotationBinder {
|| property.isAnnotationPresent( PrimaryKeyJoinColumns.class ); || property.isAnnotationPresent( PrimaryKeyJoinColumns.class );
Cascade hibernateCascade = property.getAnnotation( Cascade.class ); Cascade hibernateCascade = property.getAnnotation( Cascade.class );
NotFound notFound = property.getAnnotation( NotFound.class ); NotFound notFound = property.getAnnotation( NotFound.class );
boolean ignoreNotFound = notFound != null && notFound.action() == NotFoundAction.IGNORE; NotFoundAction notFoundAction = notFound == null ? null : notFound.action();
// MapsId means the columns belong to the pk; // MapsId means the columns belong to the pk;
// A @MapsId association (obviously) must be non-null when the entity is first persisted. // A @MapsId association (obviously) must be non-null when the entity is first persisted.
// If a @MapsId association is not mapped with @NotFound(IGNORE), then the association // If a @MapsId association is not mapped with @NotFound(IGNORE), then the association
@ -2662,8 +2663,8 @@ public final class AnnotationBinder {
// @OneToOne(optional = true) with @PKJC makes the association optional. // @OneToOne(optional = true) with @PKJC makes the association optional.
final boolean mandatory = !ann.optional() final boolean mandatory = !ann.optional()
|| property.isAnnotationPresent( Id.class ) || property.isAnnotationPresent( Id.class )
|| property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound; || property.isAnnotationPresent( MapsId.class ) && notFoundAction != NotFoundAction.IGNORE;
matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch() ); matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), notFoundAction, ann.fetch() );
OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
JoinTable assocTable = propertyHolder.getJoinTable(property); JoinTable assocTable = propertyHolder.getJoinTable(property);
if ( assocTable != null ) { if ( assocTable != null ) {
@ -2677,7 +2678,7 @@ public final class AnnotationBinder {
joinColumns, joinColumns,
!mandatory, !mandatory,
getFetchMode( ann.fetch() ), getFetchMode( ann.fetch() ),
ignoreNotFound, notFoundAction,
onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(), onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(),
ToOneBinder.getTargetEntity(inferredData, context), ToOneBinder.getTargetEntity(inferredData, context),
propertyHolder, propertyHolder,
@ -2714,8 +2715,8 @@ public final class AnnotationBinder {
Cascade hibernateCascade = property.getAnnotation( Cascade.class ); Cascade hibernateCascade = property.getAnnotation( Cascade.class );
NotFound notFound = property.getAnnotation( NotFound.class ); NotFound notFound = property.getAnnotation( NotFound.class );
boolean ignoreNotFound = notFound != null && notFound.action() == NotFoundAction.IGNORE; NotFoundAction notFoundAction = notFound == null ? null : notFound.action();
matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch() ); matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), notFoundAction, ann.fetch() );
OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class );
JoinTable assocTable = propertyHolder.getJoinTable(property); JoinTable assocTable = propertyHolder.getJoinTable(property);
if ( assocTable != null ) { if ( assocTable != null ) {
@ -2732,12 +2733,12 @@ public final class AnnotationBinder {
// the association is optional. // the association is optional.
final boolean mandatory = !ann.optional() final boolean mandatory = !ann.optional()
|| property.isAnnotationPresent( Id.class ) || property.isAnnotationPresent( Id.class )
|| property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound; || property.isAnnotationPresent( MapsId.class ) && notFoundAction != null;
bindManyToOne( bindManyToOne(
getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist), getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist),
joinColumns, joinColumns,
!mandatory, !mandatory,
ignoreNotFound, notFoundAction,
onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(), onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(),
ToOneBinder.getTargetEntity(inferredData, context), ToOneBinder.getTargetEntity(inferredData, context),
propertyHolder, propertyHolder,
@ -3545,7 +3546,7 @@ public final class AnnotationBinder {
String cascadeStrategy, String cascadeStrategy,
AnnotatedJoinColumn[] columns, AnnotatedJoinColumn[] columns,
boolean optional, boolean optional,
boolean ignoreNotFound, NotFoundAction notFoundAction,
boolean cascadeOnDelete, boolean cascadeOnDelete,
XClass targetEntity, XClass targetEntity,
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
@ -3565,7 +3566,7 @@ public final class AnnotationBinder {
final XProperty property = inferredData.getProperty(); final XProperty property = inferredData.getProperty();
defineFetchingStrategy( value, property ); defineFetchingStrategy( value, property );
//value.setFetchMode( fetchMode ); //value.setFetchMode( fetchMode );
value.setIgnoreNotFound( ignoreNotFound ); value.setNotFoundAction( notFoundAction );
value.setCascadeDeleteEnabled( cascadeOnDelete ); value.setCascadeDeleteEnabled( cascadeOnDelete );
//value.setLazy( fetchMode != FetchMode.JOIN ); //value.setLazy( fetchMode != FetchMode.JOIN );
if ( !optional ) { if ( !optional ) {
@ -3671,6 +3672,8 @@ public final class AnnotationBinder {
Fetch fetch = property.getAnnotation( Fetch.class ); Fetch fetch = property.getAnnotation( Fetch.class );
ManyToOne manyToOne = property.getAnnotation( ManyToOne.class ); ManyToOne manyToOne = property.getAnnotation( ManyToOne.class );
OneToOne oneToOne = property.getAnnotation( OneToOne.class ); OneToOne oneToOne = property.getAnnotation( OneToOne.class );
NotFound notFound = property.getAnnotation( NotFound.class );
FetchType fetchType; FetchType fetchType;
if ( manyToOne != null ) { if ( manyToOne != null ) {
fetchType = manyToOne.fetch(); fetchType = manyToOne.fetch();
@ -3683,7 +3686,12 @@ public final class AnnotationBinder {
"Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne" "Define fetch strategy on a property not annotated with @OneToMany nor @OneToOne"
); );
} }
if ( lazy != null ) {
if ( notFound != null ) {
toOne.setLazy( false );
toOne.setUnwrapProxy( true );
}
else if ( lazy != null ) {
toOne.setLazy( !( lazy.value() == LazyToOneOption.FALSE ) ); toOne.setLazy( !( lazy.value() == LazyToOneOption.FALSE ) );
toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) ); toOne.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) );
} }
@ -3692,6 +3700,7 @@ public final class AnnotationBinder {
toOne.setUnwrapProxy( fetchType != FetchType.LAZY ); toOne.setUnwrapProxy( fetchType != FetchType.LAZY );
toOne.setUnwrapProxyImplicit( true ); toOne.setUnwrapProxyImplicit( true );
} }
if ( fetch != null ) { if ( fetch != null ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
toOne.setFetchMode( FetchMode.JOIN ); toOne.setFetchMode( FetchMode.JOIN );
@ -3718,7 +3727,7 @@ public final class AnnotationBinder {
AnnotatedJoinColumn[] joinColumns, AnnotatedJoinColumn[] joinColumns,
boolean optional, boolean optional,
FetchMode fetchMode, FetchMode fetchMode,
boolean ignoreNotFound, NotFoundAction notFoundAction,
boolean cascadeOnDelete, boolean cascadeOnDelete,
XClass targetEntity, XClass targetEntity,
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
@ -3769,7 +3778,7 @@ public final class AnnotationBinder {
propertyHolder, propertyHolder,
inferredData, inferredData,
targetEntity, targetEntity,
ignoreNotFound, notFoundAction,
cascadeOnDelete, cascadeOnDelete,
optional, optional,
cascadeStrategy, cascadeStrategy,
@ -3789,7 +3798,7 @@ public final class AnnotationBinder {
else { else {
//has a FK on the table //has a FK on the table
bindManyToOne( bindManyToOne(
cascadeStrategy, joinColumns, optional, ignoreNotFound, cascadeOnDelete, cascadeStrategy, joinColumns, optional, notFoundAction, cascadeOnDelete,
targetEntity, targetEntity,
propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass, propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass,
propertyBinder, context propertyBinder, context
@ -4137,9 +4146,9 @@ public final class AnnotationBinder {
private static void matchIgnoreNotFoundWithFetchType( private static void matchIgnoreNotFoundWithFetchType(
String entity, String entity,
String association, String association,
boolean ignoreNotFound, NotFoundAction notFoundAction,
FetchType fetchType) { FetchType fetchType) {
if ( ignoreNotFound && fetchType == FetchType.LAZY ) { if ( notFoundAction != null && fetchType == FetchType.LAZY ) {
LOG.ignoreNotFoundWithFetchTypeLazy( entity, association ); LOG.ignoreNotFoundWithFetchTypeLazy( entity, association );
} }
} }

View File

@ -14,6 +14,7 @@ import jakarta.persistence.JoinColumns;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.LazyGroup;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.cfg.annotations.PropertyBinder; import org.hibernate.cfg.annotations.PropertyBinder;
@ -39,7 +40,7 @@ public class OneToOneSecondPass implements SecondPass {
private final String ownerEntity; private final String ownerEntity;
private final String ownerProperty; private final String ownerProperty;
private final PropertyHolder propertyHolder; private final PropertyHolder propertyHolder;
private final boolean ignoreNotFound; private final NotFoundAction notFoundAction;
private final PropertyData inferredData; private final PropertyData inferredData;
private final XClass targetEntity; private final XClass targetEntity;
private final boolean cascadeOnDelete; private final boolean cascadeOnDelete;
@ -55,7 +56,7 @@ public class OneToOneSecondPass implements SecondPass {
PropertyHolder propertyHolder, PropertyHolder propertyHolder,
PropertyData inferredData, PropertyData inferredData,
XClass targetEntity, XClass targetEntity,
boolean ignoreNotFound, NotFoundAction notFoundAction,
boolean cascadeOnDelete, boolean cascadeOnDelete,
boolean optional, boolean optional,
String cascadeStrategy, String cascadeStrategy,
@ -66,7 +67,7 @@ public class OneToOneSecondPass implements SecondPass {
this.mappedBy = mappedBy; this.mappedBy = mappedBy;
this.propertyHolder = propertyHolder; this.propertyHolder = propertyHolder;
this.buildingContext = buildingContext; this.buildingContext = buildingContext;
this.ignoreNotFound = ignoreNotFound; this.notFoundAction = notFoundAction;
this.inferredData = inferredData; this.inferredData = inferredData;
this.targetEntity = targetEntity; this.targetEntity = targetEntity;
this.cascadeOnDelete = cascadeOnDelete; this.cascadeOnDelete = cascadeOnDelete;
@ -187,7 +188,7 @@ public class OneToOneSecondPass implements SecondPass {
); );
ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() ); ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() );
//FIXME use ignore not found here //FIXME use ignore not found here
manyToOne.setIgnoreNotFound( ignoreNotFound ); manyToOne.setNotFoundAction( notFoundAction );
manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() ); manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() );
manyToOne.setFetchMode( value.getFetchMode() ); manyToOne.setFetchMode( value.getFetchMode() );
manyToOne.setLazy( value.isLazy() ); manyToOne.setLazy( value.isLazy() );

View File

@ -13,6 +13,22 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.function.Supplier; import java.util.function.Supplier;
import jakarta.persistence.Access;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.MapKey;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderColumn;
import org.hibernate.AnnotationException; import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
@ -44,7 +60,8 @@ import org.hibernate.annotations.ListIndexJdbcType;
import org.hibernate.annotations.ListIndexJdbcTypeCode; import org.hibernate.annotations.ListIndexJdbcTypeCode;
import org.hibernate.annotations.Loader; import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ManyToAny; import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.MapKeyCustomCompositeType; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction; import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.annotations.OptimisticLock; import org.hibernate.annotations.OptimisticLock;
@ -115,23 +132,6 @@ import org.hibernate.usertype.UserCollectionType;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import jakarta.persistence.Access;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Embeddable;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.MapKey;
import jakarta.persistence.MapKeyColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderColumn;
import static jakarta.persistence.AccessType.PROPERTY; import static jakarta.persistence.AccessType.PROPERTY;
import static org.hibernate.cfg.AnnotatedColumn.checkPropertyConsistency; import static org.hibernate.cfg.AnnotatedColumn.checkPropertyConsistency;
import static org.hibernate.cfg.AnnotationBinder.fillComponent; import static org.hibernate.cfg.AnnotationBinder.fillComponent;
@ -186,7 +186,7 @@ public abstract class CollectionBinder {
private AnnotatedColumn[] elementColumns; private AnnotatedColumn[] elementColumns;
private boolean isEmbedded; private boolean isEmbedded;
private XProperty property; private XProperty property;
private boolean ignoreNotFound; private NotFoundAction notFoundAction;
private TableBinder tableBinder; private TableBinder tableBinder;
private AnnotatedColumn[] mapKeyColumns; private AnnotatedColumn[] mapKeyColumns;
private AnnotatedJoinColumn[] mapKeyManyToManyColumns; private AnnotatedJoinColumn[] mapKeyManyToManyColumns;
@ -719,7 +719,7 @@ public abstract class CollectionBinder {
isEmbedded, isEmbedded,
property, property,
collectionType, collectionType,
ignoreNotFound, notFoundAction,
oneToMany, oneToMany,
tableBinder, tableBinder,
buildingContext buildingContext
@ -970,6 +970,8 @@ public abstract class CollectionBinder {
ManyToMany manyToMany = property.getAnnotation( ManyToMany.class ); ManyToMany manyToMany = property.getAnnotation( ManyToMany.class );
ElementCollection elementCollection = property.getAnnotation( ElementCollection.class ); ElementCollection elementCollection = property.getAnnotation( ElementCollection.class );
ManyToAny manyToAny = property.getAnnotation( ManyToAny.class ); ManyToAny manyToAny = property.getAnnotation( ManyToAny.class );
NotFound notFound = property.getAnnotation( NotFound.class );
FetchType fetchType; FetchType fetchType;
if ( oneToMany != null ) { if ( oneToMany != null ) {
fetchType = oneToMany.fetch(); fetchType = oneToMany.fetch();
@ -985,36 +987,58 @@ public abstract class CollectionBinder {
} }
else { else {
throw new AssertionFailure( throw new AssertionFailure(
"Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @ElementCollection" "Define fetch strategy on a property not annotated with @ManyToOne nor @OneToMany nor @CollectionOfElements"
); );
} }
if ( lazy != null ) { if ( notFound != null ) {
collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) ); collection.setLazy( false );
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
} if ( lazy != null ) {
else { collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
collection.setLazy( fetchType == FetchType.LAZY );
collection.setExtraLazy( false );
}
if ( fetch != null ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
collection.setFetchMode( FetchMode.JOIN );
collection.setLazy( false );
} }
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
collection.setFetchMode( FetchMode.SELECT ); if ( fetch != null ) {
} if ( fetch.value() != null ) {
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { collection.setFetchMode( fetch.value().getHibernateFetchMode() );
collection.setFetchMode( FetchMode.SELECT ); if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
collection.setSubselectLoadable( true ); collection.setSubselectLoadable( true );
collection.getOwner().setSubselectLoadableCollections( true ); collection.getOwner().setSubselectLoadableCollections( true );
}
}
} }
else { else {
throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
} }
} }
else { else {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); if ( lazy != null ) {
collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) );
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
}
else {
collection.setLazy( fetchType == FetchType.LAZY );
collection.setExtraLazy( false );
}
if ( fetch != null ) {
if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) {
collection.setFetchMode( FetchMode.JOIN );
collection.setLazy( false );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SELECT ) {
collection.setFetchMode( FetchMode.SELECT );
}
else if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) {
collection.setFetchMode( FetchMode.SELECT );
collection.setSubselectLoadable( true );
collection.getOwner().setSubselectLoadableCollections( true );
}
else {
throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() );
}
}
else {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
}
} }
} }
@ -1044,7 +1068,7 @@ public abstract class CollectionBinder {
final boolean isEmbedded, final boolean isEmbedded,
final XProperty property, final XProperty property,
final XClass collType, final XClass collType,
final boolean ignoreNotFound, final NotFoundAction notFoundAction,
final boolean unique, final boolean unique,
final TableBinder assocTableBinder, final TableBinder assocTableBinder,
final MetadataBuildingContext buildingContext) { final MetadataBuildingContext buildingContext) {
@ -1062,7 +1086,7 @@ public abstract class CollectionBinder {
property, property,
unique, unique,
assocTableBinder, assocTableBinder,
ignoreNotFound, notFoundAction,
buildingContext buildingContext
); );
} }
@ -1083,7 +1107,7 @@ public abstract class CollectionBinder {
XProperty property, XProperty property,
boolean unique, boolean unique,
TableBinder associationTableBinder, TableBinder associationTableBinder,
boolean ignoreNotFound, NotFoundAction notFoundAction,
MetadataBuildingContext buildingContext) { MetadataBuildingContext buildingContext) {
PersistentClass persistentClass = persistentClasses.get( collType.getName() ); PersistentClass persistentClass = persistentClasses.get( collType.getName() );
boolean reversePropertyInJoin = false; boolean reversePropertyInJoin = false;
@ -1118,7 +1142,7 @@ public abstract class CollectionBinder {
fkJoinColumns, fkJoinColumns,
collType, collType,
cascadeDeleteEnabled, cascadeDeleteEnabled,
ignoreNotFound, notFoundAction,
buildingContext, buildingContext,
inheritanceStatePerClass inheritanceStatePerClass
); );
@ -1132,8 +1156,10 @@ public abstract class CollectionBinder {
keyColumns, keyColumns,
inverseColumns, inverseColumns,
elementColumns, elementColumns,
isEmbedded, collType, isEmbedded,
ignoreNotFound, unique, collType,
notFoundAction,
unique,
cascadeDeleteEnabled, cascadeDeleteEnabled,
associationTableBinder, associationTableBinder,
property, property,
@ -1150,7 +1176,7 @@ public abstract class CollectionBinder {
AnnotatedJoinColumn[] fkJoinColumns, AnnotatedJoinColumn[] fkJoinColumns,
XClass collectionType, XClass collectionType,
boolean cascadeDeleteEnabled, boolean cascadeDeleteEnabled,
boolean ignoreNotFound, NotFoundAction notFoundAction,
MetadataBuildingContext buildingContext, MetadataBuildingContext buildingContext,
Map<XClass, InheritanceState> inheritanceStatePerClass) { Map<XClass, InheritanceState> inheritanceStatePerClass) {
@ -1166,7 +1192,7 @@ public abstract class CollectionBinder {
new org.hibernate.mapping.OneToMany( buildingContext, collection.getOwner() ); new org.hibernate.mapping.OneToMany( buildingContext, collection.getOwner() );
collection.setElement( oneToMany ); collection.setElement( oneToMany );
oneToMany.setReferencedEntityName( collectionType.getName() ); oneToMany.setReferencedEntityName( collectionType.getName() );
oneToMany.setIgnoreNotFound( ignoreNotFound ); oneToMany.setNotFoundAction( notFoundAction );
String assocClass = oneToMany.getReferencedEntityName(); String assocClass = oneToMany.getReferencedEntityName();
PersistentClass associatedClass = persistentClasses.get( assocClass ); PersistentClass associatedClass = persistentClasses.get( assocClass );
@ -1589,7 +1615,8 @@ public abstract class CollectionBinder {
AnnotatedColumn[] elementColumns, AnnotatedColumn[] elementColumns,
boolean isEmbedded, boolean isEmbedded,
XClass collType, XClass collType,
boolean ignoreNotFound, boolean unique, NotFoundAction notFoundAction,
boolean unique,
boolean cascadeDeleteEnabled, boolean cascadeDeleteEnabled,
TableBinder associationTableBinder, TableBinder associationTableBinder,
XProperty property, XProperty property,
@ -1656,7 +1683,7 @@ public abstract class CollectionBinder {
element = handleCollectionOfEntities( element = handleCollectionOfEntities(
collValue, collValue,
collType, collType,
ignoreNotFound, notFoundAction,
property, property,
buildingContext, buildingContext,
collectionEntity, collectionEntity,
@ -1841,7 +1868,14 @@ public abstract class CollectionBinder {
} }
} }
private ManyToOne handleCollectionOfEntities(Collection collValue, XClass collType, boolean ignoreNotFound, XProperty property, MetadataBuildingContext buildingContext, PersistentClass collectionEntity, String hqlOrderBy) { private ManyToOne handleCollectionOfEntities(
Collection collValue,
XClass collType,
NotFoundAction notFoundAction,
XProperty property,
MetadataBuildingContext buildingContext,
PersistentClass collectionEntity,
String hqlOrderBy) {
ManyToOne element; ManyToOne element;
element = new ManyToOne(buildingContext, collValue.getCollectionTable() ); element = new ManyToOne(buildingContext, collValue.getCollectionTable() );
collValue.setElement( element ); collValue.setElement( element );
@ -1851,7 +1885,7 @@ public abstract class CollectionBinder {
//make the second join non lazy //make the second join non lazy
element.setFetchMode( FetchMode.JOIN ); element.setFetchMode( FetchMode.JOIN );
element.setLazy( false ); element.setLazy( false );
element.setIgnoreNotFound(ignoreNotFound); element.setNotFoundAction( notFoundAction );
// as per 11.1.38 of JPA 2.0 spec, default to primary key if no column is specified by @OrderBy. // as per 11.1.38 of JPA 2.0 spec, default to primary key if no column is specified by @OrderBy.
if ( hqlOrderBy != null ) { if ( hqlOrderBy != null ) {
collValue.setManyToManyOrdering( collValue.setManyToManyOrdering(
@ -2276,8 +2310,18 @@ public abstract class CollectionBinder {
this.property = property; this.property = property;
} }
public NotFoundAction getNotFoundAction() {
return notFoundAction;
}
public void setNotFoundAction(NotFoundAction notFoundAction) {
this.notFoundAction = notFoundAction;
}
public void setIgnoreNotFound(boolean ignoreNotFound) { public void setIgnoreNotFound(boolean ignoreNotFound) {
this.ignoreNotFound = ignoreNotFound; this.notFoundAction = ignoreNotFound
? NotFoundAction.IGNORE
: null;
} }
public void setMapKeyColumns(AnnotatedColumn[] mapKeyColumns) { public void setMapKeyColumns(AnnotatedColumn[] mapKeyColumns) {

View File

@ -12,6 +12,7 @@ import java.util.function.Supplier;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.CollectionId; import org.hibernate.annotations.CollectionId;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
@ -61,7 +62,7 @@ public class IdBagBinder extends BagBinder {
XProperty property, XProperty property,
boolean unique, boolean unique,
TableBinder associationTableBinder, TableBinder associationTableBinder,
boolean ignoreNotFound, NotFoundAction notFoundAction,
MetadataBuildingContext buildingContext) { MetadataBuildingContext buildingContext) {
boolean result = super.bindStarToManySecondPass( boolean result = super.bindStarToManySecondPass(
persistentClasses, persistentClasses,
@ -74,7 +75,7 @@ public class IdBagBinder extends BagBinder {
property, property,
unique, unique,
associationTableBinder, associationTableBinder,
ignoreNotFound, notFoundAction,
getBuildingContext() getBuildingContext()
); );

View File

@ -10,6 +10,7 @@ import java.util.Map;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.OrderBy; import org.hibernate.annotations.OrderBy;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
@ -71,7 +72,7 @@ public class ListBinder extends CollectionBinder {
final boolean isEmbedded, final boolean isEmbedded,
final XProperty property, final XProperty property,
final XClass collType, final XClass collType,
final boolean ignoreNotFound, final NotFoundAction notFoundAction,
final boolean unique, final boolean unique,
final TableBinder assocTableBinder, final TableBinder assocTableBinder,
final MetadataBuildingContext buildingContext) { final MetadataBuildingContext buildingContext) {
@ -90,7 +91,7 @@ public class ListBinder extends CollectionBinder {
property, property,
unique, unique,
assocTableBinder, assocTableBinder,
ignoreNotFound, notFoundAction,
buildingContext buildingContext
); );
bindIndex( property, collType, buildingContext ); bindIndex( property, collType, buildingContext );

View File

@ -16,6 +16,7 @@ import org.hibernate.AssertionFailure;
import org.hibernate.FetchMode; import org.hibernate.FetchMode;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.MapKeyCustomCompositeType; import org.hibernate.annotations.MapKeyCustomCompositeType;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.spi.BootstrapContext; import org.hibernate.boot.spi.BootstrapContext;
@ -96,7 +97,7 @@ public class MapBinder extends CollectionBinder {
final boolean isEmbedded, final boolean isEmbedded,
final XProperty property, final XProperty property,
final XClass collType, final XClass collType,
final boolean ignoreNotFound, final NotFoundAction notFoundAction,
final boolean unique, final boolean unique,
final TableBinder assocTableBinder, final TableBinder assocTableBinder,
final MetadataBuildingContext buildingContext) { final MetadataBuildingContext buildingContext) {
@ -105,7 +106,7 @@ public class MapBinder extends CollectionBinder {
throws MappingException { throws MappingException {
bindStarToManySecondPass( bindStarToManySecondPass(
persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns, persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns,
isEmbedded, property, unique, assocTableBinder, ignoreNotFound, buildingContext isEmbedded, property, unique, assocTableBinder, notFoundAction, buildingContext
); );
bindKeyFromAssociationTable( bindKeyFromAssociationTable(
collType, persistentClasses, mapKeyPropertyName, property, isEmbedded, buildingContext, collType, persistentClasses, mapKeyPropertyName, property, isEmbedded, buildingContext,

View File

@ -1715,8 +1715,8 @@ public interface CoreMessageLogger extends BasicLogger {
void usingJtaPlatform(String jtaPlatformClassName); void usingJtaPlatform(String jtaPlatformClassName);
@LogMessage(level = WARN) @LogMessage(level = WARN)
@Message(value = "The [%2$s] association in the [%1$s] entity uses both @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY. " + @Message(value = "`.%1$s.%2$s` uses both @NotFound and FetchType.LAZY. @ManyToOne and " +
"The NotFoundAction.IGNORE @ManyToOne and @OneToOne associations are always fetched eagerly.", id = 491) "@OneToOne associations mapped with `@NotFound` are forced to EAGER fetching.", id = 491)
void ignoreNotFoundWithFetchTypeLazy(String entity, String association); void ignoreNotFoundWithFetchTypeLazy(String entity, String association);
@LogMessage(level = INFO) @LogMessage(level = INFO)

View File

@ -10,6 +10,7 @@ import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.type.EntityType; import org.hibernate.type.EntityType;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -19,8 +20,8 @@ import org.hibernate.type.Type;
* @author Gavin King * @author Gavin King
*/ */
public class ManyToOne extends ToOne { public class ManyToOne extends ToOne {
private boolean ignoreNotFound;
private boolean isLogicalOneToOne; private boolean isLogicalOneToOne;
private NotFoundAction notFoundAction;
private Type resolvedType; private Type resolvedType;
@ -30,7 +31,7 @@ public class ManyToOne extends ToOne {
private ManyToOne(ManyToOne original) { private ManyToOne(ManyToOne original) {
super( original ); super( original );
this.ignoreNotFound = original.ignoreNotFound; this.notFoundAction = original.notFoundAction;
this.isLogicalOneToOne = original.isLogicalOneToOne; this.isLogicalOneToOne = original.isLogicalOneToOne;
} }
@ -113,12 +114,22 @@ public class ManyToOne extends ToOne {
return visitor.accept(this); return visitor.accept(this);
} }
public NotFoundAction getNotFoundAction() {
return notFoundAction;
}
public void setNotFoundAction(NotFoundAction notFoundAction) {
this.notFoundAction = notFoundAction;
}
public boolean isIgnoreNotFound() { public boolean isIgnoreNotFound() {
return ignoreNotFound; return notFoundAction == NotFoundAction.IGNORE;
} }
public void setIgnoreNotFound(boolean ignoreNotFound) { public void setIgnoreNotFound(boolean ignoreNotFound) {
this.ignoreNotFound = ignoreNotFound; this.notFoundAction = ignoreNotFound
? NotFoundAction.IGNORE
: null;
} }
public void markAsLogicalOneToOne() { public void markAsLogicalOneToOne() {

View File

@ -12,6 +12,7 @@ import java.util.Objects;
import org.hibernate.FetchMode; import org.hibernate.FetchMode;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.Mapping;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
@ -29,7 +30,7 @@ public class OneToMany implements Value {
private String referencedEntityName; private String referencedEntityName;
private PersistentClass associatedClass; private PersistentClass associatedClass;
private boolean ignoreNotFound; private NotFoundAction notFoundAction;
public OneToMany(MetadataBuildingContext buildingContext, PersistentClass owner) throws MappingException { public OneToMany(MetadataBuildingContext buildingContext, PersistentClass owner) throws MappingException {
this.buildingContext = buildingContext; this.buildingContext = buildingContext;
@ -41,7 +42,7 @@ public class OneToMany implements Value {
this.referencingTable = original.referencingTable; this.referencingTable = original.referencingTable;
this.referencedEntityName = original.referencedEntityName; this.referencedEntityName = original.referencedEntityName;
this.associatedClass = original.associatedClass; this.associatedClass = original.associatedClass;
this.ignoreNotFound = original.ignoreNotFound; this.notFoundAction = original.notFoundAction;
} }
@Override @Override
@ -197,12 +198,22 @@ public class OneToMany implements Value {
return false; return false;
} }
public NotFoundAction getNotFoundAction() {
return notFoundAction;
}
public void setNotFoundAction(NotFoundAction notFoundAction) {
this.notFoundAction = notFoundAction;
}
public boolean isIgnoreNotFound() { public boolean isIgnoreNotFound() {
return ignoreNotFound; return notFoundAction == NotFoundAction.IGNORE;
} }
public void setIgnoreNotFound(boolean ignoreNotFound) { public void setIgnoreNotFound(boolean ignoreNotFound) {
this.ignoreNotFound = ignoreNotFound; this.notFoundAction = ignoreNotFound
? NotFoundAction.IGNORE
: null;
} }
} }

View File

@ -277,6 +277,7 @@ public class BasicAttributeMapping
getContainingTableExpression(), getContainingTableExpression(),
allowFkOptimization allowFkOptimization
); );
return expressionResolver.resolveSqlSelection( return expressionResolver.resolveSqlSelection(
expressionResolver.resolveSqlExpression( expressionResolver.resolveSqlExpression(
SqlExpressionResolver.createColumnReferenceKey( SqlExpressionResolver.createColumnReferenceKey(

View File

@ -13,6 +13,7 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
@ -82,6 +83,7 @@ public class EntityCollectionPart
private final Nature nature; private final Nature nature;
private final EntityMappingType entityMappingType; private final EntityMappingType entityMappingType;
private final Set<String> targetKeyPropertyNames; private final Set<String> targetKeyPropertyNames;
private final NotFoundAction notFoundAction;
private ModelPart fkTargetModelPart; private ModelPart fkTargetModelPart;
private ForeignKeyDescriptor fkDescriptor; private ForeignKeyDescriptor fkDescriptor;
@ -90,12 +92,15 @@ public class EntityCollectionPart
CollectionPersister collectionDescriptor, CollectionPersister collectionDescriptor,
Nature nature, Nature nature,
Value bootModelValue, Value bootModelValue,
NotFoundAction notFoundAction,
EntityMappingType entityMappingType, EntityMappingType entityMappingType,
MappingModelCreationProcess creationProcess) { MappingModelCreationProcess creationProcess) {
this.notFoundAction = notFoundAction;
this.navigableRole = collectionDescriptor.getNavigableRole().appendContainer( nature.getName() ); this.navigableRole = collectionDescriptor.getNavigableRole().appendContainer( nature.getName() );
this.collectionDescriptor = collectionDescriptor; this.collectionDescriptor = collectionDescriptor;
this.nature = nature; this.nature = nature;
this.entityMappingType = entityMappingType; this.entityMappingType = entityMappingType;
final String referencedPropertyName; final String referencedPropertyName;
final PersistentClass entityBinding; final PersistentClass entityBinding;
if ( bootModelValue instanceof OneToMany ) { if ( bootModelValue instanceof OneToMany ) {
@ -112,6 +117,7 @@ public class EntityCollectionPart
entityBinding = toOne.getBuildingContext().getMetadataCollector() entityBinding = toOne.getBuildingContext().getMetadataCollector()
.getEntityBinding( entityMappingType.getEntityName() ); .getEntityBinding( entityMappingType.getEntityName() );
} }
if ( referencedPropertyName == null ) { if ( referencedPropertyName == null ) {
final Set<String> targetKeyPropertyNames = new HashSet<>( 2 ); final Set<String> targetKeyPropertyNames = new HashSet<>( 2 );
targetKeyPropertyNames.add( EntityIdentifierMapping.ROLE_LOCAL_NAME ); targetKeyPropertyNames.add( EntityIdentifierMapping.ROLE_LOCAL_NAME );
@ -460,7 +466,8 @@ public class EntityCollectionPart
boolean selected, boolean selected,
String resultVariable, String resultVariable,
DomainResultCreationState creationState) { DomainResultCreationState creationState) {
final boolean added = creationState.registerVisitedAssociationKey( getForeignKeyDescriptor().getAssociationKey() ); final ForeignKeyDescriptor foreignKeyDescriptor = getForeignKeyDescriptor();
final boolean added = creationState.registerVisitedAssociationKey( foreignKeyDescriptor.getAssociationKey() );
final TableGroup partTableGroup = resolveTableGroup( fetchablePath, creationState ); final TableGroup partTableGroup = resolveTableGroup( fetchablePath, creationState );
final EntityFetchJoinedImpl fetch = new EntityFetchJoinedImpl( final EntityFetchJoinedImpl fetch = new EntityFetchJoinedImpl(
@ -470,9 +477,11 @@ public class EntityCollectionPart
fetchablePath, fetchablePath,
creationState creationState
); );
if ( added ) { if ( added ) {
creationState.removeVisitedAssociationKey( getForeignKeyDescriptor().getAssociationKey() ); creationState.removeVisitedAssociationKey( foreignKeyDescriptor.getAssociationKey() );
} }
return fetch; return fetch;
} }

View File

@ -16,6 +16,7 @@ import org.hibernate.FetchMode;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.SharedSessionContract; import org.hibernate.SharedSessionContract;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.boot.model.relational.SqlStringGenerationContext;
import org.hibernate.collection.internal.StandardArraySemantics; import org.hibernate.collection.internal.StandardArraySemantics;
import org.hibernate.collection.internal.StandardBagSemantics; import org.hibernate.collection.internal.StandardBagSemantics;
@ -1376,6 +1377,7 @@ public class MappingModelCreationHelper {
collectionDescriptor, collectionDescriptor,
CollectionPart.Nature.INDEX, CollectionPart.Nature.INDEX,
bootMapKeyDescriptor, bootMapKeyDescriptor,
null,
associatedEntity, associatedEntity,
creationProcess creationProcess
); );
@ -1473,10 +1475,22 @@ public class MappingModelCreationHelper {
final EntityType elementEntityType = (EntityType) collectionDescriptor.getElementType(); final EntityType elementEntityType = (EntityType) collectionDescriptor.getElementType();
final EntityPersister associatedEntity = creationProcess.getEntityPersister( elementEntityType.getAssociatedEntityName() ); final EntityPersister associatedEntity = creationProcess.getEntityPersister( elementEntityType.getAssociatedEntityName() );
final NotFoundAction notFoundAction;
if ( element instanceof ManyToOne ) {
notFoundAction = ( (ManyToOne) element ).getNotFoundAction();
}
else if ( element instanceof OneToMany ) {
notFoundAction = ( (OneToMany) element ).getNotFoundAction();
}
else {
throw new IllegalArgumentException( "Just seeing if this happens" );
}
final EntityCollectionPart elementDescriptor = new EntityCollectionPart( final EntityCollectionPart elementDescriptor = new EntityCollectionPart(
collectionDescriptor, collectionDescriptor,
CollectionPart.Nature.ELEMENT, CollectionPart.Nature.ELEMENT,
bootDescriptor.getElement(), bootDescriptor.getElement(),
notFoundAction,
associatedEntity, associatedEntity,
creationProcess creationProcess
); );

View File

@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping.internal;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.IntFunction; import java.util.function.IntFunction;
@ -41,6 +42,7 @@ import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableGroupProducer; import org.hibernate.sql.ast.tree.from.TableGroupProducer;
import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.UnknownTableReferenceException;
import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate; import org.hibernate.sql.ast.tree.predicate.ComparisonPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate; import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResult;
@ -230,10 +232,25 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver();
final TableReference tableReference = tableGroup.resolveTableReference( final TableReference tableReference;
navigablePath.append( getTargetPart().getFetchableName() ), try {
selectableMapping.getContainingTableExpression() tableReference = tableGroup.resolveTableReference(
); navigablePath.append( getTargetPart().getFetchableName() ),
selectableMapping.getContainingTableExpression()
);
}
catch (IllegalStateException tableNotFoundException) {
throw new UnknownTableReferenceException(
selectableMapping.getContainingTableExpression(),
String.format(
Locale.ROOT,
"Unable to determine TableReference (`%s`) for `%s`",
selectableMapping.getContainingTableExpression(),
getNavigableRole().getFullPath()
)
);
}
final String identificationVariable = tableReference.getIdentificationVariable(); final String identificationVariable = tableReference.getIdentificationVariable();
final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( final SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection(

View File

@ -15,6 +15,7 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchStyle;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -51,9 +52,9 @@ import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.property.access.spi.PropertyAccess; import org.hibernate.property.access.spi.PropertyAccess;
import org.hibernate.query.sqm.spi.EntityIdentifierNavigablePath;
import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.spi.TreatedNavigablePath; import org.hibernate.query.spi.TreatedNavigablePath;
import org.hibernate.query.sqm.spi.EntityIdentifierNavigablePath;
import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.SqlAstJoinType;
import org.hibernate.sql.ast.spi.FromClauseAccess; import org.hibernate.sql.ast.spi.FromClauseAccess;
@ -132,7 +133,7 @@ public class ToOneAttributeMapping
*/ */
private final boolean isKeyTableNullable; private final boolean isKeyTableNullable;
private final boolean isConstrained; private final boolean isConstrained;
private final boolean isIgnoreNotFound; private final NotFoundAction notFoundAction;
private final boolean unwrapProxy; private final boolean unwrapProxy;
private final boolean isOptional; private final boolean isOptional;
private final EntityMappingType entityMappingType; private final EntityMappingType entityMappingType;
@ -193,7 +194,7 @@ public class ToOneAttributeMapping
name, name,
stateArrayPosition, stateArrayPosition,
attributeMetadataAccess, attributeMetadataAccess,
mappedFetchTiming, adjustFetchTiming( mappedFetchTiming, bootValue ),
mappedFetchStyle, mappedFetchStyle,
declaringType, declaringType,
propertyAccess, propertyAccess,
@ -208,7 +209,7 @@ public class ToOneAttributeMapping
if ( bootValue instanceof ManyToOne ) { if ( bootValue instanceof ManyToOne ) {
final ManyToOne manyToOne = (ManyToOne) bootValue; final ManyToOne manyToOne = (ManyToOne) bootValue;
this.isIgnoreNotFound = ( (ManyToOne) bootValue ).isIgnoreNotFound(); this.notFoundAction = ( (ManyToOne) bootValue ).getNotFoundAction();
if ( manyToOne.isLogicalOneToOne() ) { if ( manyToOne.isLogicalOneToOne() ) {
cardinality = Cardinality.LOGICAL_ONE_TO_ONE; cardinality = Cardinality.LOGICAL_ONE_TO_ONE;
} }
@ -350,7 +351,7 @@ public class ToOneAttributeMapping
else { else {
this.bidirectionalAttributeName = bidirectionalAttributeName; this.bidirectionalAttributeName = bidirectionalAttributeName;
} }
isIgnoreNotFound = isNullable(); notFoundAction = isNullable() ? NotFoundAction.IGNORE : null;
isKeyTableNullable = isNullable(); isKeyTableNullable = isNullable();
isOptional = ! bootValue.isConstrained(); isOptional = ! bootValue.isConstrained();
} }
@ -454,6 +455,15 @@ public class ToOneAttributeMapping
} }
} }
private static FetchTiming adjustFetchTiming(FetchTiming mappedFetchTiming, ToOne bootValue) {
if ( bootValue instanceof ManyToOne ) {
if ( ( (ManyToOne) bootValue ).getNotFoundAction() != null ) {
return FetchTiming.IMMEDIATE;
}
}
return mappedFetchTiming;
}
private TableGroupProducer resolveDeclaringTableGroupProducer(EntityPersister declaringEntityPersister) { private TableGroupProducer resolveDeclaringTableGroupProducer(EntityPersister declaringEntityPersister) {
// Also handle cases where a collection contains an embeddable, that contains an association // Also handle cases where a collection contains an embeddable, that contains an association
NavigableRole parentRole = getNavigableRole().getParent(); NavigableRole parentRole = getNavigableRole().getParent();
@ -497,7 +507,7 @@ public class ToOneAttributeMapping
this.isNullable = original.isNullable; this.isNullable = original.isNullable;
this.isKeyTableNullable = original.isKeyTableNullable; this.isKeyTableNullable = original.isKeyTableNullable;
this.isOptional = original.isOptional; this.isOptional = original.isOptional;
this.isIgnoreNotFound = original.isIgnoreNotFound; this.notFoundAction = original.notFoundAction;
this.unwrapProxy = original.unwrapProxy; this.unwrapProxy = original.unwrapProxy;
this.entityMappingType = original.entityMappingType; this.entityMappingType = original.entityMappingType;
this.referencedPropertyName = original.referencedPropertyName; this.referencedPropertyName = original.referencedPropertyName;
@ -606,7 +616,8 @@ public class ToOneAttributeMapping
// We can only use the parent table group if the FK is located there and ignoreNotFound is false // We can only use the parent table group if the FK is located there and ignoreNotFound is false
// If this is not the case, the FK is not constrained or on a join/secondary table, so we need a join // If this is not the case, the FK is not constrained or on a join/secondary table, so we need a join
this.canUseParentTableGroup = !isIgnoreNotFound && sideNature == ForeignKeyDescriptor.Nature.KEY this.canUseParentTableGroup = notFoundAction != NotFoundAction.IGNORE
&& sideNature == ForeignKeyDescriptor.Nature.KEY
&& declaringTableGroupProducer.containsTableReference( identifyingColumnsTableExpression ); && declaringTableGroupProducer.containsTableReference( identifyingColumnsTableExpression );
} }
@ -928,23 +939,24 @@ public class ToOneAttributeMapping
} }
// The referencedNavigablePath can be null if this is a collection initialization // The referencedNavigablePath can be null if this is a collection initialization
if ( referencedNavigablePath != null ) { if ( referencedNavigablePath != null ) {
// If this is the key side, we must ensure that the key is not null, so we create a domain result for it
// In the CircularBiDirectionalFetchImpl we return null if the key is null instead of the bidirectional value
final DomainResult<?> keyDomainResult;
// For now, we don't do this if the key table is nullable to avoid an additional join
if ( sideNature == ForeignKeyDescriptor.Nature.KEY && !isKeyTableNullable ) {
keyDomainResult = foreignKeyDescriptor.createKeyDomainResult(
fetchablePath,
creationState.getSqlAstCreationState()
.getFromClauseAccess()
.findTableGroup( realFetchParent.getNavigablePath() ),
creationState
);
}
else {
keyDomainResult = null;
}
if ( hasBidirectionalFetchParent ) { if ( hasBidirectionalFetchParent ) {
// If this is the key side, we must ensure that the key is not null, so we create a domain result for it
// In the CircularBiDirectionalFetchImpl we return null if the key is null instead of the bidirectional value
final DomainResult<?> keyDomainResult;
// For now, we don't do this if the key table is nullable to avoid an additional join
if ( sideNature == ForeignKeyDescriptor.Nature.KEY && !isKeyTableNullable ) {
keyDomainResult = foreignKeyDescriptor.createKeyDomainResult(
fetchablePath,
creationState.getSqlAstCreationState()
.getFromClauseAccess()
.findTableGroup( realFetchParent.getNavigablePath() ),
creationState
);
}
else {
keyDomainResult = null;
}
return new CircularBiDirectionalFetchImpl( return new CircularBiDirectionalFetchImpl(
FetchTiming.IMMEDIATE, FetchTiming.IMMEDIATE,
fetchablePath, fetchablePath,
@ -968,6 +980,7 @@ public class ToOneAttributeMapping
fetchParent, fetchParent,
this, this,
tableGroup, tableGroup,
keyDomainResult,
fetchablePath, fetchablePath,
creationState creationState
); );
@ -1012,9 +1025,7 @@ public class ToOneAttributeMapping
final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState();
final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess(); final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess();
final TableGroup parentTableGroup = fromClauseAccess.getTableGroup( final TableGroup parentTableGroup = fromClauseAccess.getTableGroup( fetchParent.getNavigablePath() );
fetchParent.getNavigablePath()
);
final NavigablePath parentNavigablePath = fetchablePath.getParent(); final NavigablePath parentNavigablePath = fetchablePath.getParent();
assert parentNavigablePath.equals( fetchParent.getNavigablePath() ) assert parentNavigablePath.equals( fetchParent.getNavigablePath() )
@ -1072,10 +1083,33 @@ public class ToOneAttributeMapping
} }
} }
} }
final DomainResult<?> keyResult;
if ( notFoundAction != null ) {
if ( sideNature == ForeignKeyDescriptor.Nature.KEY ) {
keyResult = foreignKeyDescriptor.createKeyDomainResult(
fetchablePath,
parentTableGroup,
creationState
);
}
else {
keyResult = foreignKeyDescriptor.createTargetDomainResult(
fetchablePath,
parentTableGroup,
creationState
);
}
}
else {
keyResult = null;
}
final EntityFetchJoinedImpl entityFetchJoined = new EntityFetchJoinedImpl( final EntityFetchJoinedImpl entityFetchJoined = new EntityFetchJoinedImpl(
fetchParent, fetchParent,
this, this,
tableGroup, tableGroup,
keyResult,
fetchablePath, fetchablePath,
creationState creationState
); );
@ -1555,8 +1589,12 @@ public class ToOneAttributeMapping
return isConstrained; return isConstrained;
} }
public NotFoundAction getNotFoundAction() {
return notFoundAction;
}
public boolean isIgnoreNotFound(){ public boolean isIgnoreNotFound(){
return isIgnoreNotFound; return notFoundAction == NotFoundAction.IGNORE;
} }
@Override @Override

View File

@ -8,4 +8,7 @@
/** /**
* Package defining a SQL AST for use in creating and executing various JDBC operations * Package defining a SQL AST for use in creating and executing various JDBC operations
*/ */
@Incubating
package org.hibernate.sql.ast; package org.hibernate.sql.ast;
import org.hibernate.Incubating;

View File

@ -7,6 +7,7 @@
package org.hibernate.sql.ast.tree.from; package org.hibernate.sql.ast.tree.from;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.NavigablePath;
@ -37,8 +38,17 @@ public abstract class AbstractColumnReferenceQualifier implements ColumnReferenc
allowFkOptimization, allowFkOptimization,
true true
); );
if ( tableReference == null ) { if ( tableReference == null ) {
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" ); throw new UnknownTableReferenceException(
tableExpression,
String.format(
Locale.ROOT,
"Unable to determine TableReference (`%s`) for `%s`",
tableExpression,
navigablePath.getFullPath()
)
);
} }
return tableReference; return tableReference;

View File

@ -27,6 +27,8 @@ public interface ColumnReferenceQualifier {
* @param navigablePath The path for which to look up the table reference, may be null * @param navigablePath The path for which to look up the table reference, may be null
* @param tableExpression The table expression for which to look up the table reference * @param tableExpression The table expression for which to look up the table reference
* @param allowFkOptimization Whether a foreign key optimization is allowed i.e. use the FK column on the key-side * @param allowFkOptimization Whether a foreign key optimization is allowed i.e. use the FK column on the key-side
*
* @throws UnknownTableReferenceException to indicate that the given tableExpression could not be resolved
*/ */
TableReference resolveTableReference( TableReference resolveTableReference(
NavigablePath navigablePath, NavigablePath navigablePath,

View File

@ -47,7 +47,10 @@ public abstract class DerivedTableReference extends AbstractTableReference {
NavigablePath navigablePath, NavigablePath navigablePath,
String tableExpression, String tableExpression,
boolean allowFkOptimization) { boolean allowFkOptimization) {
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" ); throw new UnknownTableReferenceException(
tableExpression,
"TableReferences cannot be resolved relative to DerivedTableReferences - `" + tableExpression + "` : " + navigablePath.getFullPath()
);
} }
@Override @Override

View File

@ -9,6 +9,7 @@ package org.hibernate.sql.ast.tree.from;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -243,8 +244,17 @@ public class LazyTableGroup extends DelegatingTableGroup {
allowFkOptimization, allowFkOptimization,
true true
); );
if ( tableReference == null ) { if ( tableReference == null ) {
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" ); throw new UnknownTableReferenceException(
tableExpression,
String.format(
Locale.ROOT,
"Unable to determine TableReference (`%s`) for `%s`",
tableExpression,
navigablePath.getFullPath()
)
);
} }
return tableReference; return tableReference;

View File

@ -8,6 +8,7 @@ package org.hibernate.sql.ast.tree.from;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -121,8 +122,17 @@ public class MappedByTableGroup extends DelegatingTableGroup implements VirtualT
allowFkOptimization, allowFkOptimization,
true true
); );
if ( tableReference == null ) { if ( tableReference == null ) {
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" ); throw new UnknownTableReferenceException(
tableExpression,
String.format(
Locale.ROOT,
"Unable to determine TableReference (`%s`) for `%s`",
tableExpression,
navigablePath.getFullPath()
)
);
} }
return tableReference; return tableReference;

View File

@ -8,6 +8,7 @@ package org.hibernate.sql.ast.tree.from;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -81,7 +82,16 @@ public class NamedTableReference extends AbstractTableReference {
if ( tableExpression.equals( getTableExpression() ) ) { if ( tableExpression.equals( getTableExpression() ) ) {
return this; return this;
} }
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" );
throw new UnknownTableReferenceException(
tableExpression,
String.format(
Locale.ROOT,
"Unable to determine TableReference (`%s`) for `%s`",
tableExpression,
navigablePath.getFullPath()
)
);
} }
@Override @Override

View File

@ -6,7 +6,10 @@
*/ */
package org.hibernate.sql.ast.tree.from; package org.hibernate.sql.ast.tree.from;
import java.util.Locale;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.model.domain.NavigableRole;
import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.NavigablePath;
/** /**
@ -34,7 +37,16 @@ public class UnionTableReference extends NamedTableReference {
if ( hasTableExpression( tableExpression ) ) { if ( hasTableExpression( tableExpression ) ) {
return this; return this;
} }
throw new IllegalStateException( "Could not resolve binding for table `" + tableExpression + "`" );
throw new UnknownTableReferenceException(
tableExpression,
String.format(
Locale.ROOT,
"Unable to determine TableReference (`%s`) for `%s`",
tableExpression,
navigablePath.getFullPath()
)
);
} }
@Override @Override

View File

@ -0,0 +1,30 @@
/*
* 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.ast.tree.from;
import org.hibernate.HibernateException;
/**
* Thrown when a {@link TableReference} cannot be resolved
* for a table-name.
*
* @see ColumnReferenceQualifier
*
* @author Steve Ebersole
*/
public class UnknownTableReferenceException extends HibernateException {
private final String tableExpression;
public UnknownTableReferenceException(String tableExpression, String message) {
super( message );
this.tableExpression = tableExpression;
}
public String getTableExpression() {
return tableExpression;
}
}

View File

@ -10,8 +10,11 @@
* to execute is modelled by {@link org.hibernate.sql.exec.spi.JdbcOperation} and * to execute is modelled by {@link org.hibernate.sql.exec.spi.JdbcOperation} and
* are executed via the corresponding executor. * are executed via the corresponding executor.
* *
* For operations that return ResultSets, be sure to see {@link org.hibernate.loader.ast.results} * For operations that return ResultSets, be sure to see {@link org.hibernate.sql.results}
* which provides support for processing results starting with * which provides support for processing results starting with
* {@link org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping} * {@link org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping}
*/ */
@Incubating
package org.hibernate.sql.exec; package org.hibernate.sql.exec;
import org.hibernate.Incubating;

View File

@ -231,6 +231,10 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
return navigablePath; return navigablePath;
} }
protected boolean isMissing() {
return missing;
}
protected abstract boolean isEntityReturn(); protected abstract boolean isEntityReturn();
@Override @Override
@ -282,8 +286,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
return; return;
} }
if ( EntityLoadingLogger.TRACE_ENABLED ) { if ( EntityLoadingLogging.TRACE_ENABLED ) {
EntityLoadingLogger.LOGGER.tracef( EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef(
"(%s) Beginning Initializer#resolveKey process for entity : %s", "(%s) Beginning Initializer#resolveKey process for entity : %s",
StringHelper.collapse( this.getClass().getName() ), StringHelper.collapse( this.getClass().getName() ),
getNavigablePath().getFullPath() getNavigablePath().getFullPath()
@ -300,7 +304,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
resolveEntityKey( rowProcessingState ); resolveEntityKey( rowProcessingState );
if ( entityKey == null ) { if ( entityKey == null ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) EntityKey (%s) is null", "(%s) EntityKey (%s) is null",
getSimpleConcreteImplName(), getSimpleConcreteImplName(),
getNavigablePath() getNavigablePath()
@ -311,8 +315,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
return; return;
} }
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Hydrated EntityKey (%s): %s", "(%s) Hydrated EntityKey (%s): %s",
getSimpleConcreteImplName(), getSimpleConcreteImplName(),
getNavigablePath(), getNavigablePath(),
@ -437,8 +441,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
final Object entityIdentifier = entityKey.getIdentifier(); final Object entityIdentifier = entityKey.getIdentifier();
if ( EntityLoadingLogger.TRACE_ENABLED ) { if ( EntityLoadingLogging.TRACE_ENABLED ) {
EntityLoadingLogger.LOGGER.tracef( EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef(
"(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s",
StringHelper.collapse( this.getClass().getName() ), StringHelper.collapse( this.getClass().getName() ),
getNavigablePath(), getNavigablePath(),
@ -523,8 +527,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
private void setIsOwningInitializer(Object entityIdentifier,LoadingEntityEntry existingLoadingEntry) { private void setIsOwningInitializer(Object entityIdentifier,LoadingEntityEntry existingLoadingEntry) {
if ( existingLoadingEntry != null ) { if ( existingLoadingEntry != null ) {
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Found existing loading entry [%s] - using loading instance", "(%s) Found existing loading entry [%s] - using loading instance",
getSimpleConcreteImplName(), getSimpleConcreteImplName(),
toLoggableString( getNavigablePath(), entityIdentifier ) toLoggableString( getNavigablePath(), entityIdentifier )
@ -546,8 +550,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
if ( !isOwningInitializer ) { if ( !isOwningInitializer ) {
// the entity is already being loaded elsewhere // the entity is already being loaded elsewhere
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing",
getSimpleConcreteImplName(), getSimpleConcreteImplName(),
toLoggableString( getNavigablePath(), entityIdentifier ), toLoggableString( getNavigablePath(), entityIdentifier ),
@ -598,8 +602,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
entityKey.getIdentifier() entityKey.getIdentifier()
); );
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Created new entity instance [%s] : %s", "(%s) Created new entity instance [%s] : %s",
getSimpleConcreteImplName(), getSimpleConcreteImplName(),
toLoggableString( getNavigablePath(), entityIdentifier ), toLoggableString( getNavigablePath(), entityIdentifier ),
@ -680,8 +684,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
final Object entityIdentifier = entityKey.getIdentifier(); final Object entityIdentifier = entityKey.getIdentifier();
if ( EntityLoadingLogger.TRACE_ENABLED ) { if ( EntityLoadingLogging.TRACE_ENABLED ) {
EntityLoadingLogger.LOGGER.tracef( EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef(
"(%s) Beginning Initializer#initializeInstance process for entity %s", "(%s) Beginning Initializer#initializeInstance process for entity %s",
getSimpleConcreteImplName(), getSimpleConcreteImplName(),
toLoggableString( getNavigablePath(), entityIdentifier ) toLoggableString( getNavigablePath(), entityIdentifier )
@ -778,8 +782,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
// No need to put into the entity cache is this is coming from the query cache already // No need to put into the entity cache is this is coming from the query cache already
if ( !rowProcessingState.isQueryCacheHit() && cacheAccess != null && session.getCacheMode().isPutEnabled() ) { if ( !rowProcessingState.isQueryCacheHit() && cacheAccess != null && session.getCacheMode().isPutEnabled() ) {
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%S) Adding entityInstance to second-level cache: %s", "(%S) Adding entityInstance to second-level cache: %s",
getSimpleConcreteImplName(), getSimpleConcreteImplName(),
toLoggableString( getNavigablePath(), entityIdentifier ) toLoggableString( getNavigablePath(), entityIdentifier )
@ -871,8 +875,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
concreteDescriptor.afterInitialize( toInitialize, session ); concreteDescriptor.afterInitialize( toInitialize, session );
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Done materializing entityInstance : %s", "(%s) Done materializing entityInstance : %s",
getSimpleConcreteImplName(), getSimpleConcreteImplName(),
toLoggableString( getNavigablePath(), entityIdentifier ) toLoggableString( getNavigablePath(), entityIdentifier )

View File

@ -13,14 +13,11 @@ import org.jboss.logging.Logger;
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public interface EntityLoadingLogger { public interface EntityLoadingLogging {
String LOGGER_NAME = LoadingLogger.subLoggerName( "entity" ); String LOCAL_NAME = "entity";
String LOGGER_NAME = LoadingLogger.subLoggerName( LOCAL_NAME );
Logger ENTITY_LOADING_LOGGER = LoadingLogger.subLogger( LOCAL_NAME );
/** boolean TRACE_ENABLED = ENTITY_LOADING_LOGGER.isTraceEnabled();
* Static access to the logging instance boolean DEBUG_ENABLED = ENTITY_LOADING_LOGGER.isDebugEnabled();
*/
Logger LOGGER = LoadingLogger.subLogger( "entity" );
boolean TRACE_ENABLED = LOGGER.isTraceEnabled();
boolean DEBUG_ENABLED = LOGGER.isDebugEnabled();
} }

View File

@ -28,7 +28,7 @@ import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer;
import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.EntityInitializer;
import org.hibernate.sql.results.graph.entity.EntityLoadingLogger; import org.hibernate.sql.results.graph.entity.EntityLoadingLogging;
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
@ -109,8 +109,8 @@ public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess
.findInitializer( entityKey ); .findInitializer( entityKey );
if ( initializer != null ) { if ( initializer != null ) {
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Found an initializer for entity (%s) : %s", "(%s) Found an initializer for entity (%s) : %s",
CONCRETE_NAME, CONCRETE_NAME,
toLoggableString( getNavigablePath(), entityIdentifier ), toLoggableString( getNavigablePath(), entityIdentifier ),
@ -131,8 +131,8 @@ public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess
if ( existingLoadingEntry != null ) { if ( existingLoadingEntry != null ) {
if ( existingLoadingEntry.getEntityInitializer() != this ) { if ( existingLoadingEntry.getEntityInitializer() != this ) {
// the entity is already being loaded elsewhere // the entity is already being loaded elsewhere
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing",
CONCRETE_NAME, CONCRETE_NAME,
toLoggableString( getNavigablePath(), entityIdentifier ), toLoggableString( getNavigablePath(), entityIdentifier ),

View File

@ -6,10 +6,16 @@
*/ */
package org.hibernate.sql.results.graph.entity.internal; package org.hibernate.sql.results.graph.entity.internal;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.engine.FetchTiming; import org.hibernate.engine.FetchTiming;
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.NavigablePath;
import org.hibernate.sql.ast.spi.FromClauseAccess;
import org.hibernate.sql.ast.spi.SqlAstCreationState;
import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.DomainResultCreationState;
import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.sql.results.graph.FetchParent;
import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.FetchParentAccess;
@ -22,25 +28,54 @@ import org.hibernate.sql.results.graph.entity.EntityValuedFetchable;
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class EntityFetchJoinedImpl extends AbstractNonLazyEntityFetch { public class EntityFetchJoinedImpl extends AbstractNonLazyEntityFetch {
private final EntityResultImpl entityResult; private final EntityResultImpl entityResult;
private final DomainResult<?> keyResult;
private final NotFoundAction notFoundAction;
private final String sourceAlias; private final String sourceAlias;
public EntityFetchJoinedImpl( public EntityFetchJoinedImpl(
FetchParent fetchParent, FetchParent fetchParent,
EntityValuedFetchable fetchedAttribute, ToOneAttributeMapping toOneMapping,
TableGroup tableGroup, TableGroup tableGroup,
DomainResult<?> keyResult,
NavigablePath navigablePath, NavigablePath navigablePath,
DomainResultCreationState creationState) { DomainResultCreationState creationState) {
super( fetchParent, fetchedAttribute, navigablePath ); super( fetchParent, toOneMapping, navigablePath );
this.keyResult = keyResult;
this.notFoundAction = toOneMapping.getNotFoundAction();
this.sourceAlias = tableGroup.getSourceAlias(); this.sourceAlias = tableGroup.getSourceAlias();
this.entityResult = new EntityResultImpl( this.entityResult = new EntityResultImpl(
navigablePath, navigablePath,
fetchedAttribute, toOneMapping,
tableGroup, tableGroup,
null, null,
creationState creationState
); );
this.entityResult.afterInitialize( this, creationState );
}
public EntityFetchJoinedImpl(
FetchParent fetchParent,
EntityCollectionPart collectionPart,
TableGroup tableGroup,
NavigablePath navigablePath,
DomainResultCreationState creationState) {
super( fetchParent, collectionPart, navigablePath );
this.notFoundAction = null;
this.keyResult = null;
this.sourceAlias = tableGroup.getSourceAlias();
this.entityResult = new EntityResultImpl(
navigablePath,
collectionPart,
tableGroup,
null,
creationState
);
this.entityResult.afterInitialize( this, creationState ); this.entityResult.afterInitialize( this, creationState );
} }
@ -56,6 +91,8 @@ public class EntityFetchJoinedImpl extends AbstractNonLazyEntityFetch {
getReferencedModePart(), getReferencedModePart(),
getNavigablePath(), getNavigablePath(),
creationState.determineEffectiveLockMode( sourceAlias ), creationState.determineEffectiveLockMode( sourceAlias ),
notFoundAction,
keyResult,
entityResult.getIdentifierFetch(), entityResult.getIdentifierFetch(),
entityResult.getDiscriminatorFetch(), entityResult.getDiscriminatorFetch(),
creationState creationState

View File

@ -24,18 +24,18 @@ import org.hibernate.sql.results.graph.entity.EntityInitializer;
* @author Andrea Boriero * @author Andrea Boriero
*/ */
public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch { public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
private final DomainResult result; private final DomainResult keyResult;
private final boolean selectByUniqueKey; private final boolean selectByUniqueKey;
public EntityFetchSelectImpl( public EntityFetchSelectImpl(
FetchParent fetchParent, FetchParent fetchParent,
ToOneAttributeMapping fetchedAttribute, ToOneAttributeMapping fetchedAttribute,
NavigablePath navigablePath, NavigablePath navigablePath,
DomainResult result, DomainResult keyResult,
boolean selectByUniqueKey, boolean selectByUniqueKey,
DomainResultCreationState creationState) { DomainResultCreationState creationState) {
super( navigablePath, fetchedAttribute, fetchParent ); super( navigablePath, fetchedAttribute, fetchParent );
this.result = result; this.keyResult = keyResult;
this.selectByUniqueKey = selectByUniqueKey; this.selectByUniqueKey = selectByUniqueKey;
} }
@ -65,7 +65,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
fetchedAttribute, fetchedAttribute,
getNavigablePath(), getNavigablePath(),
entityPersister, entityPersister,
result.createResultAssembler( parentAccess, creationState ) keyResult.createResultAssembler( parentAccess, creationState )
); );
} }
if ( entityPersister.isBatchLoadable() ) { if ( entityPersister.isBatchLoadable() ) {
@ -74,7 +74,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
fetchedAttribute, fetchedAttribute,
getNavigablePath(), getNavigablePath(),
entityPersister, entityPersister,
result.createResultAssembler( parentAccess, creationState ) keyResult.createResultAssembler( parentAccess, creationState )
); );
} }
else { else {
@ -83,7 +83,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch {
fetchedAttribute, fetchedAttribute,
getNavigablePath(), getNavigablePath(),
entityPersister, entityPersister,
result.createResultAssembler( parentAccess, creationState ) keyResult.createResultAssembler( parentAccess, creationState )
); );
} }
} }

View File

@ -6,17 +6,23 @@
*/ */
package org.hibernate.sql.results.graph.entity.internal; package org.hibernate.sql.results.graph.entity.internal;
import org.hibernate.FetchNotFoundException;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.internal.log.LoggingHelper; import org.hibernate.internal.log.LoggingHelper;
import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.ModelPart;
import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.NavigablePath;
import org.hibernate.sql.results.graph.AssemblerCreationState; import org.hibernate.sql.results.graph.AssemblerCreationState;
import org.hibernate.sql.results.graph.DomainResult;
import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.Fetch; import org.hibernate.sql.results.graph.Fetch;
import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer;
import org.hibernate.sql.results.graph.entity.EntityLoadingLogging;
import org.hibernate.sql.results.graph.entity.EntityResultGraphNode; import org.hibernate.sql.results.graph.entity.EntityResultGraphNode;
import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; import org.hibernate.sql.results.graph.entity.EntityValuedFetchable;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
/** /**
* @author Andrea Boriero * @author Andrea Boriero
@ -24,13 +30,18 @@ import org.hibernate.sql.results.graph.entity.EntityValuedFetchable;
public class EntityJoinedFetchInitializer extends AbstractEntityInitializer { public class EntityJoinedFetchInitializer extends AbstractEntityInitializer {
private static final String CONCRETE_NAME = EntityJoinedFetchInitializer.class.getSimpleName(); private static final String CONCRETE_NAME = EntityJoinedFetchInitializer.class.getSimpleName();
private final DomainResultAssembler<?> keyAssembler;
private final EntityValuedFetchable referencedFetchable;
private final boolean isEnhancedForLazyLoading; private final boolean isEnhancedForLazyLoading;
private final NotFoundAction notFoundAction;
public EntityJoinedFetchInitializer( public EntityJoinedFetchInitializer(
EntityResultGraphNode resultDescriptor, EntityResultGraphNode resultDescriptor,
EntityValuedFetchable referencedFetchable, EntityValuedFetchable referencedFetchable,
NavigablePath navigablePath, NavigablePath navigablePath,
LockMode lockMode, LockMode lockMode,
NotFoundAction notFoundAction,
DomainResult<?> keyResult,
Fetch identifierFetch, Fetch identifierFetch,
Fetch discriminatorFetch, Fetch discriminatorFetch,
AssemblerCreationState creationState) { AssemblerCreationState creationState) {
@ -43,8 +54,14 @@ public class EntityJoinedFetchInitializer extends AbstractEntityInitializer {
null, null,
creationState creationState
); );
this.referencedFetchable = referencedFetchable;
this.notFoundAction = notFoundAction;
this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState );
if ( getConcreteDescriptor() != null ) { if ( getConcreteDescriptor() != null ) {
this.isEnhancedForLazyLoading = getConcreteDescriptor().getBytecodeEnhancementMetadata() this.isEnhancedForLazyLoading = getConcreteDescriptor()
.getBytecodeEnhancementMetadata()
.isEnhancedForLazyLoading(); .isEnhancedForLazyLoading();
} }
else { else {
@ -52,6 +69,37 @@ public class EntityJoinedFetchInitializer extends AbstractEntityInitializer {
} }
} }
@Override
public void resolveKey(RowProcessingState rowProcessingState) {
super.resolveKey( rowProcessingState );
// super processes the foreign-key target column. here we
// need to also look at the foreign-key value column to check
// for a dangling foreign-key
if ( notFoundAction != null && keyAssembler != null ) {
final Object fkKeyValue = keyAssembler.assemble( rowProcessingState );
if ( fkKeyValue != null ) {
if ( isMissing() ) {
if ( notFoundAction == NotFoundAction.EXCEPTION ) {
throw new FetchNotFoundException(
referencedFetchable.getEntityMappingType().getEntityName(),
fkKeyValue
);
}
else {
EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"Ignoring dangling foreign-key due to `@NotFound(IGNORE); association will be null - %s",
getNavigablePath()
);
}
}
}
}
}
@Override @Override
protected Object getProxy(PersistenceContext persistenceContext) { protected Object getProxy(PersistenceContext persistenceContext) {
ModelPart referencedModelPart = getInitializedPart(); ModelPart referencedModelPart = getInitializedPart();

View File

@ -30,8 +30,8 @@ public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchIn
ToOneAttributeMapping fetchedAttribute, ToOneAttributeMapping fetchedAttribute,
NavigablePath fetchedNavigable, NavigablePath fetchedNavigable,
EntityPersister concreteDescriptor, EntityPersister concreteDescriptor,
DomainResultAssembler identifierAssembler) { DomainResultAssembler<?> keyAssembler) {
super( parentAccess, fetchedAttribute, fetchedNavigable, concreteDescriptor, identifierAssembler ); super( parentAccess, fetchedAttribute, fetchedNavigable, concreteDescriptor, keyAssembler );
this.fetchedAttribute = fetchedAttribute; this.fetchedAttribute = fetchedAttribute;
} }
@ -46,7 +46,7 @@ public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchIn
return; return;
} }
final Object entityIdentifier = identifierAssembler.assemble( rowProcessingState ); final Object entityIdentifier = keyAssembler.assemble( rowProcessingState );
if ( entityIdentifier == null ) { if ( entityIdentifier == null ) {
isInitialized = true; isInitialized = true;
return; return;

View File

@ -8,6 +8,8 @@ package org.hibernate.sql.results.graph.entity.internal;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.hibernate.FetchNotFoundException;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -19,14 +21,14 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping;
import org.hibernate.persister.entity.AbstractEntityPersister; import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import org.hibernate.query.sqm.spi.EntityIdentifierNavigablePath;
import org.hibernate.query.spi.NavigablePath; import org.hibernate.query.spi.NavigablePath;
import org.hibernate.query.sqm.spi.EntityIdentifierNavigablePath;
import org.hibernate.sql.results.graph.AbstractFetchParentAccess; import org.hibernate.sql.results.graph.AbstractFetchParentAccess;
import org.hibernate.sql.results.graph.DomainResultAssembler; import org.hibernate.sql.results.graph.DomainResultAssembler;
import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.entity.EntityInitializer; import org.hibernate.sql.results.graph.entity.EntityInitializer;
import org.hibernate.sql.results.graph.entity.EntityLoadingLogger; import org.hibernate.sql.results.graph.entity.EntityLoadingLogging;
import org.hibernate.sql.results.graph.entity.LoadingEntityEntry; import org.hibernate.sql.results.graph.entity.LoadingEntityEntry;
import org.hibernate.sql.results.jdbc.spi.RowProcessingState; import org.hibernate.sql.results.jdbc.spi.RowProcessingState;
@ -43,28 +45,30 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
private final boolean isEnhancedForLazyLoading; private final boolean isEnhancedForLazyLoading;
protected final EntityPersister concreteDescriptor; protected final EntityPersister concreteDescriptor;
protected final DomainResultAssembler identifierAssembler; protected final DomainResultAssembler<?> keyAssembler;
private final ToOneAttributeMapping referencedModelPart; private final ToOneAttributeMapping toOneMapping;
private Object entityIdentifier;
protected boolean isInitialized; protected boolean isInitialized;
protected Object entityInstance; protected Object entityInstance;
public EntitySelectFetchInitializer( public EntitySelectFetchInitializer(
FetchParentAccess parentAccess, FetchParentAccess parentAccess,
ToOneAttributeMapping referencedModelPart, ToOneAttributeMapping toOneMapping,
NavigablePath fetchedNavigable, NavigablePath fetchedNavigable,
EntityPersister concreteDescriptor, EntityPersister concreteDescriptor,
DomainResultAssembler identifierAssembler) { DomainResultAssembler<?> keyAssembler) {
this.parentAccess = parentAccess; this.parentAccess = parentAccess;
this.referencedModelPart = referencedModelPart; this.toOneMapping = toOneMapping;
this.navigablePath = fetchedNavigable; this.navigablePath = fetchedNavigable;
this.concreteDescriptor = concreteDescriptor; this.concreteDescriptor = concreteDescriptor;
this.identifierAssembler = identifierAssembler; this.keyAssembler = keyAssembler;
this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading();
} }
public ModelPart getInitializedPart(){ public ModelPart getInitializedPart(){
return referencedModelPart; return toOneMapping;
} }
@Override @Override
@ -101,15 +105,15 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
return; return;
} }
final Object entityIdentifier = identifierAssembler.assemble( rowProcessingState ); final Object entityIdentifier = keyAssembler.assemble( rowProcessingState );
if ( entityIdentifier == null ) { if ( entityIdentifier == null ) {
isInitialized = true; isInitialized = true;
return; return;
} }
if ( EntityLoadingLogger.TRACE_ENABLED ) { if ( EntityLoadingLogging.TRACE_ENABLED ) {
EntityLoadingLogger.LOGGER.tracef( EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef(
"(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s",
StringHelper.collapse( this.getClass().getName() ), StringHelper.collapse( this.getClass().getName() ),
getNavigablePath(), getNavigablePath(),
@ -132,8 +136,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
.findInitializer( entityKey ); .findInitializer( entityKey );
if ( initializer != null ) { if ( initializer != null ) {
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Found an initializer for entity (%s) : %s", "(%s) Found an initializer for entity (%s) : %s",
CONCRETE_NAME, CONCRETE_NAME,
toLoggableString( getNavigablePath(), entityIdentifier ), toLoggableString( getNavigablePath(), entityIdentifier ),
@ -152,8 +156,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
.findLoadingEntityEntry( entityKey ); .findLoadingEntityEntry( entityKey );
if ( existingLoadingEntry != null ) { if ( existingLoadingEntry != null ) {
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Found existing loading entry [%s] - using loading instance", "(%s) Found existing loading entry [%s] - using loading instance",
CONCRETE_NAME, CONCRETE_NAME,
toLoggableString( toLoggableString(
@ -166,8 +170,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
if ( existingLoadingEntry.getEntityInitializer() != this ) { if ( existingLoadingEntry.getEntityInitializer() != this ) {
// the entity is already being loaded elsewhere // the entity is already being loaded elsewhere
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing",
CONCRETE_NAME, CONCRETE_NAME,
toLoggableString( getNavigablePath(), entityIdentifier ), toLoggableString( getNavigablePath(), entityIdentifier ),
@ -181,8 +185,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
} }
} }
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogger.LOGGER.debugf( EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Invoking session#internalLoad for entity (%s) : %s", "(%s) Invoking session#internalLoad for entity (%s) : %s",
CONCRETE_NAME, CONCRETE_NAME,
toLoggableString( getNavigablePath(), entityIdentifier ), toLoggableString( getNavigablePath(), entityIdentifier ),
@ -193,11 +197,17 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
entityName, entityName,
entityIdentifier, entityIdentifier,
true, true,
referencedModelPart.isNullable() || referencedModelPart.isIgnoreNotFound() toOneMapping.isNullable() || toOneMapping.isIgnoreNotFound()
); );
if ( EntityLoadingLogger.DEBUG_ENABLED ) { if ( entityInstance == null ) {
EntityLoadingLogger.LOGGER.debugf( if ( toOneMapping.getNotFoundAction() == NotFoundAction.EXCEPTION ) {
throw new FetchNotFoundException( entityName, entityIdentifier );
}
}
if ( EntityLoadingLogging.DEBUG_ENABLED ) {
EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf(
"(%s) Entity [%s] : %s has being loaded by session.internalLoad.", "(%s) Entity [%s] : %s has being loaded by session.internalLoad.",
CONCRETE_NAME, CONCRETE_NAME,
toLoggableString( getNavigablePath(), entityIdentifier ), toLoggableString( getNavigablePath(), entityIdentifier ),
@ -205,7 +215,7 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
); );
} }
final boolean unwrapProxy = referencedModelPart.isUnwrapProxy() && isEnhancedForLazyLoading; final boolean unwrapProxy = toOneMapping.isUnwrapProxy() && isEnhancedForLazyLoading;
if ( entityInstance instanceof HibernateProxy ) { if ( entityInstance instanceof HibernateProxy ) {
( (HibernateProxy) entityInstance ).getHibernateLazyInitializer().setUnwrap( unwrapProxy ); ( (HibernateProxy) entityInstance ).getHibernateLazyInitializer().setUnwrap( unwrapProxy );
} }
@ -216,7 +226,7 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
if ( parentAccess instanceof EntityInitializer ) { if ( parentAccess instanceof EntityInitializer ) {
final AbstractEntityPersister concreteDescriptor = (AbstractEntityPersister) ( (EntityInitializer) parentAccess ).getConcreteDescriptor(); final AbstractEntityPersister concreteDescriptor = (AbstractEntityPersister) ( (EntityInitializer) parentAccess ).getConcreteDescriptor();
if ( concreteDescriptor.isPolymorphic() ) { if ( concreteDescriptor.isPolymorphic() ) {
final AbstractEntityPersister declaringType = (AbstractEntityPersister) referencedModelPart.getDeclaringType(); final AbstractEntityPersister declaringType = (AbstractEntityPersister) toOneMapping.getDeclaringType();
if ( concreteDescriptor != declaringType ) { if ( concreteDescriptor != declaringType ) {
if ( !declaringType.getSubclassEntityNames().contains( concreteDescriptor.getName() ) ) { if ( !declaringType.getSubclassEntityNames().contains( concreteDescriptor.getName() ) ) {
return false; return false;
@ -229,6 +239,7 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl
@Override @Override
public void finishUpRow(RowProcessingState rowProcessingState) { public void finishUpRow(RowProcessingState rowProcessingState) {
entityIdentifier = null;
entityInstance = null; entityInstance = null;
isInitialized = false; isInitialized = false;
clearResolutionListeners(); clearResolutionListeners();

View File

@ -106,10 +106,10 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association {
} }
@Override @Override
public DomainResultAssembler createAssembler( public DomainResultAssembler<?> createAssembler(
FetchParentAccess parentAccess, FetchParentAccess parentAccess,
AssemblerCreationState creationState) { AssemblerCreationState creationState) {
final DomainResultAssembler resultAssembler = keyResult.createResultAssembler( parentAccess, creationState ); final DomainResultAssembler<?> keyAssembler = keyResult.createResultAssembler( parentAccess, creationState );
final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer( final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer(
getNavigablePath(), getNavigablePath(),
@ -122,7 +122,7 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association {
fetchable, fetchable,
getNavigablePath(), getNavigablePath(),
entityMappingType.getEntityPersister(), entityMappingType.getEntityPersister(),
resultAssembler keyAssembler
); );
} }
final EntityPersister entityPersister = entityMappingType.getEntityPersister(); final EntityPersister entityPersister = entityMappingType.getEntityPersister();
@ -132,7 +132,7 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association {
(ToOneAttributeMapping) referencedModelPart, (ToOneAttributeMapping) referencedModelPart,
getReferencedPath(), getReferencedPath(),
entityPersister, entityPersister,
resultAssembler keyAssembler
); );
} }
else { else {
@ -141,7 +141,7 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association {
(ToOneAttributeMapping) referencedModelPart, (ToOneAttributeMapping) referencedModelPart,
getReferencedPath(), getReferencedPath(),
entityPersister, entityPersister,
resultAssembler keyAssembler
); );
} }
} }
@ -151,7 +151,7 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association {
getReferencedPath(), getReferencedPath(),
fetchable, fetchable,
selectByUniqueKey, selectByUniqueKey,
resultAssembler keyAssembler
); );
} }
} }

View File

@ -10,4 +10,7 @@
* defined by a "domain result graph" - one or more {@link org.hibernate.sql.results.graph.DomainResult} nodes * defined by a "domain result graph" - one or more {@link org.hibernate.sql.results.graph.DomainResult} nodes
* with zero-or-more {@link org.hibernate.sql.results.graph.Fetch} nodes * with zero-or-more {@link org.hibernate.sql.results.graph.Fetch} nodes
*/ */
@Incubating
package org.hibernate.sql.results; package org.hibernate.sql.results;
import org.hibernate.Incubating;

View File

@ -33,6 +33,7 @@ import org.junit.Test;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@ -80,8 +81,9 @@ public class JoinFormulaManyToOneNotIgnoreLazyFetchingTest extends BaseEntityMan
@Test @Test
public void testLazyLoading() { public void testLazyLoading() {
assertThat( triggerable.wasTriggered() )
assertEquals( "HHH000491: The [code] association in the [" + Stock.class.getName() + "] entity uses both @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY. The NotFoundAction.IGNORE @ManyToOne and @OneToOne associations are always fetched eagerly.", triggerable.triggerMessage() ); .describedAs( "Expecting WARN message to be logged" )
.isTrue();
List<Stock> stocks = doInJPA( this::entityManagerFactory, entityManager -> { List<Stock> stocks = doInJPA( this::entityManagerFactory, entityManager -> {
return entityManager.createQuery( return entityManager.createQuery(

View File

@ -23,18 +23,22 @@ import org.hibernate.LazyInitializationException;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import org.hibernate.cfg.AnnotationBinder; import org.hibernate.cfg.AnnotationBinder;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.logger.LoggerInspectionRule; import org.hibernate.testing.logger.LoggerInspectionRule;
import org.hibernate.testing.logger.Triggerable; import org.hibernate.testing.logger.Triggerable;
import org.hibernate.testing.transaction.TransactionUtil2;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.hibernate.testing.transaction.TransactionUtil2.fromTransaction;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@ -84,21 +88,17 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan
assertFalse( triggerable.wasTriggered() ); assertFalse( triggerable.wasTriggered() );
List<Stock> stocks = doInJPA( this::entityManagerFactory, entityManager -> { List<Stock> stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), (session) -> {
return entityManager.createQuery( return session.createQuery("SELECT s FROM Stock s order by id", Stock.class ).getResultList();
"SELECT s FROM Stock s", Stock.class )
.getResultList();
} ); } );
assertEquals( 2, stocks.size() );
try { assertThat( stocks ).hasSize( 2 );
assertEquals( "ABC", stocks.get( 0 ).getCodes().get( 0 ).getRefNumber() );
fail( "Should have thrown LazyInitializationException" ); final Stock firstStock = stocks.get( 0 );
} final Stock secondStock = stocks.get( 1 );
catch (LazyInitializationException expected) {
} assertThat( firstStock.getCodes() ).hasSize( 1 );
assertThat( secondStock.getCodes() ).hasSize( 0 );
} }
@Entity(name = "Stock") @Entity(name = "Stock")

View File

@ -34,6 +34,7 @@ import org.junit.Test;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@ -79,8 +80,9 @@ public class JoinFormulaOneToOneNotIgnoreLazyFetchingTest extends BaseEntityMana
@Test @Test
public void testLazyLoading() { public void testLazyLoading() {
assertThat( triggerable.wasTriggered() )
assertEquals( "HHH000491: The [code] association in the [" + Stock.class.getName() + "] entity uses both @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY. The NotFoundAction.IGNORE @ManyToOne and @OneToOne associations are always fetched eagerly.", triggerable.triggerMessage() ); .describedAs( "Expecting WARN message to be logged" )
.isTrue();
List<Stock> stocks = doInJPA( this::entityManagerFactory, entityManager -> { List<Stock> stocks = doInJPA( this::entityManagerFactory, entityManager -> {
return entityManager.createQuery( return entityManager.createQuery(

View File

@ -28,6 +28,7 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@ -70,7 +71,10 @@ public class LazyNotFoundManyToOneNonUpdatableNonInsertableTest extends BaseCore
doInHibernate( doInHibernate(
this::sessionFactory, session -> { this::sessionFactory, session -> {
User user = session.find( User.class, ID ); User user = session.find( User.class, ID );
assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); // per UserGuide (and simply correct behavior), `@NotFound` forces EAGER fetching
assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) )
.describedAs( "`User#lazy` is not eagerly initialized due to presence of `@NotFound`" )
.isTrue();
assertNull( user.getLazy() ); assertNull( user.getLazy() );
} }
); );

View File

@ -32,6 +32,7 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
@ -72,9 +73,12 @@ public class LazyNotFoundOneToOneNonUpdatableNonInsertableTest extends BaseCoreF
doInHibernate( doInHibernate(
this::sessionFactory, session -> { this::sessionFactory, session -> {
User user = session.find( User.class, ID ); User user = session.find( User.class, ID );
assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) )
assertNull( user.getLazy() ); .describedAs( "Expecting `User#lazy` to be bytecode initialized due to `@NotFound`" )
assertTrue( Hibernate.isPropertyInitialized( user, "lazy" ) ); .isTrue();
assertThat( user.getLazy() )
.describedAs( "Expecting `User#lazy` to be null due to `NotFoundAction#IGNORE`" )
.isNull();
} }
); );
} }
@ -90,7 +94,6 @@ public class LazyNotFoundOneToOneNonUpdatableNonInsertableTest extends BaseCoreF
@LazyToOne(value = LazyToOneOption.NO_PROXY) @LazyToOne(value = LazyToOneOption.NO_PROXY)
@NotFound(action = NotFoundAction.IGNORE) @NotFound(action = NotFoundAction.IGNORE)
@JoinColumn( @JoinColumn(
foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT),
name = "id", name = "id",
referencedColumnName = "id", referencedColumnName = "id",
insertable = false, insertable = false,

View File

@ -30,11 +30,11 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
/** /**
* @author Gail Badner * @author Gail Badner
@ -85,8 +85,16 @@ public class LazyNotFoundOneToOneTest extends BaseCoreFunctionalTestCase {
this::sessionFactory, session -> { this::sessionFactory, session -> {
User user = session.find( User.class, ID ); User user = session.find( User.class, ID );
assertThat( sqlInterceptor.getQueryCount(), is( 1 ) ); // per UserGuide (and simply correct behavior), `@NotFound` forces EAGER fetching
assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); assertThat( sqlInterceptor.getQueryCount() ).
describedAs( "Expecting 2 queries due to `@NotFound`" )
.isEqualTo( 2 );
assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) )
.describedAs( "Expecting `User#lazy` to be eagerly fetched due to `@NotFound`" )
.isTrue();
assertThat( Hibernate.isInitialized( user.getLazy() ) )
.describedAs( "Expecting `User#lazy` to be eagerly fetched due to `@NotFound`" )
.isTrue();
assertNull( user.getLazy() ); assertNull( user.getLazy() );
} }

View File

@ -11,6 +11,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.boot.spi.MetadataBuildingContext;
@ -78,7 +79,7 @@ public class CollectionBinderTest extends BaseUnitTestCase {
AnnotatedJoinColumn[] fkJoinColumns, AnnotatedJoinColumn[] fkJoinColumns,
XClass collectionType, XClass collectionType,
boolean cascadeDeleteEnabled, boolean cascadeDeleteEnabled,
boolean ignoreNotFound, NotFoundAction notFoundAction,
MetadataBuildingContext buildingContext, MetadataBuildingContext buildingContext,
Map<XClass, InheritanceState> inheritanceStatePerClass) { Map<XClass, InheritanceState> inheritanceStatePerClass) {
super.bindOneToManySecondPass( super.bindOneToManySecondPass(
@ -87,12 +88,12 @@ public class CollectionBinderTest extends BaseUnitTestCase {
fkJoinColumns, fkJoinColumns,
collectionType, collectionType,
cascadeDeleteEnabled, cascadeDeleteEnabled,
ignoreNotFound, notFoundAction,
buildingContext, buildingContext,
inheritanceStatePerClass inheritanceStatePerClass
); );
} }
}.bindOneToManySecondPass( collection, new HashMap(), null, collectionType, false, false, buildingContext, null); }.bindOneToManySecondPass( collection, new HashMap(), null, collectionType, false, null, buildingContext, null);
} catch (MappingException e) { } catch (MappingException e) {
assertEquals(expectMessage, e.getMessage()); assertEquals(expectMessage, e.getMessage());
} }

View File

@ -9,10 +9,12 @@ package org.hibernate.orm.test.notfound.exception;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.OneToOne; import jakarta.persistence.OneToOne;
import org.hibernate.FetchNotFoundException;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.ObjectNotFoundException; import org.hibernate.ObjectNotFoundException;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
@ -65,7 +67,6 @@ public class NotFoundExceptionLogicalOneToOneTest {
@Test @Test
@JiraKey( "HHH-15060" ) @JiraKey( "HHH-15060" )
@FailureExpected( reason = "We return a proxy here for `Coin#currency`, which violates NOTE #1." )
public void testGet(SessionFactoryScope scope) { public void testGet(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
@ -73,18 +74,24 @@ public class NotFoundExceptionLogicalOneToOneTest {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
session.get( Coin.class, 2 ); session.get( Coin.class, 2 );
// here we assume join, but this could use subsequent-select instead // at the moment this is handled as SELECT fetch
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " Currency " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
} ); } );
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
try { try {
session.get( Coin.class, 1 ); final Coin coin = session.get( Coin.class, 1 );
fail( "Expecting ObjectNotFoundException" ); fail( "Expecting ObjectNotFoundException, got - coin = " + coin + "; currency = " + coin.currency );
} }
catch (ObjectNotFoundException expected) { catch (FetchNotFoundException expected) {
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
assertThat( expected.getIdentifier() ).isEqualTo( 1 ); assertThat( expected.getIdentifier() ).isEqualTo( 1 );
} }
@ -93,7 +100,7 @@ public class NotFoundExceptionLogicalOneToOneTest {
@Test @Test
@JiraKey( "HHH-15060" ) @JiraKey( "HHH-15060" )
@FailureExpected( reason = "We return a proxy for `Coin#currency`, which violates NOTE #1." ) @FailureExpected( reason = "Join is not used in the SQL" )
public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) { public void testQueryImplicitPathDereferencePredicate(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
@ -123,16 +130,15 @@ public class NotFoundExceptionLogicalOneToOneTest {
@Test @Test
@JiraKey( "HHH-15060" ) @JiraKey( "HHH-15060" )
@FailureExpected( reason = "We return a proxy for `Coin#currency`, which violates NOTE #1." )
public void testQueryOwnerSelection(SessionFactoryScope scope) { public void testQueryOwnerSelection(SessionFactoryScope scope) {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
final String hql = "select c from Coin c where c.id = 1"; final String hql = "select c from Coin c where c.id = 1";
try { try {
//noinspection unused (debugging) //noinspection unused (debugging)
final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult(); final Coin coin = session.createQuery( hql, Coin.class ).uniqueResult();
fail( "Expecting ObjectNotFoundException for broken fk" ); fail( "Expecting ObjectNotFoundException; got - currency = " + coin.currency + "; coin = " + coin );
} }
catch (ObjectNotFoundException expected) { catch (FetchNotFoundException expected) {
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
assertThat( expected.getIdentifier() ).isEqualTo( 1 ); assertThat( expected.getIdentifier() ).isEqualTo( 1 );
} }

View File

@ -13,6 +13,7 @@ import jakarta.persistence.FetchType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import org.hibernate.FetchNotFoundException;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.ObjectNotFoundException; import org.hibernate.ObjectNotFoundException;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
@ -66,9 +67,6 @@ public class NotFoundExceptionManyToOneTest {
@Test @Test
@JiraKey( "HHH-15060" ) @JiraKey( "HHH-15060" )
@FailureExpected(
reason = "ObjectNotFoundException is thrown, but caught in `IdentifierLoadAccessImpl#doLoad` and null returned instead"
)
public void testGet(SessionFactoryScope scope) { public void testGet(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
@ -76,10 +74,10 @@ public class NotFoundExceptionManyToOneTest {
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
try { try {
// should fail here loading the Coin due to missing currency (see NOTE#1) // should fail here loading the Coin due to missing currency (see NOTE#1)
session.get( Coin.class, 1 ); final Coin coin = session.get( Coin.class, 1 );
fail( "Expecting ObjectNotFoundException for broken fk" ); fail( "Expecting ObjectNotFoundException - " + coin.getCurrency() );
} }
catch (ObjectNotFoundException expected) { catch (FetchNotFoundException expected) {
// technically we could use a subsequent-select rather than a join... // technically we could use a subsequent-select rather than a join...
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
@ -122,11 +120,6 @@ public class NotFoundExceptionManyToOneTest {
@Test @Test
@JiraKey( "HHH-15060" ) @JiraKey( "HHH-15060" )
@FailureExpected(
reason = "Does not do the join. Instead selects the Coin based on `currency_id` and then " +
"subsequent-selects the Currency. Ultimately results in a `Coin#1` reference with a " +
"null Currency."
)
public void testQueryOwnerSelection(SessionFactoryScope scope) { public void testQueryOwnerSelection(SessionFactoryScope scope) {
final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector();
statementInspector.clear(); statementInspector.clear();
@ -137,37 +130,23 @@ public class NotFoundExceptionManyToOneTest {
session.createQuery( hql, Coin.class ).getResultList(); session.createQuery( hql, Coin.class ).getResultList();
fail( "Expecting ObjectNotFoundException for broken fk" ); fail( "Expecting ObjectNotFoundException for broken fk" );
} }
catch (ObjectNotFoundException expected) { catch (FetchNotFoundException expected) {
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
assertThat( expected.getIdentifier() ).isEqualTo( 1 ); assertThat( expected.getIdentifier() ).isEqualTo( 1 );
// technically we could use a subsequent-select rather than a join...
assertThat( statementInspector.getSqlQueries() ).hasSize( 1 );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
} }
} ); } );
} }
@Test @Test
@JiraKey( "HHH-15060" ) @JiraKey( "HHH-15060" )
@FailureExpected(
reason = "This one is somewhat debatable. Is this selecting the association? Or simply matching Currencies?"
)
public void testQueryAssociationSelection(SessionFactoryScope scope) { public void testQueryAssociationSelection(SessionFactoryScope scope) {
// NOTE: this one is not obvious // NOTE: this one is not obvious
// - we are selecting the association so from that perspective, throwing the ObjectNotFoundException is nice // - we are selecting the association so from that perspective, throwing the ObjectNotFoundException is nice
// - the other way to look at it is that there are simply no matching results, so nothing to return // - the other way to look at it is that there are simply no matching results, so nothing to return
scope.inTransaction( (session) -> { scope.inTransaction( (session) -> {
final String hql = "select c.currency from Coin c where c.id = 1"; final String hql = "select c.currency from Coin c where c.id = 1";
try { final List<Currency> resultList = session.createQuery( hql, Currency.class ).getResultList();
session.createQuery( hql, Currency.class ).getResultList(); assertThat( resultList ).isEmpty();
fail( "Expecting ObjectNotFoundException for broken fk" );
}
catch (ObjectNotFoundException expected) {
assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() );
assertThat( expected.getIdentifier() ).isEqualTo( 1 );
}
} ); } );
} }

View File

@ -114,13 +114,14 @@ public class NotFoundIgnoreManyToOneTest {
// at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched. // at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched.
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " from Coin " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " from Currency " );
// but I believe a jon would be better assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " );
// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " Currency " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
} ); } );
} }

View File

@ -116,13 +116,14 @@ public class NotFoundIgnoreOneToOneTest {
// at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched. // at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched.
assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); assertThat( statementInspector.getSqlQueries() ).hasSize( 2 );
assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " from Coin " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " from Currency " );
// but I believe a jon would be better assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " Coin " );
// assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " Currency " );
// assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " join " );
// assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).contains( " Currency " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " Coin " );
assertThat( statementInspector.getSqlQueries().get( 1 ) ).doesNotContain( " join " );
} ); } );
} }

View File

@ -155,13 +155,13 @@ public class SessionFactoryExtension
} }
final HashMap<String,Object> settings = new HashMap<>( baseProperties ); final HashMap<String,Object> settings = new HashMap<>( baseProperties );
settings.put( AvailableSettings.HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP ); settings.put( AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP );
if ( createSecondarySchemas ) { if ( createSecondarySchemas ) {
if ( !( model.getDatabase().getDialect().canCreateSchema() ) ) { if ( !( model.getDatabase().getDialect().canCreateSchema() ) ) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
model.getDatabase().getDialect() + " does not support schema creation" ); model.getDatabase().getDialect() + " does not support schema creation" );
} }
settings.put( AvailableSettings.HBM2DDL_CREATE_SCHEMAS, true ); settings.put( AvailableSettings.JAKARTA_HBM2DDL_CREATE_SCHEMAS, true );
} }
final StandardServiceRegistry serviceRegistry = model.getMetadataBuildingOptions().getServiceRegistry(); final StandardServiceRegistry serviceRegistry = model.getMetadataBuildingOptions().getServiceRegistry();
@ -169,7 +169,7 @@ public class SessionFactoryExtension
model, model,
serviceRegistry, serviceRegistry,
settings, settings,
action -> sessionFactory.addObserver( (action) -> sessionFactory.addObserver(
new SessionFactoryObserver() { new SessionFactoryObserver() {
@Override @Override
public void sessionFactoryClosing(org.hibernate.SessionFactory factory) { public void sessionFactoryClosing(org.hibernate.SessionFactory factory) {