1111import org .hibernate .ReplicationMode ;
1212import org .hibernate .TransientObjectException ;
1313import org .hibernate .collection .spi .PersistentCollection ;
14+ import org .hibernate .engine .internal .CascadePoint ;
1415import org .hibernate .event .spi .DeleteContext ;
1516import org .hibernate .event .spi .EventSource ;
1617import org .hibernate .event .spi .MergeContext ;
1718import org .hibernate .event .spi .PersistContext ;
1819import org .hibernate .event .spi .RefreshContext ;
1920import org .hibernate .internal .CoreMessageLogger ;
2021import org .hibernate .persister .entity .EntityPersister ;
22+ import org .hibernate .type .AssociationType ;
2123import org .hibernate .type .CollectionType ;
24+ import org .hibernate .type .ManyToOneType ;
25+ import org .hibernate .type .OneToOneType ;
2226import org .hibernate .type .Type ;
2327import org .jboss .logging .Logger ;
2428
@@ -395,7 +399,12 @@ public boolean anythingToCascade(EntityPersister persister) {
395399 // with cascade NONE on all associations
396400 // if the entity has no associations, we can just ignore it
397401 return persister .hasToOnes ()
398- || persister .hasOwnedCollections ();
402+ || persister .hasOwnedCollections ()
403+ // when hibernate.unowned_association_transient_check
404+ // is enabled, we have to check unowned associations
405+ || persister .hasCollections ()
406+ && persister .getFactory ().getSessionFactoryOptions ()
407+ .isUnownedAssociationTransientCheck ();
399408 }
400409
401410 @ Override
@@ -409,6 +418,32 @@ public boolean appliesTo(Type type, CascadeStyle style) {
409418 && ( type .isComponentType () || type .isAssociationType () );
410419 }
411420
421+ @ Override
422+ public boolean cascadeNow (
423+ CascadePoint cascadePoint ,
424+ AssociationType associationType ,
425+ SessionFactoryImplementor factory ) {
426+ return super .cascadeNow ( cascadePoint , associationType , factory )
427+ && ( factory .getSessionFactoryOptions ().isUnownedAssociationTransientCheck ()
428+ || !isUnownedAssociation ( associationType , factory ) );
429+ }
430+
431+ private static boolean isUnownedAssociation (AssociationType associationType , SessionFactoryImplementor factory ) {
432+ if ( associationType instanceof ManyToOneType manyToOne ) {
433+ // logical one-to-one + non-null unique key property name indicates unowned
434+ return manyToOne .isLogicalOneToOne () && manyToOne .getRHSUniqueKeyPropertyName () != null ;
435+ }
436+ else if ( associationType instanceof OneToOneType oneToOne ) {
437+ // constrained false + non-null unique key property name indicates unowned
438+ return oneToOne .isNullable () && oneToOne .getRHSUniqueKeyPropertyName () != null ;
439+ }
440+ else if ( associationType instanceof CollectionType collectionType ) {
441+ // for collections, we can ask the persister if we're on the inverse side
442+ return collectionType .isInverse ( factory );
443+ }
444+ return false ;
445+ }
446+
412447 @ Override
413448 public boolean deleteOrphans () {
414449 return false ;
@@ -507,6 +542,14 @@ public boolean anythingToCascade(EntityPersister persister) {
507542 public boolean appliesTo (Type type , CascadeStyle style ) {
508543 return style .doCascade ( this );
509544 }
545+
546+ @ Override
547+ public boolean cascadeNow (
548+ CascadePoint cascadePoint ,
549+ AssociationType associationType ,
550+ SessionFactoryImplementor factory ) {
551+ return associationType .getForeignKeyDirection ().cascadeNow ( cascadePoint );
552+ }
510553 }
511554
512555 /**
0 commit comments