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

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

View File

@ -432,17 +432,16 @@ is accomplished using the `org.hibernate.annotation.NotFound` annotation with a
====
Not enforcing physical foreign-keys is very discouraged.
`@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.
====

View File

@ -0,0 +1,43 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate;
import java.util.Locale;
import jakarta.persistence.EntityNotFoundException;
/**
* Exception for {@link org.hibernate.annotations.NotFoundAction#EXCEPTION}
*
* @see org.hibernate.annotations.NotFound
*
* @author Steve Ebersole
*/
public class FetchNotFoundException extends EntityNotFoundException {
private final String entityName;
private final Object identifier;
public FetchNotFoundException(String entityName, Object identifier) {
super(
String.format(
Locale.ROOT,
"Entity `%s` with identifier value `%s` does not exist",
entityName,
identifier
)
);
this.entityName = entityName;
this.identifier = identifier;
}
public String getEntityName() {
return entityName;
}
public Object getIdentifier() {
return identifier;
}
}

View File

@ -7,27 +7,38 @@
package org.hibernate.annotations;
/**
* Enumerates the association fetching strategies available in Hibernate.
* <p>
* Whereas the JPA {@link jakarta.persistence.FetchType} enumeration provides a way to
* specify <em>when</em> an association should be fetched, this enumeration provides a
* way to express <em>how</em> it should be fetched.
* 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;
}
}

View File

@ -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 );
}
}

View File

@ -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() );

View File

@ -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,9 +987,30 @@ 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 ( notFound != null ) {
collection.setLazy( false );
if ( lazy != null ) {
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
}
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 {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
}
}
else {
if ( lazy != null ) {
collection.setLazy( !( lazy.value() == LazyCollectionOption.FALSE ) );
collection.setExtraLazy( lazy.value() == LazyCollectionOption.EXTRA );
@ -1017,6 +1040,7 @@ public abstract class CollectionBinder {
collection.setFetchMode( AnnotationBinder.getFetchMode( fetchType ) );
}
}
}
private XClass getCollectionType() {
if ( AnnotationBinder.isDefault( targetEntity, buildingContext ) ) {
@ -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<XClass, InheritanceState> 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) {

View File

@ -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()
);

View File

@ -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 );

View File

@ -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,

View File

@ -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)

View File

@ -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() {

View File

@ -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;
}
}

View File

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

View File

@ -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<String> 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<String> 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;
}

View File

@ -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
);

View File

@ -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(
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(

View File

@ -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,7 +939,6 @@ public class ToOneAttributeMapping
}
// The referencedNavigablePath can be null if this is a collection initialization
if ( referencedNavigablePath != 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;
@ -945,6 +955,8 @@ public class ToOneAttributeMapping
else {
keyDomainResult = null;
}
if ( hasBidirectionalFetchParent ) {
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

View File

@ -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;

View File

@ -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;

View File

@ -27,6 +27,8 @@ public interface ColumnReferenceQualifier {
* @param navigablePath The path for which to look up the table reference, may be null
* @param 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,

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,30 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.ast.tree.from;
import org.hibernate.HibernateException;
/**
* Thrown when a {@link TableReference} cannot be resolved
* for a table-name.
*
* @see ColumnReferenceQualifier
*
* @author Steve Ebersole
*/
public class UnknownTableReferenceException extends HibernateException {
private final String tableExpression;
public UnknownTableReferenceException(String tableExpression, String message) {
super( message );
this.tableExpression = tableExpression;
}
public String getTableExpression() {
return tableExpression;
}
}

View File

@ -10,8 +10,11 @@
* to execute is modelled by {@link org.hibernate.sql.exec.spi.JdbcOperation} and
* 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;

View File

@ -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 )

View File

@ -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();
}

View File

@ -28,7 +28,7 @@ import org.hibernate.sql.results.graph.FetchParentAccess;
import org.hibernate.sql.results.graph.Initializer;
import org.hibernate.sql.results.graph.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 ),

View File

@ -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

View File

@ -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 )
);
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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();

View File

@ -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
);
}
}

View File

@ -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;

View File

@ -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<Stock> stocks = doInJPA( this::entityManagerFactory, entityManager -> {
return entityManager.createQuery(

View File

@ -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<Stock> stocks = doInJPA( this::entityManagerFactory, entityManager -> {
return entityManager.createQuery(
"SELECT s FROM Stock s", Stock.class )
.getResultList();
List<Stock> 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")

View File

@ -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<Stock> stocks = doInJPA( this::entityManagerFactory, entityManager -> {
return entityManager.createQuery(

View File

@ -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() );
}
);

View File

@ -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,

View File

@ -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() );
}

View File

@ -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<XClass, InheritanceState> 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());
}

View File

@ -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 );
}

View File

@ -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<Currency> resultList = session.createQuery( hql, Currency.class ).getResultList();
assertThat( resultList ).isEmpty();
} );
}

View File

@ -114,13 +114,14 @@ public class NotFoundIgnoreManyToOneTest {
// at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched.
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 " );
} );
}

View File

@ -116,13 +116,14 @@ public class NotFoundIgnoreOneToOneTest {
// at the moment this uses a subsequent-select. on the bright side, it is at least eagerly fetched.
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 " );
} );
}

View File

@ -155,13 +155,13 @@ public class SessionFactoryExtension
}
final HashMap<String,Object> settings = new HashMap<>( baseProperties );
settings.put( AvailableSettings.HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP );
settings.put( AvailableSettings.JAKARTA_HBM2DDL_DATABASE_ACTION, Action.CREATE_DROP );
if ( createSecondarySchemas ) {
if ( !( 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) {