Skip to content

Commit 046dfda

Browse files
gavinkingdreab8
authored andcommitted
HHH-18936 suppress TransientObjectException when the FK is marked @onDelete(CASCADE)
1 parent 26446c3 commit 046dfda

16 files changed

+378
-44
lines changed

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

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import org.hibernate.HibernateException;
1616
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
17+
import org.hibernate.annotations.OnDeleteAction;
1718
import org.hibernate.collection.spi.PersistentCollection;
1819
import org.hibernate.engine.spi.CascadeStyle;
1920
import org.hibernate.engine.spi.CascadingAction;
@@ -137,6 +138,7 @@ public static <T> void cascade(
137138
final boolean isUninitializedProperty =
138139
hasUninitializedLazyProperties &&
139140
!persister.getBytecodeEnhancementMetadata().isAttributeLoaded( parent, propertyName );
141+
final boolean isCascadeDeleteEnabled = cascadeDeleteEnabled( action, persister, i );
140142

141143
if ( style.doCascade( action ) ) {
142144
final Object child;
@@ -200,7 +202,7 @@ else if ( action.performOnLazyProperty() && type instanceof EntityType ) {
200202
style,
201203
propertyName,
202204
anything,
203-
false
205+
isCascadeDeleteEnabled
204206
);
205207
}
206208
else {
@@ -215,7 +217,7 @@ else if ( action.performOnLazyProperty() && type instanceof EntityType ) {
215217
type,
216218
style,
217219
propertyName,
218-
false
220+
isCascadeDeleteEnabled
219221
);
220222
}
221223
}
@@ -471,8 +473,8 @@ private static <T> void cascadeComponent(
471473
componentPropertyStyle,
472474
subPropertyName,
473475
anything,
474-
false
475-
);
476+
cascadeDeleteEnabled( action, componentType, i )
477+
);
476478
}
477479
}
478480
}
@@ -542,7 +544,7 @@ private static <T> void cascadeCollection(
542544
style,
543545
elemType,
544546
anything,
545-
persister.isCascadeDeleteEnabled()
547+
cascadeDeleteEnabled( action, persister )
546548
);
547549
}
548550
}
@@ -678,4 +680,19 @@ private static void deleteOrphans(EventSource eventSource, String entityName, Pe
678680
}
679681
}
680682
}
683+
684+
private static <T> boolean cascadeDeleteEnabled(CascadingAction<T> action, CollectionPersister persister) {
685+
return action.directionAffectedByCascadeDelete() == ForeignKeyDirection.FROM_PARENT
686+
&& persister.isCascadeDeleteEnabled();
687+
}
688+
689+
private static <T> boolean cascadeDeleteEnabled(CascadingAction<T> action, EntityPersister persister, int i) {
690+
return action.directionAffectedByCascadeDelete() == ForeignKeyDirection.TO_PARENT
691+
&& persister.getEntityMetamodel().getPropertyOnDeleteActions()[i] == OnDeleteAction.CASCADE;
692+
}
693+
694+
private static <T> boolean cascadeDeleteEnabled(CascadingAction<T> action, CompositeType componentType, int i) {
695+
return action.directionAffectedByCascadeDelete() == ForeignKeyDirection.TO_PARENT
696+
&& componentType.getOnDeleteAction( i ) == OnDeleteAction.CASCADE;
697+
}
681698
}

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@
77
package org.hibernate.engine.spi;
88

99
import java.util.Iterator;
10+
import java.util.List;
1011

12+
import org.checkerframework.checker.nullness.qual.Nullable;
1113
import org.hibernate.HibernateException;
14+
import org.hibernate.Incubating;
1215
import org.hibernate.event.spi.EventSource;
1316
import org.hibernate.persister.entity.EntityPersister;
1417
import org.hibernate.type.CollectionType;
18+
import org.hibernate.type.ForeignKeyDirection;
1519
import org.hibernate.type.Type;
1620

