diff --git a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc index 4565ed821f..1094ace848 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/domain/associations.adoc @@ -432,17 +432,16 @@ is accomplished using the `org.hibernate.annotation.NotFound` annotation with a ==== 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`. 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 -used instead. +Hibernate would use INNER joins. With `@NotFound`, a LEFT join is used instead. 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 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. ==== diff --git a/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java b/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java new file mode 100644 index 0000000000..364a6addc7 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/FetchNotFoundException.java @@ -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; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java index 99afb9a367..5ea41bd673 100644 --- a/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java +++ b/hibernate-core/src/main/java/org/hibernate/annotations/FetchMode.java @@ -7,27 +7,38 @@ package org.hibernate.annotations; /** - * Enumerates the association fetching strategies available in Hibernate. - *

- * Whereas the JPA {@link jakarta.persistence.FetchType} enumeration provides a way to - * specify when an association should be fetched, this enumeration provides a - * way to express how it should be fetched. + * How the association should be fetched. * + * Defines the "how", compared to {@link jakarta.persistence.FetchType} which defines "when" + * + * @author Steve Ebersole * @author Emmanuel Bernard */ 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 - * the initial SQL select. + * Use an outer join to load the related entities, collections or joins. */ - JOIN, + JOIN( org.hibernate.FetchMode.JOIN ), /** - * For collections and many-valued associations only. After the initial SQL select, - * all associated collections are fetched together in a single subsequent select. + * Available for collections only. + * + * 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; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 8392cda2af..389d7d80d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -2415,7 +2415,7 @@ public final class AnnotationBinder { collectionBinder.setPropertyHolder(propertyHolder); Cascade hibernateCascade = property.getAnnotation( Cascade.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.setAccessType( inferredData.getDefaultAccess() ); @@ -2652,7 +2652,8 @@ public final class AnnotationBinder { || property.isAnnotationPresent( PrimaryKeyJoinColumns.class ); Cascade hibernateCascade = property.getAnnotation( Cascade.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; // 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 @@ -2662,8 +2663,8 @@ public final class AnnotationBinder { // @OneToOne(optional = true) with @PKJC makes the association optional. final boolean mandatory = !ann.optional() || property.isAnnotationPresent( Id.class ) - || property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound; - matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch() ); + || property.isAnnotationPresent( MapsId.class ) && notFoundAction != NotFoundAction.IGNORE; + matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), notFoundAction, ann.fetch() ); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); JoinTable assocTable = propertyHolder.getJoinTable(property); if ( assocTable != null ) { @@ -2677,7 +2678,7 @@ public final class AnnotationBinder { joinColumns, !mandatory, getFetchMode( ann.fetch() ), - ignoreNotFound, + notFoundAction, onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(), ToOneBinder.getTargetEntity(inferredData, context), propertyHolder, @@ -2714,8 +2715,8 @@ public final class AnnotationBinder { Cascade hibernateCascade = property.getAnnotation( Cascade.class ); NotFound notFound = property.getAnnotation( NotFound.class ); - boolean ignoreNotFound = notFound != null && notFound.action() == NotFoundAction.IGNORE; - matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), ignoreNotFound, ann.fetch() ); + NotFoundAction notFoundAction = notFound == null ? null : notFound.action(); + matchIgnoreNotFoundWithFetchType( propertyHolder.getEntityName(), property.getName(), notFoundAction, ann.fetch() ); OnDelete onDeleteAnn = property.getAnnotation( OnDelete.class ); JoinTable assocTable = propertyHolder.getJoinTable(property); if ( assocTable != null ) { @@ -2732,12 +2733,12 @@ public final class AnnotationBinder { // the association is optional. final boolean mandatory = !ann.optional() || property.isAnnotationPresent( Id.class ) - || property.isAnnotationPresent( MapsId.class ) && !ignoreNotFound; + || property.isAnnotationPresent( MapsId.class ) && notFoundAction != null; bindManyToOne( getCascadeStrategy( ann.cascade(), hibernateCascade, false, forcePersist), joinColumns, !mandatory, - ignoreNotFound, + notFoundAction, onDeleteAnn != null && OnDeleteAction.CASCADE == onDeleteAnn.action(), ToOneBinder.getTargetEntity(inferredData, context), propertyHolder, @@ -3545,7 +3546,7 @@ public final class AnnotationBinder { String cascadeStrategy, AnnotatedJoinColumn[] columns, boolean optional, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, XClass targetEntity, PropertyHolder propertyHolder, @@ -3565,7 +3566,7 @@ public final class AnnotationBinder { final XProperty property = inferredData.getProperty(); defineFetchingStrategy( value, property ); //value.setFetchMode( fetchMode ); - value.setIgnoreNotFound( ignoreNotFound ); + value.setNotFoundAction( notFoundAction ); value.setCascadeDeleteEnabled( cascadeOnDelete ); //value.setLazy( fetchMode != FetchMode.JOIN ); if ( !optional ) { @@ -3671,6 +3672,8 @@ public final class AnnotationBinder { Fetch fetch = property.getAnnotation( Fetch.class ); ManyToOne manyToOne = property.getAnnotation( ManyToOne.class ); OneToOne oneToOne = property.getAnnotation( OneToOne.class ); + NotFound notFound = property.getAnnotation( NotFound.class ); + FetchType fetchType; if ( manyToOne != null ) { fetchType = manyToOne.fetch(); @@ -3683,7 +3686,12 @@ public final class AnnotationBinder { "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.setUnwrapProxy( ( lazy.value() == LazyToOneOption.NO_PROXY ) ); } @@ -3692,6 +3700,7 @@ public final class AnnotationBinder { toOne.setUnwrapProxy( fetchType != FetchType.LAZY ); toOne.setUnwrapProxyImplicit( true ); } + if ( fetch != null ) { if ( fetch.value() == org.hibernate.annotations.FetchMode.JOIN ) { toOne.setFetchMode( FetchMode.JOIN ); @@ -3718,7 +3727,7 @@ public final class AnnotationBinder { AnnotatedJoinColumn[] joinColumns, boolean optional, FetchMode fetchMode, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, XClass targetEntity, PropertyHolder propertyHolder, @@ -3769,7 +3778,7 @@ public final class AnnotationBinder { propertyHolder, inferredData, targetEntity, - ignoreNotFound, + notFoundAction, cascadeOnDelete, optional, cascadeStrategy, @@ -3789,7 +3798,7 @@ public final class AnnotationBinder { else { //has a FK on the table bindManyToOne( - cascadeStrategy, joinColumns, optional, ignoreNotFound, cascadeOnDelete, + cascadeStrategy, joinColumns, optional, notFoundAction, cascadeOnDelete, targetEntity, propertyHolder, inferredData, true, isIdentifierMapper, inSecondPass, propertyBinder, context @@ -4137,9 +4146,9 @@ public final class AnnotationBinder { private static void matchIgnoreNotFoundWithFetchType( String entity, String association, - boolean ignoreNotFound, + NotFoundAction notFoundAction, FetchType fetchType) { - if ( ignoreNotFound && fetchType == FetchType.LAZY ) { + if ( notFoundAction != null && fetchType == FetchType.LAZY ) { LOG.ignoreNotFoundWithFetchTypeLazy( entity, association ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java index 7b4982ee20..d50ea9ea22 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/OneToOneSecondPass.java @@ -14,6 +14,7 @@ import jakarta.persistence.JoinColumns; import org.hibernate.AnnotationException; import org.hibernate.MappingException; import org.hibernate.annotations.LazyGroup; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.cfg.annotations.PropertyBinder; @@ -39,7 +40,7 @@ public class OneToOneSecondPass implements SecondPass { private final String ownerEntity; private final String ownerProperty; private final PropertyHolder propertyHolder; - private final boolean ignoreNotFound; + private final NotFoundAction notFoundAction; private final PropertyData inferredData; private final XClass targetEntity; private final boolean cascadeOnDelete; @@ -55,7 +56,7 @@ public class OneToOneSecondPass implements SecondPass { PropertyHolder propertyHolder, PropertyData inferredData, XClass targetEntity, - boolean ignoreNotFound, + NotFoundAction notFoundAction, boolean cascadeOnDelete, boolean optional, String cascadeStrategy, @@ -66,7 +67,7 @@ public class OneToOneSecondPass implements SecondPass { this.mappedBy = mappedBy; this.propertyHolder = propertyHolder; this.buildingContext = buildingContext; - this.ignoreNotFound = ignoreNotFound; + this.notFoundAction = notFoundAction; this.inferredData = inferredData; this.targetEntity = targetEntity; this.cascadeOnDelete = cascadeOnDelete; @@ -187,7 +188,7 @@ public class OneToOneSecondPass implements SecondPass { ); ManyToOne manyToOne = new ManyToOne( buildingContext, mappedByJoin.getTable() ); //FIXME use ignore not found here - manyToOne.setIgnoreNotFound( ignoreNotFound ); + manyToOne.setNotFoundAction( notFoundAction ); manyToOne.setCascadeDeleteEnabled( value.isCascadeDeleteEnabled() ); manyToOne.setFetchMode( value.getFetchMode() ); manyToOne.setLazy( value.isLazy() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java index d100e19fd0..f7583e33e2 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/CollectionBinder.java @@ -13,6 +13,22 @@ import java.util.Locale; import java.util.Map; import java.util.Properties; 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.AssertionFailure; @@ -44,7 +60,8 @@ import org.hibernate.annotations.ListIndexJdbcType; import org.hibernate.annotations.ListIndexJdbcTypeCode; import org.hibernate.annotations.Loader; 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.OnDeleteAction; import org.hibernate.annotations.OptimisticLock; @@ -115,23 +132,6 @@ import org.hibernate.usertype.UserCollectionType; 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 org.hibernate.cfg.AnnotatedColumn.checkPropertyConsistency; import static org.hibernate.cfg.AnnotationBinder.fillComponent; @@ -186,7 +186,7 @@ public abstract class CollectionBinder { private AnnotatedColumn[] elementColumns; private boolean isEmbedded; private XProperty property; - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; private TableBinder tableBinder; private AnnotatedColumn[] mapKeyColumns; private AnnotatedJoinColumn[] mapKeyManyToManyColumns; @@ -719,7 +719,7 @@ public abstract class CollectionBinder { isEmbedded, property, collectionType, - ignoreNotFound, + notFoundAction, oneToMany, tableBinder, buildingContext @@ -970,6 +970,8 @@ public abstract class CollectionBinder { ManyToMany manyToMany = property.getAnnotation( ManyToMany.class ); ElementCollection elementCollection = property.getAnnotation( ElementCollection.class ); ManyToAny manyToAny = property.getAnnotation( ManyToAny.class ); + NotFound notFound = property.getAnnotation( NotFound.class ); + FetchType fetchType; if ( oneToMany != null ) { fetchType = oneToMany.fetch(); @@ -985,36 +987,58 @@ public abstract class CollectionBinder { } else { 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 ) { - 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 ); + if ( notFound != null ) { + collection.setLazy( false ); + + if ( lazy != null ) { + collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA ); } - 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 ); + + if ( fetch != null ) { + if ( fetch.value() != null ) { + collection.setFetchMode( fetch.value().getHibernateFetchMode() ); + if ( fetch.value() == org.hibernate.annotations.FetchMode.SUBSELECT ) { + collection.setSubselectLoadable( true ); + collection.getOwner().setSubselectLoadableCollections( true ); + } + } } else { - throw new AssertionFailure( "Unknown FetchMode: " + fetch.value() ); + collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) ); } } 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 XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { @@ -1062,7 +1086,7 @@ public abstract class CollectionBinder { property, unique, assocTableBinder, - ignoreNotFound, + notFoundAction, buildingContext ); } @@ -1083,7 +1107,7 @@ public abstract class CollectionBinder { XProperty property, boolean unique, TableBinder associationTableBinder, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext) { PersistentClass persistentClass = persistentClasses.get( collType.getName() ); boolean reversePropertyInJoin = false; @@ -1118,7 +1142,7 @@ public abstract class CollectionBinder { fkJoinColumns, collType, cascadeDeleteEnabled, - ignoreNotFound, + notFoundAction, buildingContext, inheritanceStatePerClass ); @@ -1132,8 +1156,10 @@ public abstract class CollectionBinder { keyColumns, inverseColumns, elementColumns, - isEmbedded, collType, - ignoreNotFound, unique, + isEmbedded, + collType, + notFoundAction, + unique, cascadeDeleteEnabled, associationTableBinder, property, @@ -1150,7 +1176,7 @@ public abstract class CollectionBinder { AnnotatedJoinColumn[] fkJoinColumns, XClass collectionType, boolean cascadeDeleteEnabled, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext, Map inheritanceStatePerClass) { @@ -1166,7 +1192,7 @@ public abstract class CollectionBinder { new org.hibernate.mapping.OneToMany( buildingContext, collection.getOwner() ); collection.setElement( oneToMany ); oneToMany.setReferencedEntityName( collectionType.getName() ); - oneToMany.setIgnoreNotFound( ignoreNotFound ); + oneToMany.setNotFoundAction( notFoundAction ); String assocClass = oneToMany.getReferencedEntityName(); PersistentClass associatedClass = persistentClasses.get( assocClass ); @@ -1589,7 +1615,8 @@ public abstract class CollectionBinder { AnnotatedColumn[] elementColumns, boolean isEmbedded, XClass collType, - boolean ignoreNotFound, boolean unique, + NotFoundAction notFoundAction, + boolean unique, boolean cascadeDeleteEnabled, TableBinder associationTableBinder, XProperty property, @@ -1656,7 +1683,7 @@ public abstract class CollectionBinder { element = handleCollectionOfEntities( collValue, collType, - ignoreNotFound, + notFoundAction, property, buildingContext, 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; element = new ManyToOne(buildingContext, collValue.getCollectionTable() ); collValue.setElement( element ); @@ -1851,7 +1885,7 @@ public abstract class CollectionBinder { //make the second join non lazy element.setFetchMode( FetchMode.JOIN ); 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. if ( hqlOrderBy != null ) { collValue.setManyToManyOrdering( @@ -2276,8 +2310,18 @@ public abstract class CollectionBinder { this.property = property; } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + this.notFoundAction = ignoreNotFound + ? NotFoundAction.IGNORE + : null; } public void setMapKeyColumns(AnnotatedColumn[] mapKeyColumns) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java index 5dec409b01..cf7ccd1d57 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/IdBagBinder.java @@ -12,6 +12,7 @@ import java.util.function.Supplier; import org.hibernate.MappingException; import org.hibernate.annotations.CollectionId; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -61,7 +62,7 @@ public class IdBagBinder extends BagBinder { XProperty property, boolean unique, TableBinder associationTableBinder, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext) { boolean result = super.bindStarToManySecondPass( persistentClasses, @@ -74,7 +75,7 @@ public class IdBagBinder extends BagBinder { property, unique, associationTableBinder, - ignoreNotFound, + notFoundAction, getBuildingContext() ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java index 8453956fec..d89bee6e8d 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/ListBinder.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.function.Supplier; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OrderBy; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; @@ -71,7 +72,7 @@ public class ListBinder extends CollectionBinder { final boolean isEmbedded, final XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { @@ -90,7 +91,7 @@ public class ListBinder extends CollectionBinder { property, unique, assocTableBinder, - ignoreNotFound, + notFoundAction, buildingContext ); bindIndex( property, collType, buildingContext ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index f907721759..cabe544b9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -16,6 +16,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.MappingException; import org.hibernate.annotations.MapKeyCustomCompositeType; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; import org.hibernate.boot.spi.BootstrapContext; @@ -96,7 +97,7 @@ public class MapBinder extends CollectionBinder { final boolean isEmbedded, final XProperty property, final XClass collType, - final boolean ignoreNotFound, + final NotFoundAction notFoundAction, final boolean unique, final TableBinder assocTableBinder, final MetadataBuildingContext buildingContext) { @@ -105,7 +106,7 @@ public class MapBinder extends CollectionBinder { throws MappingException { bindStarToManySecondPass( persistentClasses, collType, fkJoinColumns, keyColumns, inverseColumns, elementColumns, - isEmbedded, property, unique, assocTableBinder, ignoreNotFound, buildingContext + isEmbedded, property, unique, assocTableBinder, notFoundAction, buildingContext ); bindKeyFromAssociationTable( collType, persistentClasses, mapKeyPropertyName, property, isEmbedded, buildingContext, diff --git a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java index 82ff20e1a1..087d71bc9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java @@ -1715,8 +1715,8 @@ public interface CoreMessageLogger extends BasicLogger { void usingJtaPlatform(String jtaPlatformClassName); @LogMessage(level = WARN) - @Message(value = "The [%2$s] association in the [%1$s] entity uses both @NotFound(action = NotFoundAction.IGNORE) and FetchType.LAZY. " + - "The NotFoundAction.IGNORE @ManyToOne and @OneToOne associations are always fetched eagerly.", id = 491) + @Message(value = "`.%1$s.%2$s` uses both @NotFound and FetchType.LAZY. @ManyToOne and " + + "@OneToOne associations mapped with `@NotFound` are forced to EAGER fetching.", id = 491) void ignoreNotFoundWithFetchTypeLazy(String entity, String association); @LogMessage(level = INFO) diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java index d129573954..6cc05a5f87 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/ManyToOne.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.Map; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -19,8 +20,8 @@ import org.hibernate.type.Type; * @author Gavin King */ public class ManyToOne extends ToOne { - private boolean ignoreNotFound; private boolean isLogicalOneToOne; + private NotFoundAction notFoundAction; private Type resolvedType; @@ -30,7 +31,7 @@ public class ManyToOne extends ToOne { private ManyToOne(ManyToOne original) { super( original ); - this.ignoreNotFound = original.ignoreNotFound; + this.notFoundAction = original.notFoundAction; this.isLogicalOneToOne = original.isLogicalOneToOne; } @@ -113,12 +114,22 @@ public class ManyToOne extends ToOne { return visitor.accept(this); } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public boolean isIgnoreNotFound() { - return ignoreNotFound; + return notFoundAction == NotFoundAction.IGNORE; } public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + this.notFoundAction = ignoreNotFound + ? NotFoundAction.IGNORE + : null; } public void markAsLogicalOneToOne() { diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java index 509ab8e06b..b9753e242f 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/OneToMany.java @@ -12,6 +12,7 @@ import java.util.Objects; import org.hibernate.FetchMode; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.boot.spi.MetadataBuildingContext; import org.hibernate.engine.spi.Mapping; import org.hibernate.service.ServiceRegistry; @@ -29,7 +30,7 @@ public class OneToMany implements Value { private String referencedEntityName; private PersistentClass associatedClass; - private boolean ignoreNotFound; + private NotFoundAction notFoundAction; public OneToMany(MetadataBuildingContext buildingContext, PersistentClass owner) throws MappingException { this.buildingContext = buildingContext; @@ -41,7 +42,7 @@ public class OneToMany implements Value { this.referencingTable = original.referencingTable; this.referencedEntityName = original.referencedEntityName; this.associatedClass = original.associatedClass; - this.ignoreNotFound = original.ignoreNotFound; + this.notFoundAction = original.notFoundAction; } @Override @@ -197,12 +198,22 @@ public class OneToMany implements Value { return false; } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + + public void setNotFoundAction(NotFoundAction notFoundAction) { + this.notFoundAction = notFoundAction; + } + public boolean isIgnoreNotFound() { - return ignoreNotFound; + return notFoundAction == NotFoundAction.IGNORE; } public void setIgnoreNotFound(boolean ignoreNotFound) { - this.ignoreNotFound = ignoreNotFound; + this.notFoundAction = ignoreNotFound + ? NotFoundAction.IGNORE + : null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java index 47320a93c7..8c00aeeb9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/BasicAttributeMapping.java @@ -277,6 +277,7 @@ public class BasicAttributeMapping getContainingTableExpression(), allowFkOptimization ); + return expressionResolver.resolveSqlSelection( expressionResolver.resolveSqlExpression( SqlExpressionResolver.createColumnReferenceKey( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java index 723cb99438..865e886648 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/EntityCollectionPart.java @@ -13,6 +13,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import org.hibernate.NotYetImplementedFor6Exception; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.dialect.Dialect; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; @@ -82,6 +83,7 @@ public class EntityCollectionPart private final Nature nature; private final EntityMappingType entityMappingType; private final Set targetKeyPropertyNames; + private final NotFoundAction notFoundAction; private ModelPart fkTargetModelPart; private ForeignKeyDescriptor fkDescriptor; @@ -90,12 +92,15 @@ public class EntityCollectionPart CollectionPersister collectionDescriptor, Nature nature, Value bootModelValue, + NotFoundAction notFoundAction, EntityMappingType entityMappingType, MappingModelCreationProcess creationProcess) { + this.notFoundAction = notFoundAction; this.navigableRole = collectionDescriptor.getNavigableRole().appendContainer( nature.getName() ); this.collectionDescriptor = collectionDescriptor; this.nature = nature; this.entityMappingType = entityMappingType; + final String referencedPropertyName; final PersistentClass entityBinding; if ( bootModelValue instanceof OneToMany ) { @@ -112,6 +117,7 @@ public class EntityCollectionPart entityBinding = toOne.getBuildingContext().getMetadataCollector() .getEntityBinding( entityMappingType.getEntityName() ); } + if ( referencedPropertyName == null ) { final Set targetKeyPropertyNames = new HashSet<>( 2 ); targetKeyPropertyNames.add( EntityIdentifierMapping.ROLE_LOCAL_NAME ); @@ -460,7 +466,8 @@ public class EntityCollectionPart boolean selected, String resultVariable, 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 EntityFetchJoinedImpl fetch = new EntityFetchJoinedImpl( @@ -470,9 +477,11 @@ public class EntityCollectionPart fetchablePath, creationState ); + if ( added ) { - creationState.removeVisitedAssociationKey( getForeignKeyDescriptor().getAssociationKey() ); + creationState.removeVisitedAssociationKey( foreignKeyDescriptor.getAssociationKey() ); } + return fetch; } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java index d162057c51..d97419eb72 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationHelper.java @@ -16,6 +16,7 @@ import org.hibernate.FetchMode; import org.hibernate.MappingException; import org.hibernate.NotYetImplementedFor6Exception; import org.hibernate.SharedSessionContract; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.collection.internal.StandardArraySemantics; import org.hibernate.collection.internal.StandardBagSemantics; @@ -1376,6 +1377,7 @@ public class MappingModelCreationHelper { collectionDescriptor, CollectionPart.Nature.INDEX, bootMapKeyDescriptor, + null, associatedEntity, creationProcess ); @@ -1473,10 +1475,22 @@ public class MappingModelCreationHelper { final EntityType elementEntityType = (EntityType) collectionDescriptor.getElementType(); 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( collectionDescriptor, CollectionPart.Nature.ELEMENT, bootDescriptor.getElement(), + notFoundAction, associatedEntity, creationProcess ); diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java index 39eba32142..9d1936c27f 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/SimpleForeignKeyDescriptor.java @@ -8,6 +8,7 @@ package org.hibernate.metamodel.mapping.internal; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.function.BiConsumer; 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.TableGroupProducer; 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.Predicate; import org.hibernate.sql.results.graph.DomainResult; @@ -230,10 +232,25 @@ public class SimpleForeignKeyDescriptor implements ForeignKeyDescriptor, BasicVa final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); final SqlExpressionResolver sqlExpressionResolver = sqlAstCreationState.getSqlExpressionResolver(); - final TableReference tableReference = tableGroup.resolveTableReference( - navigablePath.append( getTargetPart().getFetchableName() ), - selectableMapping.getContainingTableExpression() - ); + final TableReference tableReference; + try { + 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 SqlSelection sqlSelection = sqlExpressionResolver.resolveSqlSelection( diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java index 24c64b8e31..3d61de1346 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/ToOneAttributeMapping.java @@ -15,6 +15,7 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import org.hibernate.LockMode; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.FetchStyle; import org.hibernate.engine.FetchTiming; 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.EntityPersister; 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.TreatedNavigablePath; +import org.hibernate.query.sqm.spi.EntityIdentifierNavigablePath; import org.hibernate.sql.ast.Clause; import org.hibernate.sql.ast.SqlAstJoinType; import org.hibernate.sql.ast.spi.FromClauseAccess; @@ -132,7 +133,7 @@ public class ToOneAttributeMapping */ private final boolean isKeyTableNullable; private final boolean isConstrained; - private final boolean isIgnoreNotFound; + private final NotFoundAction notFoundAction; private final boolean unwrapProxy; private final boolean isOptional; private final EntityMappingType entityMappingType; @@ -193,7 +194,7 @@ public class ToOneAttributeMapping name, stateArrayPosition, attributeMetadataAccess, - mappedFetchTiming, + adjustFetchTiming( mappedFetchTiming, bootValue ), mappedFetchStyle, declaringType, propertyAccess, @@ -208,7 +209,7 @@ public class ToOneAttributeMapping if ( bootValue instanceof ManyToOne ) { final ManyToOne manyToOne = (ManyToOne) bootValue; - this.isIgnoreNotFound = ( (ManyToOne) bootValue ).isIgnoreNotFound(); + this.notFoundAction = ( (ManyToOne) bootValue ).getNotFoundAction(); if ( manyToOne.isLogicalOneToOne() ) { cardinality = Cardinality.LOGICAL_ONE_TO_ONE; } @@ -350,7 +351,7 @@ public class ToOneAttributeMapping else { this.bidirectionalAttributeName = bidirectionalAttributeName; } - isIgnoreNotFound = isNullable(); + notFoundAction = isNullable() ? NotFoundAction.IGNORE : null; isKeyTableNullable = isNullable(); 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) { // Also handle cases where a collection contains an embeddable, that contains an association NavigableRole parentRole = getNavigableRole().getParent(); @@ -497,7 +507,7 @@ public class ToOneAttributeMapping this.isNullable = original.isNullable; this.isKeyTableNullable = original.isKeyTableNullable; this.isOptional = original.isOptional; - this.isIgnoreNotFound = original.isIgnoreNotFound; + this.notFoundAction = original.notFoundAction; this.unwrapProxy = original.unwrapProxy; this.entityMappingType = original.entityMappingType; 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 // 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 ); } @@ -928,23 +939,24 @@ public class ToOneAttributeMapping } // The referencedNavigablePath can be null if this is a collection initialization 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 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( FetchTiming.IMMEDIATE, fetchablePath, @@ -968,6 +980,7 @@ public class ToOneAttributeMapping fetchParent, this, tableGroup, + keyDomainResult, fetchablePath, creationState ); @@ -1012,9 +1025,7 @@ public class ToOneAttributeMapping final SqlAstCreationState sqlAstCreationState = creationState.getSqlAstCreationState(); final FromClauseAccess fromClauseAccess = sqlAstCreationState.getFromClauseAccess(); - final TableGroup parentTableGroup = fromClauseAccess.getTableGroup( - fetchParent.getNavigablePath() - ); + final TableGroup parentTableGroup = fromClauseAccess.getTableGroup( fetchParent.getNavigablePath() ); final NavigablePath parentNavigablePath = fetchablePath.getParent(); 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( fetchParent, this, tableGroup, + keyResult, fetchablePath, creationState ); @@ -1555,8 +1589,12 @@ public class ToOneAttributeMapping return isConstrained; } + public NotFoundAction getNotFoundAction() { + return notFoundAction; + } + public boolean isIgnoreNotFound(){ - return isIgnoreNotFound; + return notFoundAction == NotFoundAction.IGNORE; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/package-info.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/package-info.java index b1f1bfd825..55a00d5c23 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/package-info.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/package-info.java @@ -8,4 +8,7 @@ /** * Package defining a SQL AST for use in creating and executing various JDBC operations */ +@Incubating package org.hibernate.sql.ast; + +import org.hibernate.Incubating; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractColumnReferenceQualifier.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractColumnReferenceQualifier.java index 1fd6f931ee..a6f62804d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractColumnReferenceQualifier.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/AbstractColumnReferenceQualifier.java @@ -7,6 +7,7 @@ package org.hibernate.sql.ast.tree.from; import java.util.List; +import java.util.Locale; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.query.spi.NavigablePath; @@ -37,8 +38,17 @@ public abstract class AbstractColumnReferenceQualifier implements ColumnReferenc allowFkOptimization, true ); + 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; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/ColumnReferenceQualifier.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/ColumnReferenceQualifier.java index a97cc8e325..19480dd454 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/ColumnReferenceQualifier.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/ColumnReferenceQualifier.java @@ -27,6 +27,8 @@ public interface ColumnReferenceQualifier { * @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 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( NavigablePath navigablePath, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/DerivedTableReference.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/DerivedTableReference.java index c79f1d422e..1f17fe97ff 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/DerivedTableReference.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/DerivedTableReference.java @@ -47,7 +47,10 @@ public abstract class DerivedTableReference extends AbstractTableReference { NavigablePath navigablePath, String tableExpression, 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 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/LazyTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/LazyTableGroup.java index 0be805ec40..f47265e943 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/LazyTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/LazyTableGroup.java @@ -9,6 +9,7 @@ package org.hibernate.sql.ast.tree.from; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Supplier; @@ -243,8 +244,17 @@ public class LazyTableGroup extends DelegatingTableGroup { allowFkOptimization, true ); + 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; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MappedByTableGroup.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MappedByTableGroup.java index aff550fb5c..d4cc3cf822 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MappedByTableGroup.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/MappedByTableGroup.java @@ -8,6 +8,7 @@ package org.hibernate.sql.ast.tree.from; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.function.BiPredicate; import java.util.function.Consumer; @@ -121,8 +122,17 @@ public class MappedByTableGroup extends DelegatingTableGroup implements VirtualT allowFkOptimization, true ); + 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; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/NamedTableReference.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/NamedTableReference.java index 86fade1644..4e34d94820 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/NamedTableReference.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/NamedTableReference.java @@ -8,6 +8,7 @@ package org.hibernate.sql.ast.tree.from; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.function.Consumer; import java.util.function.Function; @@ -81,7 +82,16 @@ public class NamedTableReference extends AbstractTableReference { if ( tableExpression.equals( getTableExpression() ) ) { 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 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableReference.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableReference.java index aa0d523f95..899ba70535 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableReference.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnionTableReference.java @@ -6,7 +6,10 @@ */ package org.hibernate.sql.ast.tree.from; +import java.util.Locale; + import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.query.spi.NavigablePath; /** @@ -34,7 +37,16 @@ public class UnionTableReference extends NamedTableReference { if ( hasTableExpression( tableExpression ) ) { 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 diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnknownTableReferenceException.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnknownTableReferenceException.java new file mode 100644 index 0000000000..0a1bad3b23 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/tree/from/UnknownTableReferenceException.java @@ -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; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/package-info.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/package-info.java index 77f0d5dcf9..88672cabda 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/package-info.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/package-info.java @@ -10,8 +10,11 @@ * to execute is modelled by {@link org.hibernate.sql.exec.spi.JdbcOperation} and * 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 * {@link org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping} */ +@Incubating package org.hibernate.sql.exec; + +import org.hibernate.Incubating; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index 40b6d3046f..c7d197fee9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -231,6 +231,10 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces return navigablePath; } + protected boolean isMissing() { + return missing; + } + protected abstract boolean isEntityReturn(); @Override @@ -282,8 +286,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces return; } - if ( EntityLoadingLogger.TRACE_ENABLED ) { - EntityLoadingLogger.LOGGER.tracef( + if ( EntityLoadingLogging.TRACE_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( "(%s) Beginning Initializer#resolveKey process for entity : %s", StringHelper.collapse( this.getClass().getName() ), getNavigablePath().getFullPath() @@ -300,7 +304,7 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces resolveEntityKey( rowProcessingState ); if ( entityKey == null ) { - EntityLoadingLogger.LOGGER.debugf( + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) EntityKey (%s) is null", getSimpleConcreteImplName(), getNavigablePath() @@ -311,8 +315,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces return; } - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Hydrated EntityKey (%s): %s", getSimpleConcreteImplName(), getNavigablePath(), @@ -437,8 +441,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces final Object entityIdentifier = entityKey.getIdentifier(); - if ( EntityLoadingLogger.TRACE_ENABLED ) { - EntityLoadingLogger.LOGGER.tracef( + if ( EntityLoadingLogging.TRACE_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", StringHelper.collapse( this.getClass().getName() ), getNavigablePath(), @@ -523,8 +527,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces private void setIsOwningInitializer(Object entityIdentifier,LoadingEntityEntry existingLoadingEntry) { if ( existingLoadingEntry != null ) { - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Found existing loading entry [%s] - using loading instance", getSimpleConcreteImplName(), toLoggableString( getNavigablePath(), entityIdentifier ) @@ -546,8 +550,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces SharedSessionContractImplementor session) { if ( !isOwningInitializer ) { // the entity is already being loaded elsewhere - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", getSimpleConcreteImplName(), toLoggableString( getNavigablePath(), entityIdentifier ), @@ -598,8 +602,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces entityKey.getIdentifier() ); - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Created new entity instance [%s] : %s", getSimpleConcreteImplName(), toLoggableString( getNavigablePath(), entityIdentifier ), @@ -680,8 +684,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces final Object entityIdentifier = entityKey.getIdentifier(); - if ( EntityLoadingLogger.TRACE_ENABLED ) { - EntityLoadingLogger.LOGGER.tracef( + if ( EntityLoadingLogging.TRACE_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( "(%s) Beginning Initializer#initializeInstance process for entity %s", getSimpleConcreteImplName(), 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 if ( !rowProcessingState.isQueryCacheHit() && cacheAccess != null && session.getCacheMode().isPutEnabled() ) { - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%S) Adding entityInstance to second-level cache: %s", getSimpleConcreteImplName(), toLoggableString( getNavigablePath(), entityIdentifier ) @@ -871,8 +875,8 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces concreteDescriptor.afterInitialize( toInitialize, session ); - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Done materializing entityInstance : %s", getSimpleConcreteImplName(), toLoggableString( getNavigablePath(), entityIdentifier ) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityLoadingLogger.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityLoadingLogging.java similarity index 54% rename from hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityLoadingLogger.java rename to hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityLoadingLogging.java index 93e6c46c72..4fec363c29 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityLoadingLogger.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/EntityLoadingLogging.java @@ -13,14 +13,11 @@ import org.jboss.logging.Logger; /** * @author Steve Ebersole */ -public interface EntityLoadingLogger { - String LOGGER_NAME = LoadingLogger.subLoggerName( "entity" ); +public interface EntityLoadingLogging { + String LOCAL_NAME = "entity"; + String LOGGER_NAME = LoadingLogger.subLoggerName( LOCAL_NAME ); + Logger ENTITY_LOADING_LOGGER = LoadingLogger.subLogger( LOCAL_NAME ); - /** - * Static access to the logging instance - */ - Logger LOGGER = LoadingLogger.subLogger( "entity" ); - - boolean TRACE_ENABLED = LOGGER.isTraceEnabled(); - boolean DEBUG_ENABLED = LOGGER.isDebugEnabled(); + boolean TRACE_ENABLED = ENTITY_LOADING_LOGGER.isTraceEnabled(); + boolean DEBUG_ENABLED = ENTITY_LOADING_LOGGER.isDebugEnabled(); } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java index 17c5d55dec..2d816ca4c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/BatchEntitySelectFetchInitializer.java @@ -28,7 +28,7 @@ import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Initializer; import org.hibernate.sql.results.graph.entity.AbstractEntityInitializer; 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.jdbc.spi.RowProcessingState; @@ -109,8 +109,8 @@ public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess .findInitializer( entityKey ); if ( initializer != null ) { - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Found an initializer for entity (%s) : %s", CONCRETE_NAME, toLoggableString( getNavigablePath(), entityIdentifier ), @@ -131,8 +131,8 @@ public class BatchEntitySelectFetchInitializer extends AbstractFetchParentAccess if ( existingLoadingEntry != null ) { if ( existingLoadingEntry.getEntityInitializer() != this ) { // the entity is already being loaded elsewhere - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", CONCRETE_NAME, toLoggableString( getNavigablePath(), entityIdentifier ), diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java index 402247ae74..9b6fa623be 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchJoinedImpl.java @@ -6,10 +6,16 @@ */ package org.hibernate.sql.results.graph.entity.internal; +import org.hibernate.annotations.NotFoundAction; 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.sql.ast.spi.FromClauseAccess; +import org.hibernate.sql.ast.spi.SqlAstCreationState; import org.hibernate.sql.ast.tree.from.TableGroup; 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.FetchParent; import org.hibernate.sql.results.graph.FetchParentAccess; @@ -22,25 +28,54 @@ import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; * @author Steve Ebersole */ public class EntityFetchJoinedImpl extends AbstractNonLazyEntityFetch { - private final EntityResultImpl entityResult; + private final DomainResult keyResult; + private final NotFoundAction notFoundAction; + private final String sourceAlias; public EntityFetchJoinedImpl( FetchParent fetchParent, - EntityValuedFetchable fetchedAttribute, + ToOneAttributeMapping toOneMapping, TableGroup tableGroup, + DomainResult keyResult, NavigablePath navigablePath, DomainResultCreationState creationState) { - super( fetchParent, fetchedAttribute, navigablePath ); + super( fetchParent, toOneMapping, navigablePath ); + this.keyResult = keyResult; + this.notFoundAction = toOneMapping.getNotFoundAction(); this.sourceAlias = tableGroup.getSourceAlias(); + this.entityResult = new EntityResultImpl( navigablePath, - fetchedAttribute, + toOneMapping, tableGroup, null, 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 ); } @@ -56,6 +91,8 @@ public class EntityFetchJoinedImpl extends AbstractNonLazyEntityFetch { getReferencedModePart(), getNavigablePath(), creationState.determineEffectiveLockMode( sourceAlias ), + notFoundAction, + keyResult, entityResult.getIdentifierFetch(), entityResult.getDiscriminatorFetch(), creationState diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java index b1dd7fa6dc..6397e10b58 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityFetchSelectImpl.java @@ -24,18 +24,18 @@ import org.hibernate.sql.results.graph.entity.EntityInitializer; * @author Andrea Boriero */ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch { - private final DomainResult result; + private final DomainResult keyResult; private final boolean selectByUniqueKey; public EntityFetchSelectImpl( FetchParent fetchParent, ToOneAttributeMapping fetchedAttribute, NavigablePath navigablePath, - DomainResult result, + DomainResult keyResult, boolean selectByUniqueKey, DomainResultCreationState creationState) { super( navigablePath, fetchedAttribute, fetchParent ); - this.result = result; + this.keyResult = keyResult; this.selectByUniqueKey = selectByUniqueKey; } @@ -65,7 +65,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch { fetchedAttribute, getNavigablePath(), entityPersister, - result.createResultAssembler( parentAccess, creationState ) + keyResult.createResultAssembler( parentAccess, creationState ) ); } if ( entityPersister.isBatchLoadable() ) { @@ -74,7 +74,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch { fetchedAttribute, getNavigablePath(), entityPersister, - result.createResultAssembler( parentAccess, creationState ) + keyResult.createResultAssembler( parentAccess, creationState ) ); } else { @@ -83,7 +83,7 @@ public class EntityFetchSelectImpl extends AbstractNonJoinedEntityFetch { fetchedAttribute, getNavigablePath(), entityPersister, - result.createResultAssembler( parentAccess, creationState ) + keyResult.createResultAssembler( parentAccess, creationState ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityJoinedFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityJoinedFetchInitializer.java index baf694c528..35fb485c20 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityJoinedFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityJoinedFetchInitializer.java @@ -6,17 +6,23 @@ */ package org.hibernate.sql.results.graph.entity.internal; +import org.hibernate.FetchNotFoundException; import org.hibernate.LockMode; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.internal.log.LoggingHelper; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.query.spi.NavigablePath; 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.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.EntityValuedFetchable; +import org.hibernate.sql.results.jdbc.spi.RowProcessingState; /** * @author Andrea Boriero @@ -24,13 +30,18 @@ import org.hibernate.sql.results.graph.entity.EntityValuedFetchable; public class EntityJoinedFetchInitializer extends AbstractEntityInitializer { private static final String CONCRETE_NAME = EntityJoinedFetchInitializer.class.getSimpleName(); + private final DomainResultAssembler keyAssembler; + private final EntityValuedFetchable referencedFetchable; private final boolean isEnhancedForLazyLoading; + private final NotFoundAction notFoundAction; public EntityJoinedFetchInitializer( EntityResultGraphNode resultDescriptor, EntityValuedFetchable referencedFetchable, NavigablePath navigablePath, LockMode lockMode, + NotFoundAction notFoundAction, + DomainResult keyResult, Fetch identifierFetch, Fetch discriminatorFetch, AssemblerCreationState creationState) { @@ -43,8 +54,14 @@ public class EntityJoinedFetchInitializer extends AbstractEntityInitializer { null, creationState ); + this.referencedFetchable = referencedFetchable; + this.notFoundAction = notFoundAction; + + this.keyAssembler = keyResult == null ? null : keyResult.createResultAssembler( this, creationState ); + if ( getConcreteDescriptor() != null ) { - this.isEnhancedForLazyLoading = getConcreteDescriptor().getBytecodeEnhancementMetadata() + this.isEnhancedForLazyLoading = getConcreteDescriptor() + .getBytecodeEnhancementMetadata() .isEnhancedForLazyLoading(); } 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 protected Object getProxy(PersistenceContext persistenceContext) { ModelPart referencedModelPart = getInitializedPart(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java index 6097be72ae..1d4e2c5e61 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchByUniqueKeyInitializer.java @@ -30,8 +30,8 @@ public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchIn ToOneAttributeMapping fetchedAttribute, NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, - DomainResultAssembler identifierAssembler) { - super( parentAccess, fetchedAttribute, fetchedNavigable, concreteDescriptor, identifierAssembler ); + DomainResultAssembler keyAssembler) { + super( parentAccess, fetchedAttribute, fetchedNavigable, concreteDescriptor, keyAssembler ); this.fetchedAttribute = fetchedAttribute; } @@ -46,7 +46,7 @@ public class EntitySelectFetchByUniqueKeyInitializer extends EntitySelectFetchIn return; } - final Object entityIdentifier = identifierAssembler.assemble( rowProcessingState ); + final Object entityIdentifier = keyAssembler.assemble( rowProcessingState ); if ( entityIdentifier == null ) { isInitialized = true; return; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java index af25f46e0c..7dcd879621 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntitySelectFetchInitializer.java @@ -8,6 +8,8 @@ package org.hibernate.sql.results.graph.entity.internal; 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.PersistenceContext; 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.EntityPersister; import org.hibernate.proxy.HibernateProxy; -import org.hibernate.query.sqm.spi.EntityIdentifierNavigablePath; 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.DomainResultAssembler; import org.hibernate.sql.results.graph.FetchParentAccess; import org.hibernate.sql.results.graph.Initializer; 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.jdbc.spi.RowProcessingState; @@ -43,28 +45,30 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl private final boolean isEnhancedForLazyLoading; protected final EntityPersister concreteDescriptor; - protected final DomainResultAssembler identifierAssembler; - private final ToOneAttributeMapping referencedModelPart; + protected final DomainResultAssembler keyAssembler; + private final ToOneAttributeMapping toOneMapping; + + private Object entityIdentifier; protected boolean isInitialized; protected Object entityInstance; public EntitySelectFetchInitializer( FetchParentAccess parentAccess, - ToOneAttributeMapping referencedModelPart, + ToOneAttributeMapping toOneMapping, NavigablePath fetchedNavigable, EntityPersister concreteDescriptor, - DomainResultAssembler identifierAssembler) { + DomainResultAssembler keyAssembler) { this.parentAccess = parentAccess; - this.referencedModelPart = referencedModelPart; + this.toOneMapping = toOneMapping; this.navigablePath = fetchedNavigable; this.concreteDescriptor = concreteDescriptor; - this.identifierAssembler = identifierAssembler; + this.keyAssembler = keyAssembler; this.isEnhancedForLazyLoading = concreteDescriptor.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); } public ModelPart getInitializedPart(){ - return referencedModelPart; + return toOneMapping; } @Override @@ -101,15 +105,15 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl return; } - final Object entityIdentifier = identifierAssembler.assemble( rowProcessingState ); + final Object entityIdentifier = keyAssembler.assemble( rowProcessingState ); if ( entityIdentifier == null ) { isInitialized = true; return; } - if ( EntityLoadingLogger.TRACE_ENABLED ) { - EntityLoadingLogger.LOGGER.tracef( + if ( EntityLoadingLogging.TRACE_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.tracef( "(%s) Beginning Initializer#resolveInstance process for entity (%s) : %s", StringHelper.collapse( this.getClass().getName() ), getNavigablePath(), @@ -132,8 +136,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl .findInitializer( entityKey ); if ( initializer != null ) { - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Found an initializer for entity (%s) : %s", CONCRETE_NAME, toLoggableString( getNavigablePath(), entityIdentifier ), @@ -152,8 +156,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl .findLoadingEntityEntry( entityKey ); if ( existingLoadingEntry != null ) { - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Found existing loading entry [%s] - using loading instance", CONCRETE_NAME, toLoggableString( @@ -166,8 +170,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl if ( existingLoadingEntry.getEntityInitializer() != this ) { // the entity is already being loaded elsewhere - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Entity [%s] being loaded by another initializer [%s] - skipping processing", CONCRETE_NAME, toLoggableString( getNavigablePath(), entityIdentifier ), @@ -181,8 +185,8 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl } } - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( EntityLoadingLogging.DEBUG_ENABLED ) { + EntityLoadingLogging.ENTITY_LOADING_LOGGER.debugf( "(%s) Invoking session#internalLoad for entity (%s) : %s", CONCRETE_NAME, toLoggableString( getNavigablePath(), entityIdentifier ), @@ -193,11 +197,17 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl entityName, entityIdentifier, true, - referencedModelPart.isNullable() || referencedModelPart.isIgnoreNotFound() + toOneMapping.isNullable() || toOneMapping.isIgnoreNotFound() ); - if ( EntityLoadingLogger.DEBUG_ENABLED ) { - EntityLoadingLogger.LOGGER.debugf( + if ( entityInstance == null ) { + 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.", CONCRETE_NAME, 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 ) { ( (HibernateProxy) entityInstance ).getHibernateLazyInitializer().setUnwrap( unwrapProxy ); } @@ -216,7 +226,7 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl if ( parentAccess instanceof EntityInitializer ) { final AbstractEntityPersister concreteDescriptor = (AbstractEntityPersister) ( (EntityInitializer) parentAccess ).getConcreteDescriptor(); if ( concreteDescriptor.isPolymorphic() ) { - final AbstractEntityPersister declaringType = (AbstractEntityPersister) referencedModelPart.getDeclaringType(); + final AbstractEntityPersister declaringType = (AbstractEntityPersister) toOneMapping.getDeclaringType(); if ( concreteDescriptor != declaringType ) { if ( !declaringType.getSubclassEntityNames().contains( concreteDescriptor.getName() ) ) { return false; @@ -229,6 +239,7 @@ public class EntitySelectFetchInitializer extends AbstractFetchParentAccess impl @Override public void finishUpRow(RowProcessingState rowProcessingState) { + entityIdentifier = null; entityInstance = null; isInitialized = false; clearResolutionListeners(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java index 6a1a85f70f..d208ad5338 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/internal/domain/CircularFetchImpl.java @@ -106,10 +106,10 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association { } @Override - public DomainResultAssembler createAssembler( + public DomainResultAssembler createAssembler( FetchParentAccess parentAccess, AssemblerCreationState creationState) { - final DomainResultAssembler resultAssembler = keyResult.createResultAssembler( parentAccess, creationState ); + final DomainResultAssembler keyAssembler = keyResult.createResultAssembler( parentAccess, creationState ); final EntityInitializer initializer = (EntityInitializer) creationState.resolveInitializer( getNavigablePath(), @@ -122,7 +122,7 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association { fetchable, getNavigablePath(), entityMappingType.getEntityPersister(), - resultAssembler + keyAssembler ); } final EntityPersister entityPersister = entityMappingType.getEntityPersister(); @@ -132,7 +132,7 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association { (ToOneAttributeMapping) referencedModelPart, getReferencedPath(), entityPersister, - resultAssembler + keyAssembler ); } else { @@ -141,7 +141,7 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association { (ToOneAttributeMapping) referencedModelPart, getReferencedPath(), entityPersister, - resultAssembler + keyAssembler ); } } @@ -151,7 +151,7 @@ public class CircularFetchImpl implements BiDirectionalFetch, Association { getReferencedPath(), fetchable, selectByUniqueKey, - resultAssembler + keyAssembler ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/package-info.java b/hibernate-core/src/main/java/org/hibernate/sql/results/package-info.java index 8a50b8727d..218099598d 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/package-info.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/package-info.java @@ -10,4 +10,7 @@ * 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 */ +@Incubating package org.hibernate.sql.results; + +import org.hibernate.Incubating; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaManyToOneNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaManyToOneNotIgnoreLazyFetchingTest.java index a43b26a882..d408d7729c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaManyToOneNotIgnoreLazyFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaManyToOneNotIgnoreLazyFetchingTest.java @@ -33,6 +33,7 @@ import org.junit.Test; import org.jboss.logging.Logger; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -80,8 +81,9 @@ public class JoinFormulaManyToOneNotIgnoreLazyFetchingTest extends BaseEntityMan @Test public void testLazyLoading() { - - 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() ); + assertThat( triggerable.wasTriggered() ) + .describedAs( "Expecting WARN message to be logged" ) + .isTrue(); List stocks = doInJPA( this::entityManagerFactory, entityManager -> { return entityManager.createQuery( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java index 07a391ffaa..1403281f29 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToManyNotIgnoreLazyFetchingTest.java @@ -23,18 +23,22 @@ import org.hibernate.LazyInitializationException; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; import org.hibernate.cfg.AnnotationBinder; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.logger.LoggerInspectionRule; import org.hibernate.testing.logger.Triggerable; +import org.hibernate.testing.transaction.TransactionUtil2; import org.junit.Rule; import org.junit.Test; 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.TransactionUtil2.fromTransaction; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -84,21 +88,17 @@ public class JoinFormulaOneToManyNotIgnoreLazyFetchingTest extends BaseEntityMan assertFalse( triggerable.wasTriggered() ); - List stocks = doInJPA( this::entityManagerFactory, entityManager -> { - return entityManager.createQuery( - "SELECT s FROM Stock s", Stock.class ) - .getResultList(); + List stocks = fromTransaction( entityManagerFactory().unwrap( SessionFactoryImplementor.class ), (session) -> { + return session.createQuery("SELECT s FROM Stock s order by id", Stock.class ).getResultList(); } ); - assertEquals( 2, stocks.size() ); - try { - assertEquals( "ABC", stocks.get( 0 ).getCodes().get( 0 ).getRefNumber() ); + assertThat( stocks ).hasSize( 2 ); - fail( "Should have thrown LazyInitializationException" ); - } - catch (LazyInitializationException expected) { + final Stock firstStock = stocks.get( 0 ); + final Stock secondStock = stocks.get( 1 ); - } + assertThat( firstStock.getCodes() ).hasSize( 1 ); + assertThat( secondStock.getCodes() ).hasSize( 0 ); } @Entity(name = "Stock") diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToOneNotIgnoreLazyFetchingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToOneNotIgnoreLazyFetchingTest.java index 90ce73feec..25ad48f306 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToOneNotIgnoreLazyFetchingTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/formula/JoinFormulaOneToOneNotIgnoreLazyFetchingTest.java @@ -34,6 +34,7 @@ import org.junit.Test; import org.jboss.logging.Logger; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -79,8 +80,9 @@ public class JoinFormulaOneToOneNotIgnoreLazyFetchingTest extends BaseEntityMana @Test public void testLazyLoading() { - - 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() ); + assertThat( triggerable.wasTriggered() ) + .describedAs( "Expecting WARN message to be logged" ) + .isTrue(); List stocks = doInJPA( this::entityManagerFactory, entityManager -> { return entityManager.createQuery( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java index 6e398e9898..3369b77f5d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundManyToOneNonUpdatableNonInsertableTest.java @@ -28,6 +28,7 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; import org.junit.runner.RunWith; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -70,7 +71,10 @@ public class LazyNotFoundManyToOneNonUpdatableNonInsertableTest extends BaseCore doInHibernate( this::sessionFactory, session -> { 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() ); } ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java index 6d141aad2a..0fe3bfd928 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneNonUpdatableNonInsertableTest.java @@ -32,6 +32,7 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; import org.junit.runner.RunWith; +import static org.assertj.core.api.Assertions.assertThat; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -72,9 +73,12 @@ public class LazyNotFoundOneToOneNonUpdatableNonInsertableTest extends BaseCoreF doInHibernate( this::sessionFactory, session -> { User user = session.find( User.class, ID ); - assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); - assertNull( user.getLazy() ); - assertTrue( Hibernate.isPropertyInitialized( user, "lazy" ) ); + assertThat( Hibernate.isPropertyInitialized( user, "lazy" ) ) + .describedAs( "Expecting `User#lazy` to be bytecode initialized due to `@NotFound`" ) + .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) @NotFound(action = NotFoundAction.IGNORE) @JoinColumn( - foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), name = "id", referencedColumnName = "id", insertable = false, diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java index e5c737c5b1..7fae20fcc9 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/lazy/notfound/LazyNotFoundOneToOneTest.java @@ -30,11 +30,11 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; import org.junit.runner.RunWith; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; /** * @author Gail Badner @@ -85,8 +85,16 @@ public class LazyNotFoundOneToOneTest extends BaseCoreFunctionalTestCase { this::sessionFactory, session -> { User user = session.find( User.class, ID ); - assertThat( sqlInterceptor.getQueryCount(), is( 1 ) ); - assertFalse( Hibernate.isPropertyInitialized( user, "lazy" ) ); + // per UserGuide (and simply correct behavior), `@NotFound` forces EAGER fetching + 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() ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/annotations/CollectionBinderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/annotations/CollectionBinderTest.java index d2546e4fc3..b06781322f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/annotations/CollectionBinderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/annotations/CollectionBinderTest.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.Map; import org.hibernate.MappingException; +import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.boot.spi.InFlightMetadataCollector; import org.hibernate.boot.spi.MetadataBuildingContext; @@ -78,7 +79,7 @@ public class CollectionBinderTest extends BaseUnitTestCase { AnnotatedJoinColumn[] fkJoinColumns, XClass collectionType, boolean cascadeDeleteEnabled, - boolean ignoreNotFound, + NotFoundAction notFoundAction, MetadataBuildingContext buildingContext, Map inheritanceStatePerClass) { super.bindOneToManySecondPass( @@ -87,12 +88,12 @@ public class CollectionBinderTest extends BaseUnitTestCase { fkJoinColumns, collectionType, cascadeDeleteEnabled, - ignoreNotFound, + notFoundAction, buildingContext, inheritanceStatePerClass ); } - }.bindOneToManySecondPass( collection, new HashMap(), null, collectionType, false, false, buildingContext, null); + }.bindOneToManySecondPass( collection, new HashMap(), null, collectionType, false, null, buildingContext, null); } catch (MappingException e) { assertEquals(expectMessage, e.getMessage()); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java index 68bcb38295..3051bf15a2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionLogicalOneToOneTest.java @@ -9,10 +9,12 @@ package org.hibernate.orm.test.notfound.exception; import java.io.Serializable; import java.util.List; import jakarta.persistence.Entity; +import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.OneToOne; +import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.ObjectNotFoundException; import org.hibernate.annotations.NotFound; @@ -65,7 +67,6 @@ public class NotFoundExceptionLogicalOneToOneTest { @Test @JiraKey( "HHH-15060" ) - @FailureExpected( reason = "We return a proxy here for `Coin#currency`, which violates NOTE #1." ) public void testGet(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -73,18 +74,24 @@ public class NotFoundExceptionLogicalOneToOneTest { scope.inTransaction( (session) -> { session.get( Coin.class, 2 ); - // here we assume join, but this could use subsequent-select instead - assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); - assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); - assertThat( statementInspector.getSqlQueries().get( 0 ) ).doesNotContain( " inner " ); + // at the moment this is handled as SELECT fetch + assertThat( statementInspector.getSqlQueries() ).hasSize( 2 ); + + 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) -> { try { - session.get( Coin.class, 1 ); - fail( "Expecting ObjectNotFoundException" ); + final Coin coin = session.get( Coin.class, 1 ); + fail( "Expecting ObjectNotFoundException, got - coin = " + coin + "; currency = " + coin.currency ); } - catch (ObjectNotFoundException expected) { + catch (FetchNotFoundException expected) { assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); assertThat( expected.getIdentifier() ).isEqualTo( 1 ); } @@ -93,7 +100,7 @@ public class NotFoundExceptionLogicalOneToOneTest { @Test @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) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -123,16 +130,15 @@ public class NotFoundExceptionLogicalOneToOneTest { @Test @JiraKey( "HHH-15060" ) - @FailureExpected( reason = "We return a proxy for `Coin#currency`, which violates NOTE #1." ) public void testQueryOwnerSelection(SessionFactoryScope scope) { scope.inTransaction( (session) -> { final String hql = "select c from Coin c where c.id = 1"; try { //noinspection unused (debugging) 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.getIdentifier() ).isEqualTo( 1 ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java index 3c9b767891..0049d710f7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/exception/NotFoundExceptionManyToOneTest.java @@ -13,6 +13,7 @@ import jakarta.persistence.FetchType; import jakarta.persistence.Id; import jakarta.persistence.ManyToOne; +import org.hibernate.FetchNotFoundException; import org.hibernate.Hibernate; import org.hibernate.ObjectNotFoundException; import org.hibernate.annotations.NotFound; @@ -66,9 +67,6 @@ public class NotFoundExceptionManyToOneTest { @Test @JiraKey( "HHH-15060" ) - @FailureExpected( - reason = "ObjectNotFoundException is thrown, but caught in `IdentifierLoadAccessImpl#doLoad` and null returned instead" - ) public void testGet(SessionFactoryScope scope) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -76,10 +74,10 @@ public class NotFoundExceptionManyToOneTest { scope.inTransaction( (session) -> { try { // should fail here loading the Coin due to missing currency (see NOTE#1) - session.get( Coin.class, 1 ); - fail( "Expecting ObjectNotFoundException for broken fk" ); + final Coin coin = session.get( Coin.class, 1 ); + fail( "Expecting ObjectNotFoundException - " + coin.getCurrency() ); } - catch (ObjectNotFoundException expected) { + catch (FetchNotFoundException expected) { // technically we could use a subsequent-select rather than a join... assertThat( statementInspector.getSqlQueries() ).hasSize( 1 ); assertThat( statementInspector.getSqlQueries().get( 0 ) ).contains( " join " ); @@ -122,11 +120,6 @@ public class NotFoundExceptionManyToOneTest { @Test @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) { final SQLStatementInspector statementInspector = scope.getCollectingStatementInspector(); statementInspector.clear(); @@ -137,37 +130,23 @@ public class NotFoundExceptionManyToOneTest { session.createQuery( hql, Coin.class ).getResultList(); fail( "Expecting ObjectNotFoundException for broken fk" ); } - catch (ObjectNotFoundException expected) { + catch (FetchNotFoundException expected) { assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); 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 @JiraKey( "HHH-15060" ) - @FailureExpected( - reason = "This one is somewhat debatable. Is this selecting the association? Or simply matching Currencies?" - ) public void testQueryAssociationSelection(SessionFactoryScope scope) { // NOTE: this one is not obvious // - 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 scope.inTransaction( (session) -> { final String hql = "select c.currency from Coin c where c.id = 1"; - try { - session.createQuery( hql, Currency.class ).getResultList(); - fail( "Expecting ObjectNotFoundException for broken fk" ); - } - catch (ObjectNotFoundException expected) { - assertThat( expected.getEntityName() ).isEqualTo( Currency.class.getName() ); - assertThat( expected.getIdentifier() ).isEqualTo( 1 ); - } + final List resultList = session.createQuery( hql, Currency.class ).getResultList(); + assertThat( resultList ).isEmpty(); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java index a5977e3f90..caf0b53693 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreManyToOneTest.java @@ -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. 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() ).hasSize( 1 ); -// 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 " ); } ); } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java index 84d3f3ef0b..feb12a4f1b 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/notfound/ignore/NotFoundIgnoreOneToOneTest.java @@ -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. 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() ).hasSize( 1 ); -// 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 " ); } ); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java index 9f52b474e1..6739b84901 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/junit/SessionFactoryExtension.java @@ -155,13 +155,13 @@ public class SessionFactoryExtension } final HashMap 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 ( !( model.getDatabase().getDialect().canCreateSchema() ) ) { throw new UnsupportedOperationException( 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(); @@ -169,7 +169,7 @@ public class SessionFactoryExtension model, serviceRegistry, settings, - action -> sessionFactory.addObserver( + (action) -> sessionFactory.addObserver( new SessionFactoryObserver() { @Override public void sessionFactoryClosing(org.hibernate.SessionFactory factory) {