diff --git a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc index 91294f31a8..3f0336dc77 100644 --- a/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc +++ b/documentation/src/main/asciidoc/userguide/appendices/Annotations.adoc @@ -642,6 +642,8 @@ For JPA cascading, prefer using the http://docs.oracle.com/javaee/7/api/javax/pe When combining both JPA and Hibernate `CascadeType` strategies, Hibernate will merge both sets of cascades. +See the <> chapter for more info. + [[annotations-hibernate-check]] ==== `@Check` diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc index 7b99d8ffee..71524c6611 100644 --- a/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/PersistenceContext.adoc @@ -2,6 +2,7 @@ == Persistence Contexts :sourcedir: ../../../../../test/java/org/hibernate/userguide/pc :sourcedir-caching: ../../../../../test/java/org/hibernate/userguide/caching +:extrasdir: extras Both the `org.hibernate.Session` API and `javax.persistence.EntityManager` API represent a context for dealing with persistent data. This concept is called a `persistence context`. @@ -562,4 +563,125 @@ To verify if an entity instance is currently attached to the running persistence ---- include::{sourcedir-caching}/FirstLevelCacheTest.java[tags=caching-management-contains-example] ---- -==== \ No newline at end of file +==== + +[[pc-cascade]] +=== Cascading entity state transitions + +JPA allows you to propagate the state transition from a parent entity to a child. +For this purpose, the JPA `javax.persistence.CascadeType` defines various cascade types: + +`ALL`:: cascades all entity state transitions +`PERSIST`:: cascades the entity persist operation. +`MERGE`:: cascades the entity merge operation. +`REMOVE`:: cascades the entity remove operation. +`REFRESH`:: cascades the entity refresh operation. +`DETACH`:: cascades the entity detach operation. + +Additionally, the `CascadeType.ALL` will propagate any Hibernate-specific operation, which is defined by the `org.hibernate.annotations.CascadeType` enum: + +`SAVE_UPDATE`:: cascades the entity saveOrUpdate operation. +`REPLICATE`:: cascades the entity replicate operation. +`LOCK`:: cascades the entity lock operation. + +The following examples will explain some of the aforementioned cascade operations using the following entities: + +[source, JAVA, indent=0] +---- +include::{sourcedir}/Person.java[tags=pc-cascade-domain-model-example] + +include::{sourcedir}/Phone.java[tags=pc-cascade-domain-model-example] +---- + +[[pc-cascade-persist]] +==== `CascadeType.PERSIST` + +The `CascadeType.PERSIST` allows us to persist a child entity along with the parent one. + +[[pc-cascade-persist-example]] +.`CascadeType.PERSIST` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadePersistTest.java[tags=pc-cascade-persist-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/pc-cascade-persist-example.sql[] +---- +==== + +Even if just the `Person` parent entity was persisted, Hibernate has managed to cascade the persist operation to the associated `Phone` child entity as well. + +[[pc-cascade-merge]] +==== `CascadeType.MERGE` + +The `CascadeType.MERGE` allows us to merge a child entity along with the parent one. + +.`CascadeType.MERGE` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeMergeTest.java[tags=pc-cascade-merge-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/pc-cascade-merge-example.sql[] +---- +==== + +During merge, the current state of the entity is copied onto the entity version that was just fetched from the database. +That's the reason why Hibernate executed the SELECT statement which fetched both the `Person` entity along with its children. + +[[pc-cascade-remove]] +==== `CascadeType.REMOVE` + +The `CascadeType.REMOVE` allows us to remove a child entity along with the parent one. +Traditionally, Hibernate called this operation delete, that's why the `org.hibernate.annotations.CascadeType` provides a `DELETE` cascade option. +However, `CascadeType.REMOVE` and `org.hibernate.annotations.CascadeType.DELETE` are identical. + +[[pc-cascade-remove-example]] +.`CascadeType.REMOVE` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeRemoveTest.java[tags=pc-cascade-remove-example] +---- + +[source, SQL, indent=0] +---- +include::{extrasdir}/pc-cascade-remove-example.sql[] +---- +==== + +[[pc-cascade-detach]] +==== `CascadeType.DETACH` + +`CascadeType.DETACH` is used to propagate the detach operation from a parent entity to a child. + +[[pc-cascade-detach-example]] +.`CascadeType.DETACH` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeDetachTest.java[tags=pc-cascade-detach-example] +---- +==== + +[[pc-cascade-lock]] +==== `CascadeType.LOCK` + +Although unintuitively, `CascadeType.LOCK` does not propagate a lock request from a parent entity to its children. +Such a use case requires the use of the `PessimisticLockScope.EXTENDED` value pf the `javax.persistence.lock.scope` property. + +However, `CascadeType.LOCK` allows us to reattach a parent entity along with it s children to the currently running Persistence Context. + +.`CascadeType.LOCK` example +==== +[source, JAVA, indent=0] +---- +include::{sourcedir}/CascadeLockTest.java[tags=pc-cascade-lock-example] +---- +==== diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-merge-example.sql b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-merge-example.sql new file mode 100644 index 0000000000..8745b18504 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-merge-example.sql @@ -0,0 +1,15 @@ +SELECT + p.id as id1_0_1_, + p.name as name2_0_1_, + ph.owner_id as owner_id3_1_3_, + ph.id as id1_1_3_, + ph.id as id1_1_0_, + ph."number" as number2_1_0_, + ph.owner_id as owner_id3_1_0_ +FROM + Person p +LEFT OUTER JOIN + Phone ph + on p.id=ph.owner_id +WHERE + p.id = 1 \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-persist-example.sql b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-persist-example.sql new file mode 100644 index 0000000000..864ca8aab9 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-persist-example.sql @@ -0,0 +1,5 @@ +INSERT INTO Person ( name, id ) +VALUES ( 'John Doe', 1 ) + +INSERT INTO Phone ( `number`, person_id, id ) +VALUE ( '123-456-7890', 1, 1 ) \ No newline at end of file diff --git a/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-remove-example.sql b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-remove-example.sql new file mode 100644 index 0000000000..deaf354db1 --- /dev/null +++ b/documentation/src/main/asciidoc/userguide/chapters/pc/extras/pc-cascade-remove-example.sql @@ -0,0 +1,3 @@ +DELETE FROM Phone WHERE id = 1 + +DELETE FROM Person WHERE id = 1 \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeDetachTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeDetachTest.java new file mode 100644 index 0000000000..9490f954ca --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeDetachTest.java @@ -0,0 +1,59 @@ +package org.hibernate.userguide.pc; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Fábio Takeo Ueno + */ +public class CascadeDetachTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class + }; + } + + @Test + public void detachTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setId( 1L ); + person.setName( "John Doe" ); + + Phone phone = new Phone(); + phone.setId( 1L ); + phone.setNumber( "123-456-7890" ); + + person.addPhone( phone ); + entityManager.persist( person ); + + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + //tag::pc-cascade-detach-example[] + Person person = entityManager.find( Person.class, 1L ); + assertEquals( 1, person.getPhones().size() ); + Phone phone = person.getPhones().get( 0 ); + + assertTrue( entityManager.contains( person )); + assertTrue( entityManager.contains( phone )); + + entityManager.detach( person ); + + assertFalse( entityManager.contains( person )); + assertFalse( entityManager.contains( phone )); + + //end::pc-cascade-detach-example[] + } ); + } +} \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeLockTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeLockTest.java new file mode 100644 index 0000000000..4196f531b3 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeLockTest.java @@ -0,0 +1,67 @@ +package org.hibernate.userguide.pc; + +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.Session; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Fábio Takeo Ueno + */ +public class CascadeLockTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class + }; + } + + @Test + public void lockTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setId( 1L ); + person.setName( "John Doe" ); + + Phone phone = new Phone(); + phone.setId( 1L ); + phone.setNumber( "123-456-7890" ); + + person.addPhone( phone ); + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + //tag::pc-cascade-lock-example[] + Person person = entityManager.find( Person.class, 1L ); + assertEquals( 1, person.getPhones().size() ); + Phone phone = person.getPhones().get( 0 ); + + assertTrue( entityManager.contains( person ) ); + assertTrue( entityManager.contains( phone ) ); + + entityManager.detach( person ); + + assertFalse( entityManager.contains( person ) ); + assertFalse( entityManager.contains( phone ) ); + + entityManager.unwrap( Session.class ) + .buildLockRequest( new LockOptions( LockMode.NONE ) ) + .lock( person ); + + assertTrue( entityManager.contains( person ) ); + assertTrue( entityManager.contains( phone ) ); + //end::pc-cascade-lock-example[] + } ); + } +} \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeMergeTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeMergeTest.java new file mode 100644 index 0000000000..caf0ed1186 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeMergeTest.java @@ -0,0 +1,54 @@ +package org.hibernate.userguide.pc; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Fábio Takeo Ueno + */ +public class CascadeMergeTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class + }; + } + + @Test + public void mergeTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setId( 1L ); + person.setName( "John Doe" ); + + Phone phone = new Phone(); + phone.setId( 1L ); + phone.setNumber( "123-456-7890" ); + + person.addPhone( phone ); + + entityManager.persist( person ); + + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + //tag::pc-cascade-merge-example[] + Phone phone = entityManager.find( Phone.class, 1L ); + Person person = phone.getOwner(); + + person.setName( "John Doe Jr." ); + phone.setNumber( "987-654-3210" ); + + entityManager.clear(); + + entityManager.merge( person ); + //end::pc-cascade-merge-example[] + } ); + } +} \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadePersistTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadePersistTest.java new file mode 100644 index 0000000000..d1c66d1713 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadePersistTest.java @@ -0,0 +1,40 @@ +package org.hibernate.userguide.pc; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Fábio Takeo Ueno + */ +public class CascadePersistTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class + }; + } + + @Test + public void persistTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::pc-cascade-persist-example[] + Person person = new Person(); + person.setId( 1L ); + person.setName( "John Doe" ); + + Phone phone = new Phone(); + phone.setId( 1L ); + phone.setNumber( "123-456-7890" ); + + person.addPhone( phone ); + + entityManager.persist( person ); + //end::pc-cascade-persist-example[] + } ); + } +} \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/CascadeRemoveTest.java b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeRemoveTest.java new file mode 100644 index 0000000000..1429401b2b --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/CascadeRemoveTest.java @@ -0,0 +1,45 @@ +package org.hibernate.userguide.pc; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Fábio Takeo Ueno + */ +public class CascadeRemoveTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + Phone.class + }; + } + + @Test + public void deleteTest() { + doInJPA( this::entityManagerFactory, entityManager -> { + Person person = new Person(); + person.setId( 1L ); + person.setName( "John Doe" ); + + Phone phone = new Phone(); + phone.setId( 1L ); + phone.setNumber( "123-456-7890" ); + + person.addPhone( phone ); + entityManager.persist( person ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + //tag::pc-cascade-remove-example[] + Person person = entityManager.find( Person.class, 1L ); + + entityManager.remove( person ); + //end::pc-cascade-remove-example[] + } ); + } +} \ No newline at end of file diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/Person.java b/documentation/src/test/java/org/hibernate/userguide/pc/Person.java new file mode 100644 index 0000000000..452e8a9186 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/Person.java @@ -0,0 +1,51 @@ +package org.hibernate.userguide.pc; + +import java.util.ArrayList; +import java.util.List; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +/** + * @author Vlad Mihalcea + */ +//tag::pc-cascade-domain-model-example[] +@Entity +public class Person { + + @Id + private Long id; + + private String name; + + @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL) + private List phones = new ArrayList<>(); + + 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 List getPhones() { + return phones; + } + + public void addPhone(Phone phone) { + this.phones.add( phone ); + phone.setOwner( this ); + } +} + +//end::pc-cascade-domain-model-example[] diff --git a/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java b/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java new file mode 100644 index 0000000000..d9e58df212 --- /dev/null +++ b/documentation/src/test/java/org/hibernate/userguide/pc/Phone.java @@ -0,0 +1,49 @@ +package org.hibernate.userguide.pc; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +/** + * @author Vlad Mihalcea + */ +//tag::pc-cascade-domain-model-example[] +@Entity +public class Phone { + + @Id + private Long id; + + @Column(name = "`number`") + private String number; + + @ManyToOne(fetch = FetchType.LAZY) + private Person owner; + + 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; + } +} +//end::pc-cascade-domain-model-example[] \ No newline at end of file