Skip to content

Commit a7d29a0

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

File tree

15 files changed

+344
-20
lines changed

15 files changed

+344
-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 @Nullable
130+
ForeignKeyDirection directionAffectedByCascadeDelete();
114131
}

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55
package org.hibernate.engine.spi;
66

7+
import org.checkerframework.checker.nullness.qual.Nullable;
78
import org.hibernate.HibernateException;
89
import org.hibernate.Internal;
910
import org.hibernate.LockMode;
@@ -21,6 +22,7 @@
2122
import org.hibernate.persister.entity.EntityPersister;
2223
import org.hibernate.type.AssociationType;
2324
import org.hibernate.type.CollectionType;
25+
import org.hibernate.type.ForeignKeyDirection;
2426
import org.hibernate.type.ManyToOneType;
2527
import org.hibernate.type.OneToOneType;
2628
import org.hibernate.type.Type;
@@ -89,6 +91,11 @@ public boolean anythingToCascade(EntityPersister persister) {
8991
return persister.hasCascadeDelete();
9092
}
9193

94+
@Override
95+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
96+
return ForeignKeyDirection.FROM_PARENT;
97+
}
98+
9299
@Override
93100
public String toString() {
94101
return "ACTION_DELETE";
@@ -387,7 +394,7 @@ public void cascade(
387394
Void nothing,
388395
boolean isCascadeDeleteEnabled)
389396
throws HibernateException {
390-
if ( child != null && isChildTransient( session, child, childEntityName ) ) {
397+
if ( child != null && isChildTransient( session, child, childEntityName, isCascadeDeleteEnabled ) ) {
391398
throw new TransientPropertyValueException(
392399
"Persistent instance of '" + parentEntityName
393400
+ "' references an unsaved transient instance of '" + childEntityName
@@ -481,13 +488,18 @@ public boolean performOnLazyProperty() {
481488
return false;
482489
}
483490

491+
@Override
492+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
493+
return ForeignKeyDirection.TO_PARENT;
494+
}
495+
484496
@Override
485497
public String toString() {
486498
return "ACTION_CHECK_ON_FLUSH";
487499
}
488500
};
489501

490-
private static boolean isChildTransient(EventSource session, Object child, String entityName) {
502+
private static boolean isChildTransient(EventSource session, Object child, String entityName, boolean isCascadeDeleteEnabled) {
491503
if ( isHibernateProxy( child ) ) {
492504
// a proxy is always non-transient
493505
// and ForeignKeys.isTransient()
@@ -502,7 +514,11 @@ private static boolean isChildTransient(EventSource session, Object child, Strin
502514
// we are good, even if it's not yet
503515
// inserted, since ordering problems
504516
// are detected and handled elsewhere
505-
return entry.getStatus().isDeletedOrGone();
517+
return entry.getStatus().isDeletedOrGone()
518+
// if the foreign key is 'on delete cascade'
519+
// we don't have to throw because the database
520+
// will delete the parent for us
521+
&& !isCascadeDeleteEnabled;
506522
}
507523
else {
508524
// TODO: check if it is a merged entity which has not yet been flushed
@@ -580,6 +596,11 @@ public boolean cascadeNow(
580596
SessionFactoryImplementor factory) {
581597
return associationType.getForeignKeyDirection().cascadeNow( cascadePoint );
582598
}
599+
600+
@Override @Nullable
601+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
602+
return null;
603+
}
583604
}
584605

585606
/**

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)