Skip to content

Commit c48c9b8

Browse files
committed
HHH-18936 suppress TransientObjectException when the FK is marked @onDelete(CASCADE)
1 parent 6b5099d commit c48c9b8

File tree

15 files changed

+343
-20
lines changed

15 files changed

+343
-20
lines changed

hibernate-core/src/main/java/org/hibernate/engine/internal/Cascade.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.List;
1111

1212
import org.hibernate.HibernateException;
13+
import org.hibernate.annotations.OnDeleteAction;
1314
import org.hibernate.collection.spi.PersistentCollection;
1415
import org.hibernate.engine.spi.CascadeStyle;
1516
import org.hibernate.engine.spi.CascadingAction;
@@ -111,6 +112,7 @@ public static <T> void cascade(
111112
final boolean isUninitializedProperty =
112113
hasUninitializedLazyProperties
113114
&& !bytecodeEnhancement.isAttributeLoaded( parent, propertyName );
115+
final boolean isCascadeDeleteEnabled = cascadeDeleteEnabled( action, persister, i );
114116

115117
if ( action.appliesTo( type, style ) ) {
116118
final Object child;
@@ -170,7 +172,7 @@ else if ( action.performOnLazyProperty() && type instanceof EntityType ) {
170172
style,
171173
propertyName,
172174
anything,
173-
false
175+
isCascadeDeleteEnabled
174176
);
175177
}
176178
else if ( action.deleteOrphans()
@@ -186,7 +188,7 @@ && isLogicalOneToOne( type ) ) {
186188
type,
187189
style,
188190
propertyName,
189-
false
191+
isCascadeDeleteEnabled
190192
);
191193
}
192194
}
@@ -421,7 +423,7 @@ private static <T> void cascadeComponent(
421423
componentPropertyStyle,
422424
subPropertyName,
423425
anything,
424-
false
426+
cascadeDeleteEnabled( action, componentType, i )
425427
);
426428
}
427429
}
@@ -508,7 +510,7 @@ private static <T> void cascadeCollection(
508510
style,
509511
elemType,
510512
anything,
511-
persister.isCascadeDeleteEnabled()
513+
cascadeDeleteEnabled( action, persister )
512514
);
513515
}
514516
}
@@ -607,7 +609,8 @@ private static <T> void cascadeCollectionElements(
607609
final PersistentCollection<?> persistentCollection =
608610
child instanceof PersistentCollection<?> collection
609611
? collection
610-
: eventSource.getPersistenceContext().getCollectionHolder( child );
612+
: eventSource.getPersistenceContextInternal()
613+
.getCollectionHolder( child );
611614

612615
final boolean deleteOrphans = style.hasOrphanDelete()
613616
&& action.deleteOrphans()
@@ -654,4 +657,19 @@ private static void deleteOrphans(EventSource eventSource, String entityName, Pe
654657
}
655658
}
656659
}
660+
661+
private static <T> boolean cascadeDeleteEnabled(CascadingAction<T> action, CollectionPersister persister) {
662+
return action.directionAffectedByCascadeDelete() == ForeignKeyDirection.FROM_PARENT
663+
&& persister.isCascadeDeleteEnabled();
664+
}
665+
666+
private static <T> boolean cascadeDeleteEnabled(CascadingAction<T> action, EntityPersister persister, int i) {
667+
return action.directionAffectedByCascadeDelete() == ForeignKeyDirection.TO_PARENT
668+
&& persister.getEntityMetamodel().getPropertyOnDeleteActions()[i] == OnDeleteAction.CASCADE;
669+
}
670+
671+
private static <T> boolean cascadeDeleteEnabled(CascadingAction<T> action, CompositeType componentType, int i) {
672+
return action.directionAffectedByCascadeDelete() == ForeignKeyDirection.TO_PARENT
673+
&& componentType.getOnDeleteAction( i ) == OnDeleteAction.CASCADE;
674+
}
657675
}

hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingAction.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@
88
import java.util.List;
99

1010
import org.checkerframework.checker.nullness.qual.Nullable;
11+
import org.hibernate.Incubating;
1112
import org.hibernate.engine.internal.CascadePoint;
1213
import org.hibernate.event.spi.EventSource;
1314
import org.hibernate.persister.entity.EntityPersister;
1415
import org.hibernate.type.AssociationType;
1516
import org.hibernate.type.CollectionType;
17+
import org.hibernate.type.ForeignKeyDirection;
1618
import org.hibernate.type.Type;
1719

