From 48909896b69c983ff7234b1e1435a881dfb5746c Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Thu, 20 Sep 2018 11:31:40 +0300 Subject: [PATCH] HHH-12974 - Document @OnDelete behavior in regards to disabling the Persistence Context entity removal cascading event --- .../chapters/pc/PersistenceContext.adoc | 47 +++++- .../pc/CascadeOnDeleteCollectionTest.java | 149 ++++++++++++++++++ .../id/IdClassManyToOneCascadeTest.java | 2 +- ...EntityGraphUsingFetchGraphForLazyTest.java | 2 +- ...DropUtf8WithoutHbm2DdlCharsetNameTest.java | 2 +- .../bag/PersistentBagContainsTest.java | 2 +- .../test/tm/AfterCompletionTest.java | 2 +- 7 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteCollectionTest.java diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc index 2a812aa36e..28d7abe86f 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc @@ -825,7 +825,7 @@ the automatic schema generator will apply the ON DELETE CASCADE SQL directive to as illustrated by the following example. [[pc-cascade-on-delete-mapping-example]] -.`@OnDelete` mapping +.`@OnDelete` `@ManyToOne` mapping ==== [source, JAVA, indent=0] ---- @@ -843,10 +843,10 @@ include::{extrasdir}/pc-cascade-on-delete-mapping-example.sql[] ---- ==== -Now, you can just remove the `Person` entity, and the associated `Phone` is going to be removed automatically. +Now, you can just remove the `Person` entity, and the associated `Phone` entities are going to be deleted automatically via the Foreign Key cascade. [[pc-cascade-on-delete-example]] -.`@OnDelete` example +.`@OnDelete` `@ManyToOne` delete example ==== [source, JAVA, indent=0] ---- @@ -857,4 +857,45 @@ include::{sourcedir}/CascadeOnDeleteTest.java[tags=pc-cascade-on-delete-example] ---- include::{extrasdir}/pc-cascade-on-delete-example.sql[] ---- +==== + +The `@OnDelete` annotation can also be placed on a collection, as +illustrated in the following example. + +[[pc-cascade-on-delete-collection-mapping-example]] +.`@OnDelete` `One@ToMany` mapping +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeOnDeleteCollectionTest.java[tags=pc-cascade-on-delete-collection-mapping-Person-example] +---- + +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeOnDeleteCollectionTest.java[tags=pc-cascade-on-delete-collection-mapping-Phone-example] +---- +==== + +Now, when removing the `Person` entity, all the associated `Phone` child entities are deleted via the Foreign Key cascade even if the `@OneToMany` collection was using the `CascadeType.ALL` attribute. + +[[pc-cascade-on-delete-collection-example]] +.`@OnDelete` `@ManyToOne` delete example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeOnDeleteCollectionTest.java[tags=pc-cascade-on-delete-collection-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/pc-cascade-on-delete-example.sql[] +---- +==== + +[NOTE] +==== +Without the `@OnDelete` annotation, the `@OneToMany` association relies on the `cascade` attribute to propagate the `remove` entity state transition from the parent entity to its children. +However, when the `@OnDelete` annotation is in place, Hibernate prevents the child entity `DELETE` statement from being executed while flushing the Persistence Context. + +This way, only the parent entity gets deleted, and all the associated child records are removed by the database engine, instead of being deleted explicitly via `DELETE` statements. ==== \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteCollectionTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteCollectionTest.java new file mode 100644 index 0000000000..96780aaaae --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeOnDeleteCollectionTest.java @@ -0,0 +1,149 @@ +package org.hibernate.userguide.pc; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; + +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Vlad Mihalcea + */ +public class CascadeOnDeleteCollectionTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class + }; + } + + @Test + public void test() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setId( 1L ); + person.setName( "John Doe" ); + entityManager.persist( person ); + + Phone phone1 = new Phone(); + phone1.setId( 1L ); + phone1.setNumber( "123-456-7890" ); + phone1.setOwner( person ); + person.addPhone( phone1 ); + + Phone phone2 = new Phone(); + phone2.setId( 2L ); + phone2.setNumber( "101-010-1234" ); + phone2.setOwner( person ); + person.addPhone( phone2 ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::pc-cascade-on-delete-collection-example[] + Person person = entityManager.find( Person.class, 1L ); + entityManager.remove( person ); + //end::pc-cascade-on-delete-collection-example[] + } ); + + } + + //tag::pc-cascade-on-delete-collection-mapping-Person-example[] + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String name; + + @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) + @OnDelete(action = OnDeleteAction.CASCADE) + private List phones = new ArrayList<>(); + + //Getters and setters are omitted for brevity + + //end::pc-cascade-on-delete-collection-mapping-Person-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public void addPhone(Phone phone) { + phone.setOwner( this ); + phones.add( phone ); + } + + //tag::pc-cascade-on-delete-collection-mapping-Person-example[] + } + //end::pc-cascade-on-delete-collection-mapping-Person-example[] + + //tag::pc-cascade-on-delete-collection-mapping-Phone-example[] + @Entity(name = "Phone") + public static class Phone { + + @Id + private Long id; + + @Column(name = "`number`") + private String number; + + @ManyToOne(fetch = FetchType.LAZY) + private Person owner; + + //Getters and setters are omitted for brevity + + //end::pc-cascade-on-delete-collection-mapping-Phone-example[] + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public Person getOwner() { + return owner; + } + + public void setOwner(Person owner) { + this.owner = owner; + } + //tag::pc-cascade-on-delete-collection-mapping-Phone-example[] + } + //end::pc-cascade-on-delete-collection-mapping-Phone-example[] +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/id/IdClassManyToOneCascadeTest.java b/hibernate-core/src/test/java/org/hibernate/id/IdClassManyToOneCascadeTest.java index 3cdf7bc395..35da3f7153 100644 --- a/hibernate-core/src/test/java/org/hibernate/id/IdClassManyToOneCascadeTest.java +++ b/hibernate-core/src/test/java/org/hibernate/id/IdClassManyToOneCascadeTest.java @@ -19,7 +19,7 @@ import org.hibernate.testing.TestForIssue; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.wildfly.common.Assert.assertTrue; +import static org.junit.Assert.assertTrue; /** * @author Vlad Mihalcea diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphUsingFetchGraphForLazyTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphUsingFetchGraphForLazyTest.java index d667ac9d8e..b40153cc90 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphUsingFetchGraphForLazyTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphUsingFetchGraphForLazyTest.java @@ -36,7 +36,7 @@ import org.jboss.logging.Logger; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.junit.Assert.assertTrue; -import static org.wildfly.common.Assert.assertFalse; +import static org.junit.Assert.assertFalse; /** * @author Vlad Mihalcea diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaCreateDropUtf8WithoutHbm2DdlCharsetNameTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaCreateDropUtf8WithoutHbm2DdlCharsetNameTest.java index f819e4ceac..e3d91076b8 100644 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaCreateDropUtf8WithoutHbm2DdlCharsetNameTest.java +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/schemagen/SchemaCreateDropUtf8WithoutHbm2DdlCharsetNameTest.java @@ -26,7 +26,7 @@ import org.hibernate.testing.TestForIssue; import org.junit.Before; import org.junit.Test; -import static org.wildfly.common.Assert.assertTrue; +import static org.junit.Assert.assertTrue; /** * @author Vlad Mihalcea diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/PersistentBagContainsTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/PersistentBagContainsTest.java index c6cc420bbf..e738f8584c 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/collection/bag/PersistentBagContainsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/bag/PersistentBagContainsTest.java @@ -21,7 +21,7 @@ import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.Test; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; -import static org.wildfly.common.Assert.assertTrue; +import static org.junit.Assert.assertTrue; /** * Tests related to contains operations on a PersistentBag. diff --git a/hibernate-core/src/test/java/org/hibernate/test/tm/AfterCompletionTest.java b/hibernate-core/src/test/java/org/hibernate/test/tm/AfterCompletionTest.java index e2c50e031c..ef0007f52e 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/tm/AfterCompletionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/tm/AfterCompletionTest.java @@ -31,7 +31,7 @@ import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; import static org.junit.Assert.assertEquals; -import static org.wildfly.common.Assert.assertTrue; +import static org.junit.Assert.assertTrue; /** * @author Chris Cranford