1721
/**
1822
* A session action that may be cascaded from parent entity to its children
1923
*
24+
* @param <T> The type of some context propagated with the cascading action
25+
*
2026
* @author Gavin King
2127
* @author Steve Ebersole
2228
*/
@@ -27,10 +33,9 @@ public interface CascadingAction<T> {
2733
*
2834
* @param session The session within which the cascade is occurring.
2935
* @param child The child to which cascading should be performed.
30-
* @param entityName The child's entity name
31-
* @param anything Anything ;) Typically some form of cascade-local cache
32-
* which is specific to each CascadingAction type
33-
* @param isCascadeDeleteEnabled Are cascading deletes enabled.
36+
* @param anything Some context specific to the kind of {@link CascadingAction}
37+
* @param isCascadeDeleteEnabled Whether the foreign key is declared with
38+
* {@link org.hibernate.annotations.OnDeleteAction#CASCADE on delete cascade}.
3439
*/
3540
void cascade(
3641
EventSource session,
@@ -92,4 +97,18 @@ default void noCascade(EventSource session, Object parent, EntityPersister persi
9297
* Should this action be performed (or noCascade consulted) in the case of lazy properties.
9398
*/
9499
boolean performOnLazyProperty();
100+
101+
/**
102+
* The cascade direction in which we care whether the foreign key is declared with
103+
* {@link org.hibernate.annotations.OnDeleteAction#CASCADE on delete cascade}.
104+
*
105+
* @apiNote This allows us to reuse the long-existing boolean parameter of
106+
* {@link #cascade(EventSource, Object, String, Object, boolean)}
107+
* for multiple purposes.
108+
*
109+
*/
110+
@Incubating @Nullable
111+
default ForeignKeyDirection directionAffectedByCascadeDelete(){
112+
return null;
113+
}
95114
}

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
@@ -6,6 +6,7 @@
66
*/
77
package org.hibernate.engine.spi;
88

9+
import org.checkerframework.checker.nullness.qual.Nullable;
910
import org.hibernate.HibernateException;
1011
import org.hibernate.Internal;
1112
import org.hibernate.LockMode;
@@ -20,6 +21,7 @@
2021
import org.hibernate.event.spi.RefreshContext;
2122
import org.hibernate.internal.CoreMessageLogger;
2223
import org.hibernate.type.CollectionType;
24+
import org.hibernate.type.ForeignKeyDirection;
2325
import org.jboss.logging.Logger;
2426

2527
import java.util.Iterator;
@@ -73,6 +75,11 @@ public boolean deleteOrphans() {
7375
return true;
7476
}
7577

78+
@Override
79+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
80+
return ForeignKeyDirection.FROM_PARENT;
81+
}
82+
7683
@Override
7784
public String toString() {
7885
return "ACTION_DELETE";
@@ -378,7 +385,7 @@ public void cascade(
378385
Void context,
379386
boolean isCascadeDeleteEnabled)
380387
throws HibernateException {
381-
if ( child != null && isChildTransient( session, child, entityName ) ) {
388+
if ( child != null && isChildTransient( session, child, entityName, isCascadeDeleteEnabled ) ) {
382389
throw new TransientObjectException( "persistent instance references an unsaved transient instance of '"
383390
+ entityName + "' (save the transient instance before flushing)" );
384391
//TODO: should be TransientPropertyValueException
@@ -419,13 +426,18 @@ public boolean performOnLazyProperty() {
419426
return false;
420427
}
421428

429+
@Override
430+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
431+
return ForeignKeyDirection.TO_PARENT;
432+
}
433+
422434
@Override
423435
public String toString() {
424436
return "ACTION_CHECK_ON_FLUSH";
425437
}
426438
};
427439

428-
private static boolean isChildTransient(EventSource session, Object child, String entityName) {
440+
private static boolean isChildTransient(EventSource session, Object child, String entityName, boolean isCascadeDeleteEnabled) {
429441
if ( isHibernateProxy( child ) ) {
430442
// a proxy is always non-transient
431443
// and ForeignKeys.isTransient()
@@ -440,7 +452,11 @@ private static boolean isChildTransient(EventSource session, Object child, Strin
440452
// we are good, even if it's not yet
441453
// inserted, since ordering problems
442454
// are detected and handled elsewhere
443-
return entry.getStatus().isDeletedOrGone();
455+
return entry.getStatus().isDeletedOrGone()
456+
// if the foreign key is 'on delete cascade'
457+
// we don't have to throw because the database
458+
// will delete the parent for us
459+
&& !isCascadeDeleteEnabled;
444460
}
445461
else {
446462
// TODO: check if it is a merged entity which has not yet been flushed
@@ -495,6 +511,11 @@ public abstract static class BaseCascadingAction<T> implements CascadingAction<T
495511
public boolean performOnLazyProperty() {
496512
return true;
497513
}
514+
515+
@Override @Nullable
516+
public ForeignKeyDirection directionAffectedByCascadeDelete() {
517+
return null;
518+
}
498519
}
499520

500521
/**

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

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.HibernateException;
1515
import org.hibernate.Internal;
1616
import org.hibernate.MappingException;
17+
import org.hibernate.annotations.OnDeleteAction;
1718
import org.hibernate.boot.model.relational.Database;
1819
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
1920
import org.hibernate.cfg.AvailableSettings;
@@ -86,7 +87,7 @@ public boolean isSynthetic() {
8687
public Type getType() throws MappingException {
8788
return value.getType();
8889
}
89-
90+
9091
public int getColumnSpan() {
9192
return value.getColumnSpan();
9293
}
@@ -106,11 +107,11 @@ public java.util.List<Selectable> getSelectables() {
106107
public java.util.List<Column> getColumns() {
107108
return value.getColumns();
108109
}
109-
110+
110111
public String getName() {
111112
return name;
112113
}
113-
114+
114115
public boolean isComposite() {
115116
return value instanceof Component;
116117
}
@@ -137,6 +138,10 @@ public void resetOptional(boolean optional) {
137138
}
138139
}
139140

141+
public OnDeleteAction getOnDeleteAction() {
142+
return value instanceof ToOne ? ( (ToOne) value ).getOnDeleteAction() : null;
143+
}
144+
140145
/**
141146
* @deprecated this method is no longer used
142147
*/
@@ -158,7 +163,7 @@ else if ( type instanceof CollectionType ) {
158163
return getCollectionCascadeStyle( collection.getElement().getType(), cascade );
159164
}
160165
else {
161-
return getCascadeStyle( cascade );
166+
return getCascadeStyle( cascade );
162167
}
163168
}
164169

@@ -192,7 +197,7 @@ else if ( elementType instanceof ComponentType ) {
192197
return getCascadeStyle( cascade );
193198
}
194199
}
195-
200+
196201
private static CascadeStyle getCascadeStyle(String cascade) {
197202
if ( cascade==null || cascade.equals("none") ) {
198203
return CascadeStyles.NONE;
@@ -205,9 +210,9 @@ private static CascadeStyle getCascadeStyle(String cascade) {
205210
styles[i++] = CascadeStyles.getCascadeStyle( tokens.nextToken() );
206211
}
207212
return new CascadeStyles.MultipleCascadeStyle(styles);
208-
}
213+
}
209214
}
210-
215+
211216
public String getCascade() {
212217
return cascade;
213218
}
@@ -231,7 +236,7 @@ public boolean isUpdateable() {
231236
}
232237

233238
public boolean isInsertable() {
234-
// if the property mapping consists of all formulas,
239+
// if the property mapping consists of all formulas,
235240
// make it non-insertable
236241
return insertable && value.hasAnyInsertableColumns();
237242
}
@@ -318,7 +323,7 @@ public boolean isValid(Mapping mapping) throws MappingException {
318323
public String toString() {
319324
return getClass().getSimpleName() + '(' + name + ')';
320325
}
321-
326+
322327
public void setLazy(boolean lazy) {
323328
this.lazy=lazy;
324329
}
@@ -364,11 +369,11 @@ public boolean isOptimisticLocked() {
364369
public void setOptimisticLocked(boolean optimisticLocked) {
365370
this.optimisticLocked = optimisticLocked;
366371
}
367-
372+
368373
public boolean isOptional() {
369374
return optional;
370375
}
371-
376+
372377
public void setOptional(boolean optional) {
373378
this.optional = optional;
374379
}
@@ -384,7 +389,7 @@ public void setPersistentClass(PersistentClass persistentClass) {
384389
public boolean isSelectable() {
385390
return selectable;
386391
}
387-
392+
388393
public void setSelectable(boolean selectable) {
389394
this.selectable = selectable;
390395
}

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

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

99
import org.hibernate.FetchMode;
10+
import org.hibernate.annotations.OnDeleteAction;
1011
import org.hibernate.engine.spi.CascadeStyle;
1112
import org.hibernate.engine.spi.SessionFactoryImplementor;
1213
import org.hibernate.persister.walking.spi.AttributeSource;
@@ -94,6 +95,11 @@ public CascadeStyle getCascadeStyle() {
9495
return attributeInformation.getCascadeStyle();
9596
}
9697

98+
@Override
99+
public OnDeleteAction getOnDeleteAction() {
100+
return attributeInformation.getOnDeleteAction();
101+
}
102+
97103
@Override
98104
public FetchMode getFetchMode() {
99105
return attributeInformation.getFetchMode();

0 commit comments

Comments
 (0)