Skip to content

Commit f10bec8

Browse files
committed
HHH-18936 suppress TransientObjectException when the FK is marked @onDelete(CASCADE)
note that this fix does not cover the case of an association inside an @embeddable
1 parent 1022e9c commit f10bec8

File tree

10 files changed

+128
-7
lines changed

10 files changed

+128
-7
lines changed

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

Lines changed: 5 additions & 2 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.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
1415
import org.hibernate.collection.spi.PersistentCollection;
1516
import org.hibernate.engine.spi.CascadeStyle;
@@ -117,6 +118,8 @@ public static <T> void cascade(
117118
hasUninitializedLazyProperties
118119
&& !persister.getBytecodeEnhancementMetadata()
119120
.isAttributeLoaded( parent, propertyName );
121+
final boolean isCascadeDeleteEnabled =
122+
persister.getEntityMetamodel().getPropertyOnDeleteActions()[i] == OnDeleteAction.CASCADE;
120123

121124
if ( style.doCascade( action ) ) {
122125
final Object child;
@@ -178,7 +181,7 @@ else if ( action.performOnLazyProperty() && type instanceof EntityType ) {
178181
style,
179182
propertyName,
180183
anything,
181-
false
184+
isCascadeDeleteEnabled
182185
);
183186
}
184187
else {
@@ -193,7 +196,7 @@ else if ( action.performOnLazyProperty() && type instanceof EntityType ) {
193196
type,
194197
style,
195198
propertyName,
196-
false
199+
isCascadeDeleteEnabled
197200
);
198201
}
199202
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ public void cascade(
340340
Void context,
341341
boolean isCascadeDeleteEnabled)
342342
throws HibernateException {
343-
if ( child != null && isChildTransient( session, child, entityName ) ) {
343+
if ( child != null && !isCascadeDeleteEnabled && isChildTransient( session, child, entityName ) ) {
344344
throw new TransientObjectException( "Persistent instance references an unsaved transient instance of '"
345345
+ entityName + "' (save the transient instance before flushing)" );
346346
//TODO: should be TransientPropertyValueException

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.hibernate.HibernateException;
1313
import org.hibernate.Internal;
1414
import org.hibernate.MappingException;
15+
import org.hibernate.annotations.OnDeleteAction;
1516
import org.hibernate.boot.model.relational.Database;
1617
import org.hibernate.boot.model.relational.SqlStringGenerationContext;
1718
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
@@ -134,6 +135,10 @@ public void resetOptional(boolean optional) {
134135
}
135136
}
136137

138+
public OnDeleteAction getOnDeleteAction() {
139+
return value instanceof ToOne toOne ? toOne.getOnDeleteAction() : null;
140+
}
141+
137142
public CascadeStyle getCascadeStyle() throws MappingException {
138143
final Type type = value.getType();
139144
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
);

hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.hibernate.HibernateException;
1919
import org.hibernate.MappingException;
2020
import org.hibernate.annotations.NotFoundAction;
21+
import org.hibernate.annotations.OnDeleteAction;
2122
import org.hibernate.boot.spi.MetadataImplementor;
2223
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper;
2324
import org.hibernate.bytecode.internal.BytecodeEnhancementMetadataNonPojoImpl;
@@ -63,6 +64,7 @@
6364
import static org.hibernate.internal.util.ReflectHelper.isFinalClass;
6465
import static org.hibernate.internal.util.collections.ArrayHelper.toIntArray;
6566
import static org.hibernate.internal.util.collections.CollectionHelper.toSmallSet;
67+
import static org.hibernate.tuple.PropertyFactory.buildIdentifierAttribute;
6668

6769
/**
6870
* Centralizes metamodel information about an entity.
@@ -102,6 +104,7 @@ public class EntityMetamodel implements Serializable {
102104
private final boolean[] propertyInsertability;
103105
private final boolean[] propertyNullability;
104106
private final boolean[] propertyVersionability;
107+
private final OnDeleteAction[] propertyOnDeleteActions;
105108
private final CascadeStyle[] cascadeStyles;
106109
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
107110

@@ -162,7 +165,7 @@ public EntityMetamodel(
162165
EntityPersister persister,
163166
RuntimeModelCreationContext creationContext,
164167
Function<String, Generator> generatorSupplier) {
165-
this.sessionFactory = creationContext.getSessionFactory();
168+
sessionFactory = creationContext.getSessionFactory();
166169

167170
// Improves performance of EntityKey#equals by avoiding content check in String#equals
168171
name = persistentClass.getEntityName().intern();
@@ -174,18 +177,18 @@ public EntityMetamodel(
174177
subclassId = persistentClass.getSubclassId();
175178

176179
final Generator idgenerator = generatorSupplier.apply( rootName );
177-
identifierAttribute = PropertyFactory.buildIdentifierAttribute( persistentClass, idgenerator );
180+
identifierAttribute = buildIdentifierAttribute( persistentClass, idgenerator );
178181

179182
versioned = persistentClass.isVersioned();
180183

181184
final boolean collectionsInDefaultFetchGroupEnabled =
182185
creationContext.getSessionFactoryOptions().isCollectionsInDefaultFetchGroupEnabled();
186+
final boolean supportsCascadeDelete = creationContext.getDialect().supportsCascadeDelete();
183187

184188
if ( persistentClass.hasPojoRepresentation() ) {
185189
final Component identifierMapperComponent = persistentClass.getIdentifierMapper();
186190
final CompositeType nonAggregatedCidMapper;
187191
final Set<String> idAttributeNames;
188-
189192
if ( identifierMapperComponent != null ) {
190193
nonAggregatedCidMapper = identifierMapperComponent.getType();
191194
idAttributeNames = new HashSet<>( );
@@ -226,6 +229,7 @@ public EntityMetamodel(
226229
propertyNullability = new boolean[propertySpan];
227230
propertyVersionability = new boolean[propertySpan];
228231
propertyLaziness = new boolean[propertySpan];
232+
propertyOnDeleteActions = new OnDeleteAction[propertySpan];
229233
cascadeStyles = new CascadeStyle[propertySpan];
230234
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
231235

@@ -318,7 +322,7 @@ public EntityMetamodel(
318322
nonlazyPropertyUpdateability[i] = attribute.isUpdateable() && !lazy;
319323
propertyCheckability[i] = propertyUpdateability[i]
320324
|| propertyType.isAssociationType() && ( (AssociationType) propertyType ).isAlwaysDirtyChecked();
321-
325+
propertyOnDeleteActions[i] = supportsCascadeDelete ? attribute.getOnDeleteAction() : null;
322326
cascadeStyles[i] = attribute.getCascadeStyle();
323327
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
324328

@@ -887,4 +891,8 @@ public boolean isInstrumented() {
887891
public BytecodeEnhancementMetadata getBytecodeEnhancementMetadata() {
888892
return bytecodeEnhancementMetadata;
889893
}
894+
895+
public OnDeleteAction[] getPropertyOnDeleteActions() {
896+
return propertyOnDeleteActions;
897+
}
890898
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Id;
9+
import jakarta.persistence.ManyToOne;
10+
import jakarta.persistence.OneToMany;
11+
import org.hibernate.annotations.OnDelete;
12+
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
13+
import org.hibernate.testing.orm.junit.Jpa;
14+
import org.junit.jupiter.api.Test;
15+
16+
import java.util.HashSet;
17+
import java.util.Set;
18+
19+
import static jakarta.persistence.FetchType.EAGER;
20+
import static org.hibernate.annotations.OnDeleteAction.CASCADE;
21+
import static org.junit.jupiter.api.Assertions.assertNull;
22+
23+
@Jpa(annotatedClasses = {OnDeleteTest.Parent.class, OnDeleteTest.Child.class})
24+
public class OnDeleteTest {
25+
@Test
26+
public void testOnDelete(EntityManagerFactoryScope scope) {
27+
Parent parent = new Parent();
28+
Child child = new Child();
29+
child.parent = parent;
30+
scope.inTransaction( em -> {
31+
em.persist( parent );
32+
em.persist( child );
33+
} );
34+
scope.inTransaction( em -> {
35+
Parent p = em.find( Parent.class, parent.id );
36+
em.remove( p );
37+
} );
38+
scope.inTransaction( em -> {
39+
assertNull( em.find( Child.class, child.id ) );
40+
} );
41+
}
42+
43+
@Test
44+
public void testOnDeleteReference(EntityManagerFactoryScope scope) {
45+
Parent parent = new Parent();
46+
Child child = new Child();
47+
child.parent = parent;
48+
parent.children.add( child );
49+
scope.inTransaction( em -> {
50+
em.persist( parent );
51+
em.persist( child );
52+
} );
53+
scope.inTransaction( em -> em.remove( em.getReference( parent ) ) );
54+
scope.inTransaction( em -> assertNull( em.find( Child.class, child.id ) ) );
55+
}
56+
57+
@Entity
58+
static class Parent {
59+
@Id
60+
long id;
61+
@OneToMany(mappedBy = "parent", fetch = EAGER)
62+
@OnDelete(action = CASCADE)
63+
Set<Child> children = new HashSet<>();
64+
}
65+
66+
@Entity
67+
static class Child {
68+
@Id
69+
long id;
70+
@ManyToOne
71+
@OnDelete(action = CASCADE)
72+
Parent parent;
73+
}
74+
}

0 commit comments

Comments
 (0)