Exercise 7.1: Cascading
This exercise shows you how cascading affects the operation of entity manager life cycle methods. The examples in this section do not require the JBoss application server to run, and they use the Hibernate standalone persistence implementation of Java Persistence. Two client applications demonstrate the concepts of cascading:
Client1.java
-
This client shows how cascading affects the operation of the persist( ), merge( ), and remove( ) methods of the EntityManager interface.
Client2.java
-
This client shows you how the merge( ) operation can also persist new related entities.
Start Up JBoss
There is no need to start up JBoss in this example because standalone persistence is being used.
Initialize the Database
The database tables will be created when the clients in this exercise run. The database is an in-memory-only database and will not be available when the program completes.
Build the Example Programs
Perform the following steps:
Open a command prompt or shell terminal and change to the ex07_1 directory created by the extraction process. Set the JAVA_HOME and JBOSS_HOME environment variables to point to where your JDK and JBoss 4.0 are installed. Examples:
Windows:
-
C:\workbook\ex07_1> set JAVA_HOME=C:\jdk1.5.0
C:\workbook\ex07_1> set JBOSS_HOME=C:\jboss-4.0.x
Unix:
-
$ export JAVA_HOME=/usr/local/jdk1.5.0
$ export JBOSS_HOME=/usr/local/jboss-4.0
Add ant to your execution path. Ant is the build utility.
Windows:
-
C:\workbook\ex07_1> set PATH=..\ant\bin;%PATH%
Unix:
-
$ export PATH=../ant/bin:$PATH
Perform the build by typing ant.
The titan.jar will be built. This JAR contains all the client and entity classes used in this example.
Examine the Entities
This exercise implements all of the entities
from Chapter 7. You can find the implementations of the Ship, Cabin, Cruise, Customer, Phone, CreditCard, and Reservation classes in the com.titan.domain
package in the src directory of the exercise. Take a moment to browse these files. We will not go into detail on every implementation of these files because this topic is already discussed in detail in Chapter 7.
Examine Client1
Client1 shows an example of manipulating the @OneToOne
relationship between the Customer and Address entity beans. It illustrates how cascading affects the behavior of the persist( ), merge( ), and remove( ) EntityManager methods. Let's examine this file.
Client1.java
package com.titan.clients;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import com.titan.domain.*;
public class Client1
{
public static void main(String[] args) throws Exception
{
Because we're using Java Persistence in a standalone application, we first obtain access to an EntityManagerFactory instance so that we can obtain EntityManager instances:
try
{
EntityManagerFactory factory =
Persistence.createEntityManagerFactory("titan");
Customer cust = createCustomerAddress(factory);
cascadeMergeAddress(factory, cust);
cascadeRemoveAddress(factory, cust);
}
finally
{
factory.close( );
}
}
The example first calls the createCustomerAddress( ) method to initialize the database. Then, the cascadeMergeAddress( ) method is called to show the effects of cascading and merging. Finally, the cascadeRemoveAddress( ) method is called to display the effects of cascading with the EntityManager.remove( ) operation. Let's examine each method.
createCustomerAddress( )
public static Customer createCustomerAddress(EntityManagerFactory factory)
{
Customer cust = new Customer( );
cust.setFirstName("Bill");
cust.setLastName("Burke");
Address address = new Address( );
address.setStreet("Beacon Street");
address.setCity("Boston");
address.setState("MA");
address.setZip("02115");
cust.setAddress(address);
The method begins by allocating a Customer and Address and then initializing their properties. The Customer-Address relationship is set up as well.
EntityManager manager = factory.createEntityManager( );
try {
manager.getTransaction().begin( );
manager.persist(cust);
manager.getTransaction().commit( );
Next, the method creates an EntityManager instance and begins a resource-local transaction. The Customer instance is then persisted using EntityManager.persist( ). When the resource-local transaction is committed, a primary key is generated for the Customer and Address instances and both are inserted into the database. Given that the @OneToOne
relationship has a CascadeType
of ALL, you do not additionally need to persist( ) the Address instance. The entity manager does this operation automatically for you.
} finally {
manager.close( );
}
System.out.println("Address was also persisted with auto-generated key: "
+ address.getId( ));
System.out.println("Return detached Customer instance: " + cust.getId( ));
return cust;
}
The createCustomerAddress( ) method ends by first cleaning up the EntityManager instance and then outputting the generated primary keys of the Customer and Address instances.
cascadeMergeAddress( )
public static void cascadeMergeAddress(EntityManagerFactory factory,
Customer cust)
{
System.out.println("Show cascade merge( )");
cust.getAddress( ).setStreet("1 Yawkey Way");
Outside of a transaction, the Customer entity's street address is updated.
EntityManager manager = factory.createEntityManager( );
try {
manager.getTransaction().begin( );
manager.merge(cust);
manager.getTransaction().commit( );
The code then begins a resource-local transaction and merges the Customer instance. Because the CascadeType is ALL for the Customer's address property, the entity manager will traverse the Address relationship and merge the changes to the Address's street. After the merge, the changes are committed to the database.
manager.clear( );
Customer custCopy = manager.find(Customer.class, cust.getId( ));
System.out.println(custCopy.getAddress().getStreet( ));
To indicate that the customer's street address has actually changed in the database, the code detaches all managed entity instances and queries for the Customer entity again.
} finally {
manager.close( );
}
}
Finally, the created EntityManager instance is cleaned up.
cascadeRemoveAddress( )
public static void cascadeRemoveAddress(EntityManagerFactory factory, Customer cust)
{
System.out.println("Show cascade remove( )");
EntityManager manager = factory.createEntityManager( );
try {
manager.getTransaction().begin( );
Customer custCopy = manager.find(Customer.class, cust.getId( ));
manager.remove(custCopy);
manager.getTransaction().commit( );
This part of the method begins a new resource-local transaction and removes the Customer entity from the database. Again, because the Customer's address property has a CascadeType
of ALL, the related Address is removed along with the Customer.
Address addressCopy = manager.find(Address.class, cust.getAddress( ).getId( ));
System.out.println("addressCopy is null: " + addressCopy);
} finally {
manager.close( );
}
}
}
Finally, the code shows that the related Address has been removed from the database by querying for it and showing that the value is null.
Run Client1
Run the Client1 application by invoking ant run.client1 at the command prompt. Remember to set your JBOSS_HOME and PATH environment variables. This is the output:
run.client1:
[java] Create 1st Customer
[java] Address was also persisted with auto-generated key: 1
[java] Return detached Customer instance: 1
[java] Show cascade merge( )
[java] 1 Yawkey Way
[java] Show cascade remove( )
[java] addressCopy is null: null
Examine Client2
Client2 shows an example of how a cascaded merge will also create related entities in the database if they do not already exist. Let's examine this file.
Client2.java
package com.titan.clients;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import com.titan.domain.*;
public class Client2
{
public static void main(String[] args) throws Exception
{
EntityManagerFactory factory =
Persistence.createEntityManagerFactory("titan");
Because this is a standalone Java application, we again need to obtain an EntityManagerFactory to create our EntityManager instances:
try
{
Customer cust = Client1.createCustomerAddress(factory);
addPhoneNumbers(factory, cust);
}
finally
{
factory.close( );
}
}
The example creates the same Customer entity that Client1 does by calling that program's createCustomerAddress( ) method. This method returns a detached Customer instance. It then adds some phone numbers to the detached Customer in the addPhoneNumbers( ) method. Let's look at the implementation of this method:
public static void addPhoneNumbers(EntityManagerFactory factory,
Customer cust)
{
Phone phone1 = new Phone( );
phone1.setNumber("617-666-6666");
phone1.setType((byte)1);
cust.getPhoneNumbers( ).add(phone1);
Outside of a transaction, the code first allocates a Phone entity and adds it to the detached Customer's set of phoneNumbers
.
EntityManager manager = factory.createEntityManager( );
try {
manager.getTransaction().begin( );
manager.merge(cust);
manager.getTransaction().commit( );
}
Next, within a resource-local transaction, the code merges the modified Customer instance. Because the Customer's phoneNumbers
property has a CascadeType
of ALL, the merge( ) operation sees that the phoneNumbers property has a brand-new Phone instance within it and inserts this new entity into the database.
manager.clear( );
Customer custCopy = manager.find(Customer.class, cust.getId( ));
for (Phone phone : custCopy.getPhoneNumbers( ))
{
System.out.println("Phone number: " + phone.getNumber( ));
}
} finally {
manager.close( );
}
}
}
Finally, the code detaches all entity instances from the entity manager by calling EntityManager.clear( ) and queries for a new copy of the Customer entity. Examining the new copy shows that the phone number was added to the Customer-Phone relationship.
Run Client2
Run the Client2 application by invoking ant run.client2 at the command prompt. Remember to set your JBOSS_HOME and PATH environment variables. This is the output:
run.client2:
[java] Create 1st Customer
[java] Address was also persisted with auto-generated key: 1
[java] Return detached Customer instance: 1
[java] Phone number: 617-666-6666
|