1820
/**
1921
* A session action that may be cascaded from parent entity to its children
2022
*
23+
* @param <T> The type of some context propagated with the cascading action
24+
*
2125
* @author Gavin King
2226
* @author Steve Ebersole
2327
*/
@@ -32,9 +36,9 @@ public interface CascadingAction<T> {
3236
* @param parentEntityName The name of the parent entity
3337
* @param propertyName The name of the attribute of the parent entity being cascaded
3438
* @param attributePath The full path of the attribute of the parent entity being cascaded
35-
* @param anything Anything ;) Typically some form of cascade-local cache
36-
* which is specific to each {@link CascadingAction} type
37-
* @param isCascadeDeleteEnabled Are cascading deletes enabled.
39+
* @param anything Some context specific to the kind of {@link CascadingAction}
40+
* @param isCascadeDeleteEnabled Whether the foreign key is declared with
41+
* {@link org.hibernate.annotations.OnDeleteAction#CASCADE on delete cascade}.
3842
*/
3943
void cascade(
4044
EventSource session,
@@ -111,4 +115,17 @@ boolean cascadeNow(
111115
CascadePoint cascadePoint,
112116
AssociationType associationType,
113117
SessionFactoryImplementor factory);
118+
119+
/**
120+
* The cascade direction in which we care whether the foreign key is declared with
121+
* {@link org.hibernate.annotations.OnDeleteAction#CASCADE on delete cascade}.
122+
*
123+
* @apiNote This allows us to reuse the long-existing boolean parameter of
124+
* {@link #cascade(EventSource,Object,String,String,String,List,Object,boolean)}
125+
* for multiple purposes.
126+
*
127+
* @since 7
128+
*/
129+
@Incubating
130+
ForeignKeyDirection directionAffectedByCascadeDelete();
114131
}

hibernate-core/src/main/java/org/hibernate/engine/spi/CascadingActions.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.hibernate.persister.entity.EntityPersister;
2222
import org.hibernate.type.AssociationType;
2323
import org.hibernate.type.CollectionType;
24+
import org.hibernate.type.ForeignKeyDirection;
2425
import org.hibernate.type.ManyToOneType;
2526
import org.hibernate.type.OneToOneType;
2627
import org.hibernate.type.Type;
@@ -89,6 +90,11 @@ public boolean anythingToCascade(EntityPersister persister) {
8990
return persister.hasCascadeDelete();
9091
}
9192

93+
@Override
94+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
95+
return ForeignKeyDirection.FROM_PARENT;
96+
}
97+
9298
@Override
9399
public String toString() {
94100
return "ACTION_DELETE";
@@ -387,7 +393,7 @@ public void cascade(
387393
Void nothing,
388394
boolean isCascadeDeleteEnabled)
389395
throws HibernateException {
390-
if ( child != null && isChildTransient( session, child, childEntityName ) ) {
396+
if ( child != null && isChildTransient( session, child, childEntityName, isCascadeDeleteEnabled ) ) {
391397
throw new TransientPropertyValueException(
392398
"Persistent instance of '" + parentEntityName
393399
+ "' references an unsaved transient instance of '" + childEntityName
@@ -481,13 +487,18 @@ public boolean performOnLazyProperty() {
481487
return false;
482488
}
483489

490+
@Override
491+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
492+
return ForeignKeyDirection.TO_PARENT;
493+
}
494+
484495
@Override
485496
public String toString() {
486497
return "ACTION_CHECK_ON_FLUSH";
487498
}
488499
};
489500

490-
private static boolean isChildTransient(EventSource session, Object child, String entityName) {
501+
private static boolean isChildTransient(EventSource session, Object child, String entityName, boolean isCascadeDeleteEnabled) {
491502
if ( isHibernateProxy( child ) ) {
492503
// a proxy is always non-transient
493504
// and ForeignKeys.isTransient()
@@ -502,7 +513,11 @@ private static boolean isChildTransient(EventSource session, Object child, Strin
502513
// we are good, even if it's not yet
503514
// inserted, since ordering problems
504515
// are detected and handled elsewhere
505-
return entry.getStatus().isDeletedOrGone();
516+
return entry.getStatus().isDeletedOrGone()
517+
// if the foreign key is 'on delete cascade'
518+
// we don't have to throw because the database
519+
// will delete the parent for us
520+
&& !isCascadeDeleteEnabled;
506521
}
507522
else {
508523
// TODO: check if it is a merged entity which has not yet been flushed
@@ -580,6 +595,11 @@ public boolean cascadeNow(
580595
SessionFactoryImplementor factory) {
581596
return associationType.getForeignKeyDirection().cascadeNow( cascadePoint );
582597
}
598+
599+
@Override
600+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
601+
return null;
602+
}
583603
}
584604

585605
/**

hibernate-core/src/main/java/org/hibernate/mapping/Property.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.Internal;
1515
import org.hibernate.MappingException;
1616
import org.hibernate.annotations.CascadeType;
17+
import org.hibernate.annotations.OnDeleteAction;
1718
import org.hibernate.boot.model.relational.Database;
1819
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
1920
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
@@ -139,6 +140,10 @@ public void resetOptional(boolean optional) {
139140
}
140141
}
141142

143+
public OnDeleteAction getOnDeleteAction() {
144+
return value instanceof ToOne toOne ? toOne.getOnDeleteAction() : null;
145+
}
146+
142147
public CascadeStyle getCascadeStyle() throws MappingException {
143148
final Type type = value.getType();
144149
if ( type instanceof AnyType ) {

hibernate-core/src/main/java/org/hibernate/tuple/AbstractNonIdentifierAttribute.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.tuple;
66

77
import org.hibernate.FetchMode;
8+
import org.hibernate.annotations.OnDeleteAction;
89
import org.hibernate.engine.spi.CascadeStyle;
910
import org.hibernate.engine.spi.SessionFactoryImplementor;
1011
import org.hibernate.persister.walking.spi.AttributeSource;
@@ -92,6 +93,11 @@ public CascadeStyle getCascadeStyle() {
9293
return attributeInformation.getCascadeStyle();
9394
}
9495

96+
@Override
97+
public OnDeleteAction getOnDeleteAction() {
98+
return attributeInformation.getOnDeleteAction();
99+
}
100+
95101
@Override
96102
public FetchMode getFetchMode() {
97103
return attributeInformation.getFetchMode();

hibernate-core/src/main/java/org/hibernate/tuple/BaselineAttributeInformation.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.tuple;
66

77
import org.hibernate.FetchMode;
8+
import org.hibernate.annotations.OnDeleteAction;
89
import org.hibernate.engine.spi.CascadeStyle;
910

1011
/**
@@ -19,6 +20,7 @@ public class BaselineAttributeInformation {
1920
private final boolean nullable;
2021
private final boolean dirtyCheckable;
2122
private final boolean versionable;
23+
private final OnDeleteAction onDeleteAction;
2224
private final CascadeStyle cascadeStyle;
2325
private final FetchMode fetchMode;
2426

@@ -30,6 +32,7 @@ public BaselineAttributeInformation(
3032
boolean dirtyCheckable,
3133
boolean versionable,
3234
CascadeStyle cascadeStyle,
35+
OnDeleteAction onDeleteAction,
3336
FetchMode fetchMode) {
3437
this.lazy = lazy;
3538
this.insertable = insertable;
@@ -38,6 +41,7 @@ public BaselineAttributeInformation(
3841
this.dirtyCheckable = dirtyCheckable;
3942
this.versionable = versionable;
4043
this.cascadeStyle = cascadeStyle;
44+
this.onDeleteAction = onDeleteAction;
4145
this.fetchMode = fetchMode;
4246
}
4347

@@ -73,6 +77,10 @@ public FetchMode getFetchMode() {
7377
return fetchMode;
7478
}
7579

80+
public OnDeleteAction getOnDeleteAction() {
81+
return onDeleteAction;
82+
}
83+
7684
public static class Builder {
7785
private boolean lazy;
7886
private boolean insertable;
@@ -81,6 +89,7 @@ public static class Builder {
8189
private boolean dirtyCheckable;
8290
private boolean versionable;
8391
private CascadeStyle cascadeStyle;
92+
private OnDeleteAction onDeleteAction;
8493
private FetchMode fetchMode;
8594

8695
public Builder setLazy(boolean lazy) {
@@ -118,6 +127,11 @@ public Builder setCascadeStyle(CascadeStyle cascadeStyle) {
118127
return this;
119128
}
120129

130+
public Builder setOnDeleteAction(OnDeleteAction onDeleteAction) {
131+
this.onDeleteAction = onDeleteAction;
132+
return this;
133+
}
134+
121135
public Builder setFetchMode(FetchMode fetchMode) {
122136
this.fetchMode = fetchMode;
123137
return this;
@@ -132,6 +146,7 @@ public BaselineAttributeInformation createInformation() {
132146
dirtyCheckable,
133147
versionable,
134148
cascadeStyle,
149+
onDeleteAction,
135150
fetchMode
136151
);
137152
}

hibernate-core/src/main/java/org/hibernate/tuple/NonIdentifierAttribute.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.tuple;
66

77
import org.hibernate.FetchMode;
8+
import org.hibernate.annotations.OnDeleteAction;
89
import org.hibernate.engine.spi.CascadeStyle;
910

1011
/**
@@ -32,5 +33,7 @@ public interface NonIdentifierAttribute extends Attribute {
3233

3334
CascadeStyle getCascadeStyle();
3435

36+
OnDeleteAction getOnDeleteAction();
37+
3538
FetchMode getFetchMode();
3639
}

hibernate-core/src/main/java/org/hibernate/tuple/PropertyFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public static VersionProperty buildVersionProperty(
9999
.setDirtyCheckable( property.isUpdateable() && !lazy )
100100
.setVersionable( property.isOptimisticLocked() )
101101
.setCascadeStyle( property.getCascadeStyle() )
102+
.setOnDeleteAction( property.getOnDeleteAction() )
102103
.createInformation()
103104
);
104105
}
@@ -169,6 +170,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute(
169170
.setDirtyCheckable( alwaysDirtyCheck || property.isUpdateable() )
170171
.setVersionable( property.isOptimisticLocked() )
171172
.setCascadeStyle( property.getCascadeStyle() )
173+
.setOnDeleteAction( property.getOnDeleteAction() )
172174
.setFetchMode( property.getValue().getFetchMode() )
173175
.createInformation()
174176
);
@@ -188,6 +190,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute(
188190
.setDirtyCheckable( alwaysDirtyCheck || property.isUpdateable() )
189191
.setVersionable( property.isOptimisticLocked() )
190192
.setCascadeStyle( property.getCascadeStyle() )
193+
.setOnDeleteAction( property.getOnDeleteAction() )
191194
.setFetchMode( property.getValue().getFetchMode() )
192195
.createInformation()
193196
);
@@ -209,6 +212,7 @@ public static NonIdentifierAttribute buildEntityBasedAttribute(
209212
.setDirtyCheckable( alwaysDirtyCheck || property.isUpdateable() )
210213
.setVersionable( property.isOptimisticLocked() )
211214
.setCascadeStyle( property.getCascadeStyle() )
215+
.setOnDeleteAction( property.getOnDeleteAction() )
212216
.setFetchMode( property.getValue().getFetchMode() )
213217
.createInformation()
214218
);

hibernate-core/src/main/java/org/hibernate/tuple/StandardProperty.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package org.hibernate.tuple;
66

77
import org.hibernate.FetchMode;
8+
import org.hibernate.annotations.OnDeleteAction;
89
import org.hibernate.engine.spi.CascadeStyle;
910
import org.hibernate.type.Type;
1011

@@ -37,6 +38,7 @@ public StandardProperty(
3738
boolean checkable,
3839
boolean versionable,
3940
CascadeStyle cascadeStyle,
41+
OnDeleteAction onDeleteAction,
4042
FetchMode fetchMode) {
4143
super(
4244
null,
@@ -52,6 +54,7 @@ public StandardProperty(
5254
.setDirtyCheckable( checkable )
5355
.setVersionable( versionable )
5456
.setCascadeStyle( cascadeStyle )
57+
.setOnDeleteAction( onDeleteAction )
5558
.setFetchMode( fetchMode )
5659
.createInformation()
5760
);

0 commit comments

Comments
 (0)