Skip to content

Commit 4ad08ed

Browse files
committed
allow @onDelete(CASCADE) with @manytoone @jointable
previously this was (unnecessarily) disallowed
1 parent 0d0d849 commit 4ad08ed

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

hibernate-core/src/main/java/org/hibernate/boot/model/internal/CollectionBinder.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,14 +1191,21 @@ private void detectMappedByProblem(boolean isMappedBy) {
11911191
}
11921192
else if ( oneToMany
11931193
&& property.hasDirectAnnotationUsage( OnDelete.class )
1194-
&& !property.hasDirectAnnotationUsage( JoinColumn.class )
1195-
&& !property.hasDirectAnnotationUsage( JoinColumns.class )) {
1194+
&& !hasExplicitJoinColumn() ) {
11961195
throw new AnnotationException( "Unidirectional '@OneToMany' association '"
11971196
+ qualify( propertyHolder.getPath(), propertyName )
11981197
+ "' is annotated '@OnDelete' and must explicitly specify a '@JoinColumn'" );
11991198
}
12001199
}
12011200

1201+
private boolean hasExplicitJoinColumn() {
1202+
return property.hasDirectAnnotationUsage( JoinColumn.class )
1203+
|| property.hasDirectAnnotationUsage( JoinColumns.class )
1204+
|| property.hasDirectAnnotationUsage( JoinTable.class )
1205+
&& property.getDirectAnnotationUsage( JoinTable.class )
1206+
.joinColumns().length > 0;
1207+
}
1208+
12021209
private void bindProperty() {
12031210
//property building
12041211
PropertyBinder binder = new PropertyBinder();
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.ondeletecascade;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Id;
9+
import jakarta.persistence.JoinColumn;
10+
import jakarta.persistence.JoinTable;
11+
import jakarta.persistence.OneToMany;
12+
import jakarta.persistence.RollbackException;
13+
import org.hibernate.Hibernate;
14+
import org.hibernate.TransientObjectException;
15+
import org.hibernate.annotations.OnDelete;
16+
import org.hibernate.annotations.OnDeleteAction;
17+
import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
18+
import org.hibernate.testing.orm.junit.Jpa;
19+
import org.junit.jupiter.api.AfterEach;
20+
import org.junit.jupiter.api.Test;
21+
22+
import java.util.HashSet;
23+
import java.util.Set;
24+
25+
import static org.junit.jupiter.api.Assertions.assertEquals;
26+
import static org.junit.jupiter.api.Assertions.assertFalse;
27+
import static org.junit.jupiter.api.Assertions.assertNotNull;
28+
import static org.junit.jupiter.api.Assertions.assertTrue;
29+
import static org.junit.jupiter.api.Assertions.fail;
30+
31+
@Jpa(annotatedClasses =
32+
{OnDeleteOneToManyJoinTableTest.Parent.class, OnDeleteOneToManyJoinTableTest.Child.class},
33+
useCollectingStatementInspector = true)
34+
//@RequiresDialectFeature(feature = DialectFeatureChecks.SupportsCascadeDeleteCheck.class)
35+
public class OnDeleteOneToManyJoinTableTest {
36+
@Test
37+
public void testOnDeleteParent(EntityManagerFactoryScope scope) {
38+
var inspector = scope.getCollectingStatementInspector();
39+
inspector.clear();
40+
Parent parent = new Parent();
41+
Child child = new Child();
42+
parent.children.add( child );
43+
scope.inTransaction( em -> {
44+
em.persist( parent );
45+
em.persist( child );
46+
} );
47+
inspector.assertExecutedCount( 3 );
48+
inspector.clear();
49+
scope.inTransaction( em -> {
50+
Parent p = em.find( Parent.class, parent.id );
51+
inspector.assertExecutedCount( 1 );
52+
em.remove( p );
53+
assertFalse( Hibernate.isInitialized( p.children ) );
54+
} );
55+
inspector.assertExecutedCount( scope.getDialect().supportsCascadeDelete() ? 2 : 3 );
56+
scope.inTransaction( em -> {
57+
assertNotNull( em.find( Child.class, child.id ) );
58+
} );
59+
60+
scope.inTransaction( em -> {
61+
assertEquals( 1,
62+
em.createNativeQuery( "select count(*) from Child", Integer.class )
63+
.getSingleResultOrNull() );
64+
assertEquals( 0,
65+
em.createNativeQuery( "select count(*) from Parent_Child", Integer.class )
66+
.getSingleResultOrNull() );
67+
assertEquals( 0,
68+
em.createNativeQuery( "select count(*) from Parent", Integer.class )
69+
.getSingleResultOrNull() );
70+
}); }
71+
72+
@Test
73+
public void testOnDeleteChildrenFails(EntityManagerFactoryScope scope) {
74+
Parent parent = new Parent();
75+
Child child = new Child();
76+
parent.children.add( child );
77+
scope.inTransaction( em -> {
78+
em.persist( parent );
79+
em.persist( child );
80+
} );
81+
try {
82+
scope.inTransaction( em -> {
83+
Parent p = em.find( Parent.class, parent.id );
84+
for ( Child c : p.children ) {
85+
em.remove( c );
86+
}
87+
} );
88+
fail();
89+
}
90+
catch (RollbackException re) {
91+
assertTrue(re.getCause().getCause() instanceof TransientObjectException);
92+
}
93+
}
94+
95+
@AfterEach
96+
public void tearDown(EntityManagerFactoryScope scope) {
97+
scope.getEntityManagerFactory().getSchemaManager().truncate();
98+
}
99+
100+
@Entity(name = "Parent")
101+
static class Parent {
102+
@Id
103+
long id;
104+
@OneToMany
105+
@JoinTable(joinColumns = @JoinColumn(name = "parent_id"))
106+
@OnDelete(action = OnDeleteAction.CASCADE)
107+
Set<Child> children = new HashSet<>();
108+
}
109+
110+
@Entity(name = "Child")
111+
static class Child {
112+
@Id
113+
long id;
114+
}
115+
}

0 commit comments

Comments
 (0)