HHH-11186 - Add examples for all Hibernate annotations

Document more annotations:

- @Cascade
This commit is contained in:
Fábio Ueno 2017-02-09 17:38:55 -02:00 committed by Vlad Mihalcea
parent 828fb09eb8
commit 260b21bd63
12 changed files with 513 additions and 1 deletions

View File

@ -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 <<chapters/pc/PersistenceContext.adoc#pc-cascade,Cascading>> chapter for more info.
[[annotations-hibernate-check]]
==== `@Check`

View File

@ -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]
----
====
====
[[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]
----
====

View File

@ -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

View File

@ -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 )

View File

@ -0,0 +1,3 @@
DELETE FROM Phone WHERE id = 1
DELETE FROM Person WHERE id = 1

View File

@ -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[]
} );
}
}

View File

@ -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[]
} );
}
}

View File

@ -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[]
} );
}
}

View File

@ -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[]
} );
}
}

View File

@ -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[]
} );
}
}

View File

@ -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<Phone> 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<Phone> getPhones() {
return phones;
}
public void addPhone(Phone phone) {
this.phones.add( phone );
phone.setOwner( this );
}
}
//end::pc-cascade-domain-model-example[]

View File

@ -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[]