diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java index d7c2b4d6daa7..c9ffc5bb8f9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/AnyBinder.java @@ -4,6 +4,7 @@ */ package org.hibernate.boot.model.internal; +import java.util.EnumSet; import java.util.Locale; import org.hibernate.AnnotationException; @@ -11,6 +12,7 @@ import org.hibernate.annotations.AnyDiscriminator; import org.hibernate.annotations.AnyDiscriminatorImplicitValues; import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.Columns; import org.hibernate.annotations.Formula; import org.hibernate.annotations.OnDelete; @@ -29,7 +31,7 @@ import jakarta.persistence.FetchType; import jakarta.persistence.JoinTable; -import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; +import static org.hibernate.boot.model.internal.BinderHelper.aggregateCascadeTypes; import static org.hibernate.boot.model.internal.DialectOverridesAnnotationHelper.getOverridableAnnotation; import static org.hibernate.boot.model.internal.BinderHelper.getPath; @@ -67,7 +69,7 @@ static void bindAny( } } bindAny( - getCascadeStrategy( null, hibernateCascade, false, context ), + aggregateCascadeTypes( null, hibernateCascade, false, context ), //@Any has no cascade attribute joinColumns, onDeleteAnn == null ? null : onDeleteAnn.action(), @@ -81,7 +83,7 @@ static void bindAny( } private static void bindAny( - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns columns, OnDeleteAction onDeleteAction, Nullability nullability, diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java index 78df9875eadb..701cfeebaf8a 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/BinderHelper.java @@ -908,26 +908,29 @@ public static FetchMode getFetchMode(FetchType fetch) { }; } - public static String getCascadeStrategy( - jakarta.persistence.CascadeType[] ejbCascades, - Cascade hibernateCascadeAnnotation, + public static EnumSet aggregateCascadeTypes( + jakarta.persistence.CascadeType[] cascadeTypes, + Cascade cascadeAnnotation, boolean orphanRemoval, MetadataBuildingContext context) { - final EnumSet cascadeTypes = convertToHibernateCascadeType( ejbCascades ); + final EnumSet cascades = + convertToHibernateCascadeType( cascadeTypes ); final CascadeType[] hibernateCascades = - hibernateCascadeAnnotation == null ? null : hibernateCascadeAnnotation.value(); + cascadeAnnotation == null + ? null + : cascadeAnnotation.value(); if ( !isEmpty( hibernateCascades ) ) { - addAll( cascadeTypes, hibernateCascades ); + addAll( cascades, hibernateCascades ); } if ( orphanRemoval ) { - cascadeTypes.add( CascadeType.DELETE_ORPHAN ); - cascadeTypes.add( CascadeType.REMOVE ); + cascades.add( CascadeType.DELETE_ORPHAN ); + cascades.add( CascadeType.REMOVE ); } - if ( cascadeTypes.contains( CascadeType.REPLICATE ) ) { + if ( cascades.contains( CascadeType.REPLICATE ) ) { warnAboutDeprecatedCascadeType( CascadeType.REPLICATE ); } - cascadeTypes.addAll( context.getEffectiveDefaults().getDefaultCascadeTypes() ); - return renderCascadeTypeList( cascadeTypes ); + cascades.addAll( context.getEffectiveDefaults().getDefaultCascadeTypes() ); + return cascades; } private static EnumSet convertToHibernateCascadeType(jakarta.persistence.CascadeType[] cascades) { @@ -951,7 +954,7 @@ private static CascadeType convertCascadeType(jakarta.persistence.CascadeType ca }; } - private static String renderCascadeTypeList(EnumSet cascadeTypes) { + public static String renderCascadeTypeList(EnumSet cascadeTypes) { final StringBuilder cascade = new StringBuilder(); for ( CascadeType cascadeType : cascadeTypes ) { cascade.append( "," ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java index b994f1d4c715..72e2ecc6f5c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java @@ -6,6 +6,7 @@ import java.lang.annotation.Annotation; import java.util.Comparator; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -17,57 +18,7 @@ import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.MappingException; -import org.hibernate.annotations.Bag; -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheLayout; -import org.hibernate.annotations.Cascade; -import org.hibernate.annotations.Check; -import org.hibernate.annotations.Checks; -import org.hibernate.annotations.CollectionId; -import org.hibernate.annotations.CollectionIdJavaType; -import org.hibernate.annotations.CollectionIdJdbcType; -import org.hibernate.annotations.CollectionIdJdbcTypeCode; -import org.hibernate.annotations.CollectionType; -import org.hibernate.annotations.Columns; -import org.hibernate.annotations.CompositeType; -import org.hibernate.annotations.Fetch; -import org.hibernate.annotations.FetchProfileOverride; -import org.hibernate.annotations.Filter; -import org.hibernate.annotations.FilterJoinTable; -import org.hibernate.annotations.Formula; -import org.hibernate.annotations.HQLSelect; -import org.hibernate.annotations.Immutable; -import org.hibernate.annotations.LazyGroup; -import org.hibernate.annotations.ListIndexBase; -import org.hibernate.annotations.ListIndexJavaType; -import org.hibernate.annotations.ListIndexJdbcType; -import org.hibernate.annotations.ListIndexJdbcTypeCode; -import org.hibernate.annotations.ManyToAny; -import org.hibernate.annotations.MapKeyJavaType; -import org.hibernate.annotations.MapKeyJdbcType; -import org.hibernate.annotations.MapKeyJdbcTypeCode; -import org.hibernate.annotations.MapKeyMutability; -import org.hibernate.annotations.MapKeyType; -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; -import org.hibernate.annotations.Parameter; -import org.hibernate.annotations.QueryCacheLayout; -import org.hibernate.annotations.SQLDelete; -import org.hibernate.annotations.SQLDeleteAll; -import org.hibernate.annotations.SQLInsert; -import org.hibernate.annotations.SQLJoinTableRestriction; -import org.hibernate.annotations.SQLOrder; -import org.hibernate.annotations.SQLRestriction; -import org.hibernate.annotations.SQLSelect; -import org.hibernate.annotations.SQLUpdate; -import org.hibernate.annotations.SoftDelete; -import org.hibernate.annotations.SortComparator; -import org.hibernate.annotations.SortNatural; -import org.hibernate.annotations.SqlFragmentAlias; -import org.hibernate.annotations.Synchronize; +import org.hibernate.annotations.*; import org.hibernate.boot.model.IdentifierGeneratorDefinition; import org.hibernate.boot.models.JpaAnnotations; import org.hibernate.boot.models.annotations.internal.JoinColumnJpaAnnotation; @@ -139,6 +90,7 @@ import static jakarta.persistence.ConstraintMode.NO_CONSTRAINT; import static jakarta.persistence.ConstraintMode.PROVIDER_DEFAULT; import static jakarta.persistence.FetchType.LAZY; +import static org.hibernate.annotations.CascadeType.DELETE_ORPHAN; import static org.hibernate.boot.model.internal.AnnotatedClassType.EMBEDDABLE; import static org.hibernate.boot.model.internal.AnnotatedClassType.NONE; import static org.hibernate.boot.model.internal.AnnotatedColumn.buildColumnFromAnnotation; @@ -147,11 +99,11 @@ import static org.hibernate.boot.model.internal.AnnotatedColumn.buildFormulaFromAnnotation; import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinColumnsWithDefaultColumnSuffix; import static org.hibernate.boot.model.internal.AnnotatedJoinColumns.buildJoinTableJoinColumns; +import static org.hibernate.boot.model.internal.BinderHelper.aggregateCascadeTypes; import static org.hibernate.boot.model.internal.BinderHelper.buildAnyValue; import static org.hibernate.boot.model.internal.BinderHelper.checkMappedByType; import static org.hibernate.boot.model.internal.BinderHelper.createSyntheticPropertyReference; import static org.hibernate.boot.model.internal.BinderHelper.extractFromPackage; -import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode; import static org.hibernate.boot.model.internal.BinderHelper.getPath; import static org.hibernate.boot.model.internal.BinderHelper.isDefault; @@ -202,7 +154,7 @@ public abstract class CollectionBinder { protected MemberDetails property; private TypeDetails collectionElementType; private TypeDetails targetEntity; - private String cascadeStrategy; + private EnumSet cascadeTypes; private String cacheConcurrencyStrategy; private String cacheRegionName; private CacheLayout queryCacheLayout; @@ -514,12 +466,9 @@ private static String handleTargetEntity( collectionBinder.setFkJoinColumns( joinColumns ); mappedBy = nullIfEmpty( oneToManyAnn.mappedBy() ); collectionBinder.setTargetEntity( oneToManyAnn.targetEntity() ); - collectionBinder.setCascadeStrategy( getCascadeStrategy( - oneToManyAnn.cascade(), - hibernateCascade, - oneToManyAnn.orphanRemoval(), - context - ) ); + collectionBinder.setCascadeStrategy( + aggregateCascadeTypes( oneToManyAnn.cascade(), hibernateCascade, + oneToManyAnn.orphanRemoval(), context ) ); collectionBinder.setOneToMany( true ); } else if ( elementCollectionAnn != null ) { @@ -535,23 +484,15 @@ else if ( elementCollectionAnn != null ) { else if ( manyToManyAnn != null ) { mappedBy = nullIfEmpty( manyToManyAnn.mappedBy() ); collectionBinder.setTargetEntity( manyToManyAnn.targetEntity() ); - collectionBinder.setCascadeStrategy( getCascadeStrategy( - manyToManyAnn.cascade(), - hibernateCascade, - false, - context - ) ); + collectionBinder.setCascadeStrategy( + aggregateCascadeTypes( manyToManyAnn.cascade(), hibernateCascade, false, context ) ); collectionBinder.setOneToMany( false ); } else if ( property.hasDirectAnnotationUsage( ManyToAny.class ) ) { mappedBy = null; collectionBinder.setTargetEntity( ClassDetails.VOID_CLASS_DETAILS ); - collectionBinder.setCascadeStrategy( getCascadeStrategy( - null, - hibernateCascade, - false, - context - ) ); + collectionBinder.setCascadeStrategy( + aggregateCascadeTypes( null, hibernateCascade, false, context ) ); collectionBinder.setOneToMany( false ); } else { @@ -807,8 +748,8 @@ private void setInsertable(boolean insertable) { this.insertable = insertable; } - private void setCascadeStrategy(String cascadeStrategy) { - this.cascadeStrategy = cascadeStrategy; + private void setCascadeStrategy(EnumSet cascadeTypes) { + this.cascadeTypes = cascadeTypes; } private void setAccessType(AccessType accessType) { @@ -1267,8 +1208,8 @@ private void bindProperty() { PropertyBinder binder = new PropertyBinder(); binder.setName( propertyName ); binder.setValue( collection ); - binder.setCascade( cascadeStrategy ); - if ( cascadeStrategy != null && cascadeStrategy.contains( "delete-orphan" ) ) { + binder.setCascade( cascadeTypes ); + if ( cascadeTypes != null && cascadeTypes.contains( DELETE_ORPHAN ) ) { collection.setOrphanDelete( true ); } binder.setLazy( collection.isLazy() ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java index d42b4a9478f7..be58e5c25db2 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/OneToOneSecondPass.java @@ -4,10 +4,12 @@ */ package org.hibernate.boot.model.internal; +import java.util.EnumSet; import java.util.Map; import org.hibernate.AnnotationException; import org.hibernate.MappingException; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.OnDeleteAction; @@ -23,6 +25,7 @@ import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; import org.hibernate.mapping.SortableValue; +import org.hibernate.mapping.Value; import org.hibernate.models.spi.MemberDetails; import org.hibernate.type.ForeignKeyDirection; @@ -49,7 +52,7 @@ public class OneToOneSecondPass implements SecondPass { private final NotFoundAction notFoundAction; private final OnDeleteAction onDeleteAction; private final boolean optional; - private final String cascadeStrategy; + private final EnumSet cascadeStrategy; private final AnnotatedJoinColumns joinColumns; private final MetadataBuildingContext buildingContext; private final String referencedEntityName; @@ -65,7 +68,7 @@ public OneToOneSecondPass( NotFoundAction notFoundAction, OnDeleteAction onDeleteAction, boolean optional, - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns columns, MetadataBuildingContext buildingContext) { this.ownerEntity = ownerEntity; @@ -84,11 +87,9 @@ public OneToOneSecondPass( @Override public void doSecondPass(Map persistentClasses) throws MappingException { - final OneToOne value = new OneToOne( - buildingContext, - propertyHolder.getTable(), - propertyHolder.getPersistentClass() - ); + final OneToOne value = + new OneToOne( buildingContext, propertyHolder.getTable(), + propertyHolder.getPersistentClass() ); final String propertyName = inferredData.getPropertyName(); value.setPropertyName( propertyName ); value.setReferencedEntityName( referencedEntityName ); @@ -100,7 +101,9 @@ public void doSecondPass(Map persistentClasses) throws value.setConstrained( !optional ); value.setForeignKeyType( getForeignKeyDirection() ); - bindForeignKeyNameAndDefinition( value, property, property.getDirectAnnotationUsage( ForeignKey.class ), buildingContext ); + bindForeignKeyNameAndDefinition( value, property, + property.getDirectAnnotationUsage( ForeignKey.class ), + buildingContext ); final PropertyBinder binder = new PropertyBinder(); binder.setName( propertyName ); @@ -144,10 +147,11 @@ private void bindUnowned(Map persistentClasses, OneToOn + "' targets the type '" + targetEntityName + "'" + problem ); } final Property targetProperty = targetProperty( oneToOne, targetEntity ); - if ( targetProperty.getValue() instanceof OneToOne ) { + final Value targetPropertyValue = targetProperty.getValue(); + if ( targetPropertyValue instanceof OneToOne ) { propertyHolder.addProperty( property, inferredData.getAttributeMember(), inferredData.getDeclaringClass() ); } - else if ( targetProperty.getValue() instanceof ManyToOne ) { + else if ( targetPropertyValue instanceof ManyToOne ) { bindTargetManyToOne( persistentClasses, oneToOne, property, targetEntity, targetProperty ); } else { @@ -158,7 +162,7 @@ else if ( targetProperty.getValue() instanceof ManyToOne ) { } checkMappedByType( mappedBy, - targetProperty.getValue(), + targetPropertyValue, oneToOne.getPropertyName(), propertyHolder, persistentClasses diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java index 1646b0969534..664f75df6408 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/PropertyBinder.java @@ -32,6 +32,7 @@ import org.hibernate.MappingException; import org.hibernate.annotations.Any; import org.hibernate.annotations.AttributeBinderType; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.CompositeType; import org.hibernate.annotations.IdGeneratorType; import org.hibernate.annotations.Immutable; @@ -122,7 +123,7 @@ public class PropertyBinder { private Component componentElement; private boolean insertable = true; private boolean updatable = true; - private String cascade; + private EnumSet cascadeTypes; private BasicValueBinder basicValueBinder; private ClassDetails declaringClass; private boolean declaringClassSet; @@ -199,8 +200,8 @@ private void setComponentElement(Component componentElement) { this.componentElement = componentElement; } - public void setCascade(String cascadeStrategy) { - this.cascade = cascadeStrategy; + public void setCascade(EnumSet cascadeTypes) { + this.cascadeTypes = cascadeTypes; } public void setBuildingContext(MetadataBuildingContext buildingContext) { @@ -438,7 +439,7 @@ public Property makeProperty() { property.setValue( value ); property.setLazy( lazy ); property.setLazyGroup( lazyGroup ); - property.setCascade( cascade ); + property.setCascade( cascadeTypes ); property.setPropertyAccessorName( accessType.getType() ); property.setReturnedClassName( returnedClassName ); // property.setPropertyAccessStrategy( propertyAccessStrategy ); diff --git a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java index 22b0ebc4a796..43f8db02a824 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/model/internal/ToOneBinder.java @@ -5,12 +5,14 @@ package org.hibernate.boot.model.internal; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.Columns; import org.hibernate.annotations.Fetch; import org.hibernate.annotations.FetchProfileOverride; @@ -46,7 +48,7 @@ import static jakarta.persistence.FetchType.EAGER; import static jakarta.persistence.FetchType.LAZY; -import static org.hibernate.boot.model.internal.BinderHelper.getCascadeStrategy; +import static org.hibernate.boot.model.internal.BinderHelper.aggregateCascadeTypes; import static org.hibernate.boot.model.internal.BinderHelper.getFetchMode; import static org.hibernate.boot.model.internal.BinderHelper.getPath; import static org.hibernate.boot.model.internal.BinderHelper.isDefault; @@ -103,7 +105,7 @@ && isIdentifier( propertyHolder, propertyBinder, isIdentifierMapper ) ) { final OnDelete onDelete = property.getDirectAnnotationUsage( OnDelete.class ); final JoinTable joinTable = propertyHolder.getJoinTable( property ); bindManyToOne( - getCascadeStrategy( manyToOne.cascade(), hibernateCascade, false, context ), + aggregateCascadeTypes( manyToOne.cascade(), hibernateCascade, false, context ), joinColumns, joinTable, !isMandatory( manyToOne.optional(), property, notFoundAction ), @@ -144,7 +146,7 @@ private static boolean isMandatory(boolean optional, MemberDetails property, Not } private static void bindManyToOne( - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns joinColumns, JoinTable joinTable, boolean optional, @@ -256,7 +258,7 @@ static boolean isTargetAnnotatedEntity(ClassDetails targetEntity, MemberDetails } private static void processManyToOneProperty( - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns columns, boolean optional, PropertyData inferredData, @@ -426,7 +428,7 @@ static void bindOneToOne( final OnDelete onDelete = property.getDirectAnnotationUsage( OnDelete.class ); final JoinTable joinTable = propertyHolder.getJoinTable( property ); bindOneToOne( - getCascadeStrategy( oneToOne.cascade(), hibernateCascade, oneToOne.orphanRemoval(), context ), + aggregateCascadeTypes( oneToOne.cascade(), hibernateCascade, oneToOne.orphanRemoval(), context ), joinColumns, joinTable, !isMandatory( oneToOne.optional(), property, notFoundAction ), @@ -447,7 +449,7 @@ static void bindOneToOne( } private static void bindOneToOne( - String cascadeStrategy, + EnumSet cascadeStrategy, AnnotatedJoinColumns joinColumns, JoinTable joinTable, boolean optional, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java index af26336d4d3b..baa750d43fc7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java @@ -10,14 +10,12 @@ import java.util.List; import org.hibernate.HibernateException; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CascadeStyle; import org.hibernate.engine.spi.CascadingAction; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.PersistenceContext; -import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.Status; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; @@ -32,13 +30,11 @@ import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.ForeignKeyDirection; -import org.hibernate.type.ManyToOneType; import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; import static java.util.Collections.EMPTY_LIST; import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy; -import static org.hibernate.engine.spi.CascadingActions.CHECK_ON_FLUSH; import static org.hibernate.pretty.MessageHelper.infoString; /** @@ -84,17 +80,15 @@ public static void cascade( final EntityPersister persister, final Object parent, final T anything) throws HibernateException { - if ( persister.hasCascades() || action == CHECK_ON_FLUSH ) { // performance opt + if ( action.anythingToCascade( persister ) ) { // performance opt final boolean traceEnabled = LOG.isTraceEnabled(); if ( traceEnabled ) { LOG.tracev( "Processing cascade {0} for: {1}", action, persister.getEntityName() ); } - final PersistenceContext persistenceContext = eventSource.getPersistenceContextInternal(); - final boolean enhancedForLazyLoading = - persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + final var bytecodeEnhancement = persister.getBytecodeEnhancementMetadata(); final EntityEntry entry; - if ( enhancedForLazyLoading ) { - entry = persistenceContext.getEntry( parent ); + if ( bytecodeEnhancement.isEnhancedForLazyLoading() ) { + entry = eventSource.getPersistenceContextInternal().getEntry( parent ); if ( entry != null && entry.getLoadedState() == null && entry.getStatus() == Status.MANAGED ) { @@ -104,10 +98,11 @@ public static void cascade( else { entry = null; } + final Type[] types = persister.getPropertyTypes(); final String[] propertyNames = persister.getPropertyNames(); final CascadeStyle[] cascadeStyles = persister.getPropertyCascadeStyles(); - final boolean hasUninitializedLazyProperties = persister.hasUninitializedLazyProperties( parent ); + final boolean hasUninitializedLazyProperties = bytecodeEnhancement.hasUnFetchedAttributes( parent ); for ( int i = 0; i < types.length; i++) { final CascadeStyle style = cascadeStyles[ i ]; @@ -115,13 +110,14 @@ public static void cascade( final Type type = types[i]; final boolean isUninitializedProperty = hasUninitializedLazyProperties - && !persister.getBytecodeEnhancementMetadata() - .isAttributeLoaded( parent, propertyName ); + && !bytecodeEnhancement.isAttributeLoaded( parent, propertyName ); - if ( style.doCascade( action ) ) { + if ( action.appliesTo( type, style ) ) { final Object child; - if ( isUninitializedProperty ) { - assert enhancedForLazyLoading; + if ( isUninitializedProperty ) { + assert bytecodeEnhancement.isEnhancedForLazyLoading(); + // Hibernate does not support lazy embeddables + assert !type.isComponentType(); // parent is a bytecode enhanced entity. // Cascade to an uninitialized, lazy value only if // parent is managed in the PersistenceContext. @@ -132,13 +128,14 @@ public static void cascade( // parent was not in the PersistenceContext continue; } - if ( type instanceof CollectionType collectionType ) { - // CollectionType#getCollection gets the PersistentCollection + else if ( type instanceof CollectionType collectionType ) { + // CollectionType.getCollection() gets the PersistentCollection // that corresponds to the uninitialized collection from the // PersistenceContext. If not present, an uninitialized // PersistentCollection will be added to the PersistenceContext. // The action may initialize it later, if necessary. - // This needs to be done even when action.performOnLazyProperty() returns false. + // This needs to be done even when action.performOnLazyProperty() + // returns false. child = collectionType.getCollection( collectionType.getKeyOfOwner( parent, eventSource ), eventSource, @@ -146,17 +143,11 @@ public static void cascade( null ); } - else if ( type instanceof AnyType || type instanceof ComponentType ) { - // Hibernate does not support lazy embeddables, so this shouldn't happen. - throw new UnsupportedOperationException( "Lazy embeddables are not supported" ); - } else if ( action.performOnLazyProperty() && type instanceof EntityType ) { - // Only need to initialize a lazy entity attribute when action.performOnLazyProperty() - // returns true. - final LazyAttributeLoadingInterceptor interceptor = - persister.getBytecodeEnhancementMetadata() - .extractInterceptor( parent ); - child = interceptor.fetchAttribute( parent, propertyName ); + // Only need to initialize a lazy entity attribute when + // action.performOnLazyProperty() returns true. + child = bytecodeEnhancement.extractInterceptor( parent ) + .fetchAttribute( parent, propertyName ); } else { @@ -181,21 +172,21 @@ else if ( action.performOnLazyProperty() && type instanceof EntityType ) { false ); } - else { - // If the property is uninitialized, then there cannot be any orphans. - if ( action.deleteOrphans() && !isUninitializedProperty && isLogicalOneToOne( type ) ) { - cascadeLogicalOneToOneOrphanRemoval( - action, - eventSource, - null, - parent, - persister.getValue( parent, i ), - type, - style, - propertyName, - false - ); - } + else if ( action.deleteOrphans() + // If the property is uninitialized, there cannot be any orphans. + && !isUninitializedProperty + && isLogicalOneToOne( type ) ) { + cascadeLogicalOneToOneOrphanRemoval( + action, + eventSource, + null, + parent, + persister.getValue( parent, i ), + type, + style, + propertyName, + false + ); } } @@ -223,11 +214,7 @@ private static void cascadeProperty( if ( child != null ) { if ( type instanceof EntityType || type instanceof CollectionType || type instanceof AnyType ) { - final AssociationType associationType = (AssociationType) type; - final boolean unownedTransient = eventSource.getSessionFactory() - .getSessionFactoryOptions() - .isUnownedAssociationTransientCheck(); - if ( cascadeAssociationNow( action, cascadePoint, associationType, eventSource.getFactory(), unownedTransient ) ) { + if ( action.cascadeNow( cascadePoint, (AssociationType) type, eventSource.getFactory() ) ) { cascadeAssociation( action, cascadePoint, @@ -387,37 +374,11 @@ private static boolean isForeignKeyToParent(Type type) { * * @param type The type representing the attribute metadata * - * @return True if the attribute represents a logical one to one association + * @return True if the attribute represents a logical one-to-one association */ private static boolean isLogicalOneToOne(Type type) { - return type instanceof EntityType entityType && entityType.isLogicalOneToOne(); - } - - private static boolean cascadeAssociationNow( - CascadingAction action, - CascadePoint cascadePoint, - AssociationType associationType, - SessionFactoryImplementor factory, - boolean unownedTransient) { - return associationType.getForeignKeyDirection().cascadeNow( cascadePoint ) - // For check on flush, we should only check unowned associations when strictness is enforced - && ( action != CHECK_ON_FLUSH || unownedTransient || !isUnownedAssociation( associationType, factory ) ); - } - - private static boolean isUnownedAssociation(AssociationType associationType, SessionFactoryImplementor factory) { - if ( associationType instanceof ManyToOneType manyToOne ) { - // logical one-to-one + non-null unique key property name indicates unowned - return manyToOne.isLogicalOneToOne() && manyToOne.getRHSUniqueKeyPropertyName() != null; - } - else if ( associationType instanceof OneToOneType oneToOne ) { - // constrained false + non-null unique key property name indicates unowned - return oneToOne.isNullable() && oneToOne.getRHSUniqueKeyPropertyName() != null; - } - else if ( associationType instanceof CollectionType collectionType ) { - // for collections, we can ask the persister if we're on the inverse side - return collectionType.isInverse( factory ); - } - return false; + return type instanceof EntityType entityType + && entityType.isLogicalOneToOne(); } private static void cascadeComponent( @@ -435,8 +396,9 @@ private static void cascadeComponent( for ( int i = 0; i < types.length; i++ ) { final CascadeStyle componentPropertyStyle = componentType.getCascadeStyle( i ); final String subPropertyName = propertyNames[i]; - if ( componentPropertyStyle.doCascade( action ) - || componentPropertyStyle.hasOrphanDelete() && action.deleteOrphans() ) { + final Type subPropertyType = types[i]; + if ( action.appliesTo( subPropertyType, componentPropertyStyle ) + || componentPropertyStyle.hasOrphanDelete() && action.deleteOrphans() ) { if ( children == null ) { // Get children on demand. children = componentType.getPropertyValues( child, eventSource ); @@ -448,7 +410,7 @@ private static void cascadeComponent( componentPath, parent, children[i], - types[i], + subPropertyType, componentPropertyStyle, subPropertyName, anything, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java index 06049e9a912b..fa515066a5fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/internal/StatefulPersistenceContext.java @@ -56,6 +56,7 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.InstanceIdentityMap; +import org.hibernate.metamodel.MappingMetamodel; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; @@ -1520,7 +1521,7 @@ public Entry[] reentrantSafeEntityEntries() { public Object getOwnerId(String entityName, String propertyName, Object childEntity, Map mergeMap) { final String collectionRole = entityName + '.' + propertyName; - final MappingMetamodelImplementor mappingMetamodel = session.getFactory().getMappingMetamodel(); + final MappingMetamodel mappingMetamodel = session.getFactory().getMappingMetamodel(); final EntityPersister persister = mappingMetamodel.getEntityDescriptor( entityName ); final CollectionPersister collectionPersister = mappingMetamodel.getCollectionDescriptor( collectionRole ); @@ -1541,7 +1542,7 @@ && isFoundInParent( propertyName, childEntity, persister, collectionPersister, p //not found in case, proceed // iterate all the entities currently associated with the persistence context. - for ( Entry me : reentrantSafeEntityEntries() ) { + for ( var me : reentrantSafeEntityEntries() ) { final EntityEntry entityEntry = me.getValue(); // does this entity entry pertain to the entity persister in which we are interested (owner)? if ( persister.isSubclassEntityName( entityEntry.getEntityName() ) ) { @@ -1687,7 +1688,7 @@ public Object getIndexInOwner(String entity, String property, Object childEntity } //Not found in cache, proceed - for ( Entry me : reentrantSafeEntityEntries() ) { + for ( var me : reentrantSafeEntityEntries() ) { final EntityEntry ee = me.getValue(); if ( persister.isSubclassEntityName( ee.getEntityName() ) ) { final Object instance = me.getKey(); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java index a718aa4eef1e..6fe8057f6609 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java @@ -7,8 +7,12 @@ import java.util.Iterator; import org.hibernate.HibernateException; +import org.hibernate.engine.internal.CascadePoint; import org.hibernate.event.spi.EventSource; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; +import org.hibernate.type.Type; /** * A session action that may be cascaded from parent entity to its children @@ -60,4 +64,29 @@ Iterator getCascadableChildrenIterator( * Should this action be performed (or noCascade consulted) in the case of lazy properties. */ boolean performOnLazyProperty(); + + /** + * Does this action have any work to do for the entity type with the given persister? + * + * @since 7 + */ + boolean anythingToCascade(EntityPersister persister); + + /** + * Does this action have any work to do for fields of the given type with the given + * cascade style? + * + * @since 7 + */ + boolean appliesTo(Type type, CascadeStyle style); + + /** + * Does this action cascade to the given association at the given {@link CascadePoint}? + * + * @since 7 + */ + boolean cascadeNow( + CascadePoint cascadePoint, + AssociationType associationType, + SessionFactoryImplementor factory); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java index 15054b72b33f..4bc7fb4dcf13 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java @@ -11,13 +11,19 @@ import org.hibernate.ReplicationMode; import org.hibernate.TransientObjectException; import org.hibernate.collection.spi.PersistentCollection; +import org.hibernate.engine.internal.CascadePoint; import org.hibernate.event.spi.DeleteContext; import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.MergeContext; import org.hibernate.event.spi.PersistContext; import org.hibernate.event.spi.RefreshContext; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; +import org.hibernate.type.ManyToOneType; +import org.hibernate.type.OneToOneType; +import org.hibernate.type.Type; import org.jboss.logging.Logger; import java.lang.invoke.MethodHandles; @@ -73,6 +79,11 @@ public boolean deleteOrphans() { return true; } + @Override + public boolean anythingToCascade(EntityPersister persister) { + return persister.hasCascadeDelete(); + } + @Override public String toString() { return "ACTION_DELETE"; @@ -276,6 +287,11 @@ public boolean performOnLazyProperty() { return false; } + @Override + public boolean anythingToCascade(EntityPersister persister) { + return persister.hasCascadePersist(); + } + @Override public String toString() { return "ACTION_PERSIST"; @@ -319,6 +335,11 @@ public boolean performOnLazyProperty() { return false; } + @Override + public boolean anythingToCascade(EntityPersister persister) { + return persister.hasCascadePersist(); + } + @Override public String toString() { return "ACTION_PERSIST_ON_FLUSH"; @@ -371,6 +392,58 @@ public Iterator getCascadableChildrenIterator( } } + @Override + public boolean anythingToCascade(EntityPersister persister) { + // Must override the implementation from the superclass + // because transient checking happens even for entities + // with cascade NONE on all associations + // if the entity has no associations, we can just ignore it + return persister.hasToOnes() + || persister.hasOwnedCollections() + // when hibernate.unowned_association_transient_check + // is enabled, we have to check unowned associations + || persister.hasCollections() + && persister.getFactory().getSessionFactoryOptions() + .isUnownedAssociationTransientCheck(); + } + + @Override + public boolean appliesTo(Type type, CascadeStyle style) { + // Very important to override the implementation from + // the superclass, because CHECK_ON_FLUSH is the only + // style that executes for fields with cascade NONE + return super.appliesTo( type, style ) + // we only care about associations here, + // but they can hide inside embeddables + && ( type.isComponentType() || type.isAssociationType() ); + } + + @Override + public boolean cascadeNow( + CascadePoint cascadePoint, + AssociationType associationType, + SessionFactoryImplementor factory) { + return super.cascadeNow( cascadePoint, associationType, factory ) + && ( factory.getSessionFactoryOptions().isUnownedAssociationTransientCheck() + || !isUnownedAssociation( associationType, factory ) ); + } + + private static boolean isUnownedAssociation(AssociationType associationType, SessionFactoryImplementor factory) { + if ( associationType instanceof ManyToOneType manyToOne ) { + // logical one-to-one + non-null unique key property name indicates unowned + return manyToOne.isLogicalOneToOne() && manyToOne.getRHSUniqueKeyPropertyName() != null; + } + else if ( associationType instanceof OneToOneType oneToOne ) { + // constrained false + non-null unique key property name indicates unowned + return oneToOne.isNullable() && oneToOne.getRHSUniqueKeyPropertyName() != null; + } + else if ( associationType instanceof CollectionType collectionType ) { + // for collections, we can ask the persister if we're on the inverse side + return collectionType.isInverse( factory ); + } + return false; + } + @Override public boolean deleteOrphans() { return false; @@ -457,6 +530,26 @@ public abstract static class BaseCascadingAction implements CascadingAction me : persistenceContext.reentrantSafeEntityEntries() ) { + for ( var me : persistenceContext.reentrantSafeEntityEntries() ) { // for ( Map.Entry me : IdentityMap.concurrentEntries( persistenceContext.getEntityEntries() ) ) { final EntityEntry entry = me.getValue(); if ( flushable( entry ) ) { @@ -145,9 +145,9 @@ void checkForTransientReferences(EventSource session, PersistenceContext persist // processed, so that all entities which will be persisted are // persistent when we do the check (I wonder if we could move this // into Nullability, instead of abusing the Cascade infrastructure) - for ( Map.Entry me : persistenceContext.reentrantSafeEntityEntries() ) { + for ( var me : persistenceContext.reentrantSafeEntityEntries() ) { final EntityEntry entry = me.getValue(); - if ( flushable( entry ) ) { + if ( checkable( entry ) ) { Cascade.cascade( CascadingActions.CHECK_ON_FLUSH, CascadePoint.BEFORE_FLUSH, @@ -167,6 +167,12 @@ private static boolean flushable(EntityEntry entry) { || status == Status.READ_ONLY; } + private static boolean checkable(EntityEntry entry) { + final Status status = entry.getStatus(); + return status == Status.MANAGED + || status == Status.SAVING; + } + private void cascadeOnFlush(EventSource session, EntityPersister persister, Object object, PersistContext anything) throws HibernateException { final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); @@ -186,11 +192,10 @@ private void prepareCollectionFlushes(PersistenceContext persistenceContext) thr // Initialize dirty flags for arrays + collections with composite elements // and reset reached, doupdate, etc. LOG.debug( "Dirty checking collections" ); - final Map, CollectionEntry> collectionEntries = - persistenceContext.getCollectionEntries(); + final var collectionEntries = persistenceContext.getCollectionEntries(); if ( collectionEntries != null ) { - for ( Map.Entry, CollectionEntry> entry : - ( (InstanceIdentityMap, CollectionEntry>) collectionEntries ).toArray() ) { + final var identityMap = (InstanceIdentityMap, CollectionEntry>) collectionEntries; + for ( var entry : identityMap.toArray() ) { entry.getValue().preFlush( entry.getKey() ); } } @@ -215,12 +220,12 @@ private int flushEntities(final FlushEvent event, final PersistenceContext persi // collections that are changing roles. This might cause entities // to be loaded. // So this needs to be safe from concurrent modification problems. - final Map.Entry[] entityEntries = persistenceContext.reentrantSafeEntityEntries(); + final var entityEntries = persistenceContext.reentrantSafeEntityEntries(); final int count = entityEntries.length; FlushEntityEvent entityEvent = null; //allow reuse of the event as it's heavily allocated in certain use cases int eventGenerationId = 0; //Used to double-check the instance reuse won't cause problems - for ( Map.Entry me : entityEntries ) { + for ( var me : entityEntries ) { // Update the status of the object and if necessary, schedule an update final EntityEntry entry = me.getValue(); final Status status = entry.getStatus(); @@ -264,17 +269,18 @@ private FlushEntityEvent createOrReuseEventInstance( private int flushCollections(final EventSource session, final PersistenceContext persistenceContext) throws HibernateException { LOG.trace( "Processing unreferenced collections" ); - final Map, CollectionEntry> collectionEntries = persistenceContext.getCollectionEntries(); + final var collectionEntries = persistenceContext.getCollectionEntries(); final int count; if ( collectionEntries == null ) { count = 0; } else { count = collectionEntries.size(); - for ( Map.Entry, CollectionEntry> me : ( (InstanceIdentityMap, CollectionEntry>) collectionEntries ).toArray() ) { + final var identityMap = (InstanceIdentityMap, CollectionEntry>) collectionEntries; + for ( var me : identityMap.toArray() ) { final CollectionEntry ce = me.getValue(); if ( !ce.isReached() && !ce.isIgnore() ) { - Collections.processUnreachableCollection( me.getKey(), session ); + processUnreachableCollection( me.getKey(), session ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java index 8ab6a700dbec..7b5406f41d99 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Property.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Property.java @@ -6,12 +6,14 @@ import java.io.Serializable; import java.util.ArrayList; +import java.util.EnumSet; import java.util.Map; import java.util.StringTokenizer; import org.hibernate.HibernateException; import org.hibernate.Internal; import org.hibernate.MappingException; +import org.hibernate.annotations.CascadeType; import org.hibernate.boot.model.relational.Database; import org.hibernate.boot.model.relational.SqlStringGenerationContext; import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper; @@ -38,6 +40,8 @@ import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableList; +import static org.hibernate.boot.model.internal.BinderHelper.renderCascadeTypeList; +import static org.hibernate.internal.util.StringHelper.isBlank; /** * A mapping model object representing a property or field of an {@linkplain PersistentClass entity} @@ -184,17 +188,23 @@ else if ( elementType instanceof ComponentType componentType ) { } private static CascadeStyle getCascadeStyle(String cascade) { - if ( cascade==null || cascade.equals("none") ) { + if ( cascade==null || cascade.equals("none") || isBlank(cascade) ) { return CascadeStyles.NONE; } else { final StringTokenizer tokens = new StringTokenizer(cascade, ", "); - final CascadeStyle[] styles = new CascadeStyle[ tokens.countTokens() ] ; - int i=0; - while ( tokens.hasMoreTokens() ) { - styles[i++] = CascadeStyles.getCascadeStyle( tokens.nextToken() ); + final int length = tokens.countTokens(); + if ( length == 1) { + return CascadeStyles.getCascadeStyle( tokens.nextToken() ); + } + else { + final CascadeStyle[] styles = new CascadeStyle[length]; + int i = 0; + while ( tokens.hasMoreTokens() ) { + styles[i++] = CascadeStyles.getCascadeStyle( tokens.nextToken() ); + } + return new CascadeStyles.MultipleCascadeStyle( styles ); } - return new CascadeStyles.MultipleCascadeStyle(styles); } } @@ -206,6 +216,10 @@ public void setCascade(String cascade) { this.cascade = cascade; } + public void setCascade(EnumSet cascadeTypes) { + cascade = cascadeTypes == null ? null : renderCascadeTypeList( cascadeTypes ); + } + public void setName(String name) { this.name = name==null ? null : name.intern(); } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 7791f06ffc7a..25cf35bc1474 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -3754,6 +3754,16 @@ public boolean hasCascades() { return entityMetamodel.hasCascades(); } + @Override + public boolean hasToOnes() { + return entityMetamodel.hasToOnes(); + } + + @Override + public boolean hasCascadePersist() { + return entityMetamodel.hasCascadePersist(); + } + @Override public boolean hasCascadeDelete() { return entityMetamodel.hasCascadeDelete(); @@ -4294,7 +4304,7 @@ protected void linkToSession(Object entity, SharedSessionContractImplementor ses private void setSession(PersistentAttributeInterceptable entity, SharedSessionContractImplementor session) { final BytecodeLazyAttributeInterceptor interceptor = - getEntityMetamodel().getBytecodeEnhancementMetadata() + entityMetamodel.getBytecodeEnhancementMetadata() .extractLazyInterceptor( entity ); if ( interceptor != null ) { interceptor.setSession( session ); @@ -4337,9 +4347,9 @@ && hasSubclasses() // todo (6.0) : this previously used `org.hibernate.tuple.entity.EntityTuplizer#determineConcreteSubclassEntityName` // - we may need something similar here... for ( EntityMappingType subclassMappingType : subclassMappingTypes.values() ) { - if ( subclassMappingType.getEntityPersister().getRepresentationStrategy() - .getInstantiator().isSameClass(instance ) ) { - return subclassMappingType.getEntityPersister(); + final EntityPersister persister = subclassMappingType.getEntityPersister(); + if ( persister.getRepresentationStrategy().getInstantiator().isSameClass( instance ) ) { + return persister; } } } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index c35453ee5d9b..9008d1e102d6 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -332,24 +332,40 @@ default void visitQuerySpaces(Consumer querySpaceConsumer) { /** * Determine whether this entity has any - * (non-{@linkplain org.hibernate.engine.spi.CascadeStyles#NONE none}) cascading. + * {@linkplain org.hibernate.engine.spi.CascadeStyles#NONE cascading} operations. * * @return True if the entity has any properties with a cascade other than NONE; * false otherwise (aka, no cascading). */ boolean hasCascades(); + /** + * Determine whether this entity has any + * {@linkplain org.hibernate.engine.spi.CascadeStyles#PERSIST persist cascading}. + * + * @return True if the entity has any properties with a cascade PERSIST or ALL; + * false otherwise. + */ + boolean hasCascadePersist(); + /** * Determine whether this entity has any * {@linkplain org.hibernate.engine.spi.CascadeStyles#DELETE delete cascading}. * - * @return True if the entity has any properties with a cascade other than NONE; + * @return True if the entity has any properties with a cascade REMOVE or ALL; * false otherwise. */ - default boolean hasCascadeDelete() { - //bad default implementation for compatibility - return hasCascades(); - } + boolean hasCascadeDelete(); + + /** + * Determine whether this entity has any many-to-one or one-to-one associations. + * + * @return True if the entity has a many-to-one or one-to-one association; + * false otherwise. + * + * @since 7 + */ + boolean hasToOnes(); /** * Determine whether this entity has any owned collections. @@ -357,10 +373,7 @@ default boolean hasCascadeDelete() { * @return True if the entity has an owned collection; * false otherwise. */ - default boolean hasOwnedCollections() { - //bad default implementation for compatibility - return hasCollections(); - } + boolean hasOwnedCollections(); /** * Determine whether instances of this entity are considered mutable. diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 9d167ee00986..301902ccdfc1 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -127,6 +127,8 @@ public class EntityMetamodel implements Serializable { private boolean lazy; //not final because proxy factory creation can fail private final boolean hasCascades; + private final boolean hasToOnes; + private final boolean hasCascadePersist; private final boolean hasCascadeDelete; private final boolean mutable; private final boolean isAbstract; @@ -240,6 +242,8 @@ public EntityMetamodel( int tempVersionProperty = NO_VERSION_INDX; boolean foundCascade = false; + boolean foundToOne = false; + boolean foundCascadePersist = false; boolean foundCascadeDelete = false; boolean foundCollection = false; boolean foundOwnedCollection = false; @@ -376,10 +380,17 @@ else if ( !allowMutation ) { if ( cascadeStyles[i] != CascadeStyles.NONE ) { foundCascade = true; } + if ( cascadeStyles[i].doCascade(CascadingActions.PERSIST) + || cascadeStyles[i].doCascade(CascadingActions.PERSIST_ON_FLUSH) ) { + foundCascadePersist = true; + } if ( cascadeStyles[i].doCascade(CascadingActions.REMOVE) ) { foundCascadeDelete = true; } + if ( indicatesToOne( attribute.getType() ) ) { + foundToOne = true; + } if ( indicatesCollection( attribute.getType() ) ) { foundCollection = true; } @@ -414,6 +425,8 @@ else if ( !allowMutation ) { versionGenerator = tempVersionGenerator; hasCascades = foundCascade; + hasToOnes = foundToOne; + hasCascadePersist = foundCascadePersist; hasCascadeDelete = foundCascadeDelete; hasNonIdentifierPropertyNamedId = foundNonIdentifierPropertyNamedId; versionPropertyIndex = tempVersionProperty; @@ -621,13 +634,26 @@ public Set getSubclassEntityNames() { return subclassEntityNames; } + private static boolean indicatesToOne(Type type) { + if ( type.isEntityType() ) { + return true; + } + else if ( type instanceof CompositeType compositeType ) { + for ( Type subtype : compositeType.getSubtypes() ) { + if ( indicatesToOne( subtype ) ) { + return true; + } + } + } + return false; + } + private static boolean indicatesCollection(Type type) { if ( type instanceof CollectionType ) { return true; } - else if ( type.isComponentType() ) { - Type[] subtypes = ( (CompositeType) type ).getSubtypes(); - for ( Type subtype : subtypes ) { + else if ( type instanceof CompositeType compositeType ) { + for ( Type subtype : compositeType.getSubtypes() ) { if ( indicatesCollection( subtype ) ) { return true; } @@ -640,8 +666,7 @@ private static boolean indicatesOwnedCollection(Type type, MetadataImplementor m if ( type instanceof CollectionType collectionType ) { return !metadata.getCollectionBinding( collectionType.getRole() ).isInverse(); } - else if ( type.isComponentType() ) { - final CompositeType compositeType = (CompositeType) type; + else if ( type instanceof CompositeType compositeType ) { for ( Type subtype : compositeType.getSubtypes() ) { if ( indicatesOwnedCollection( subtype, metadata ) ) { return true; @@ -742,10 +767,18 @@ public boolean hasCascades() { return hasCascades; } + public boolean hasToOnes() { + return hasToOnes; + } + public boolean hasCascadeDelete() { return hasCascadeDelete; } + public boolean hasCascadePersist() { + return hasCascadePersist; + } + public boolean isMutable() { return mutable; } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/HierarchyPropertyAccessTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/HierarchyPropertyAccessTest.java index cf3482429a68..e1efc7e569b8 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/HierarchyPropertyAccessTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/access/HierarchyPropertyAccessTest.java @@ -44,6 +44,10 @@ public void testParent(SessionFactoryScope scope) { assertThat( entity.getSuperProperty() ).isEqualTo( 8 ); entity.setProperty( "transient: updated" ); + // I had to add this call because this test was + // assuming that Hibernate would call this getter + // during flush, but that is no longer true + entity.getPersistProperty(); } ); scope.inTransaction( session -> { @@ -66,6 +70,10 @@ public void testChild(SessionFactoryScope scope) { assertThat( entity.getSuperProperty() ).isEqualTo( 8 ); entity.setProperty( "transient: updated" ); + // I had to add this call because this test was + // assuming that Hibernate would call this getter + // during flush, but that is no longer true + entity.getPersistProperty(); } ); scope.inTransaction( session -> { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java index 142df17c186c..382dbd68c57f 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java @@ -304,6 +304,26 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasCascadeDelete() { + return false; + } + + @Override + public boolean hasToOnes() { + return false; + } + + @Override + public boolean hasCascadePersist() { + return false; + } + + @Override + public boolean hasOwnedCollections() { + return false; + } + @Override public boolean isMutable() { return false; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java index 7eac16cb6a3c..038f3c50d0a3 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java @@ -329,6 +329,26 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasCascadeDelete() { + return false; + } + + @Override + public boolean hasToOnes() { + return false; + } + + @Override + public boolean hasCascadePersist() { + return false; + } + + @Override + public boolean hasOwnedCollections() { + return false; + } + @Override public boolean isMutable() { return false; diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java index 3467157f651a..5151f8d53fa5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java @@ -257,6 +257,26 @@ public boolean hasCascades() { return false; } + @Override + public boolean hasCascadeDelete() { + return false; + } + + @Override + public boolean hasToOnes() { + return false; + } + + @Override + public boolean hasCascadePersist() { + return false; + } + + @Override + public boolean hasOwnedCollections() { + return false; + } + public boolean isMutable() { return true; }