Java Persistence API (JPA ) Mapping

Sang Shin, sang.shin@sun.com, www.javapassion.com/j2ee



Java Platform, Enterprise Edition 5 (Java EE 5) focuses on making development easier, yet retains the richness of the J2EE 1.4 platform. Offering new and updated features such as Enterprise JavaBeans (EJB) Technology 3.0, JavaServer Faces (JSF) Technology, and the latest web services APIs, Java EE 5 makes coding simpler and more straightforward, but maintains the power that has established Java EE as the premier platform for web services and enterprise application development.

This hands-on lab takes you through the basic mapping features of JPA.  The features that will be covered in this lab include


Expected duration: 60 minutes


Software Needed

Before you begin, you need to install the following software on your computer. 


Change Log


Things to be done (by Sang Shin)


Lab Exercises





Exercise 1: Build and run "CustomerHasManyOrders" sample application


In this exercise, You are going to build a standalone Java SE sample application in which there is a One-To-Many relationship between the two entities.  (Acknowledgement: This sample application is based on the Java Persistence Example tutorial.)
  1. Create a new database
  2. Build and run the application
  3. Look under the hood of the application

(1.1) Create a new database


1. Create a new database





2. Make a connection.



3. Execute SQL command.







(1.2) Build and run the application


1. Make sure the libraries are configured. (This is an optional step.)





2. Build and run the application.






(1.3) Look under the hood of the application


1. Customer.java

package entity;

import java.io.Serializable;
import javax.persistence.*;
import static javax.persistence.CascadeType.*;

import java.util.Collection;
import java.util.ArrayList;

/**
 * @author Marina Vatkina
 */
@Entity
public class Customer implements Serializable {

    private int id;
    private String name;
    private Collection<Order> orders = new ArrayList<Order>();

    @Id
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @OneToMany(cascade = ALL, mappedBy = "customer")
    public Collection<Order> getOrders() {
        return orders;
    }

    public void setOrders(Collection<Order> newValue) {
        this.orders = newValue;
    }
}

2. Order.java

package entity;

import javax.persistence.*;

/**
 * @author Marina Vatkina
 */
@Entity
@Table(name = "ORDER_TABLE")
public class Order {

    private int id;
    private String address;
    private Customer customer;

    @Id
    @Column(name = "ORDER_ID")
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Column(name = "SHIPPING_ADDRESS")
    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @ManyToOne()
    @JoinColumn(name = "CUSTOMER_ID")
    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
}

3. Client.java

package client;

import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;

import java.util.Collection;
import java.util.List;

import entity.*;

/**
 * @author Marina Vatkina
 */
public class Client {

    private static EntityManagerFactory emf;
    private static EntityManager em;

    public static void main(String[] args) {

        // Create EntityManagerFactory for persistent unit named "pu1"
        // to be used in this test
        emf = Persistence.createEntityManagerFactory("pu1");

        // Persist all entities
        createTransactionalEntityManager();
        System.out.println("Inserting Customer and Orders... " + testInsert());
        closeTransactionalEntityManager();

        // Test query and navigation
        createEntityManager();
        System.out.println("Verifying that all are inserted... " + verifyInsert());
        closeEntityManager();

        // Get a detached instance in a new EntityManager
        createEntityManager();
        Customer c = findCustomer("Joe Smith");
        closeEntityManager();

        // Remove all entities
        createTransactionalEntityManager();
        System.out.println("Removing all... " + testDelete(c));
        closeTransactionalEntityManager();

        // Query the results
        createEntityManager();
        System.out.println("Verifying that all are removed... " + verifyDelete());
        closeEntityManager();

    }

    private static String testInsert() {

        // Create new customer
        Customer customer0 = new Customer();
        customer0.setId(1);
        customer0.setName("Joe Smith");

        // Persist the customer
        em.persist(customer0);

        // Create 2 orders
        Order order1 = new Order();
        order1.setId(100);
        order1.setAddress("123 Main St. Anytown, USA");

        Order order2 = new Order();
        order2.setId(200);
        order2.setAddress("567 1st St. Random City, USA");

        // Associate orders with the customer. The association
        // must be set on both sides of the relationship: on the
        // customer side for the orders to be persisted when
        // transaction commits, and on the order side because it
        // is the owning side.
        customer0.getOrders().add(order1);
        order1.setCustomer(customer0);

        customer0.getOrders().add(order2);
        order2.setCustomer(customer0);

        return "OK";
    }

    private static String verifyInsert() {

        Customer c = findCustomer("Joe Smith");

        Collection<Order> orders = c.getOrders();
        if (orders == null || orders.size() != 2) {
            throw new RuntimeException("Unexpected number of orders: " + ((orders == null) ? "null" : "" + orders.size()));
        }

        return "OK";
    }

    private static String testDelete(Customer c) {

        // Merge the customer to the new persistence context
        Customer c0 = em.merge(c);

        // Delete all records.
        em.remove(c0);

        return "OK";
    }

    private static String verifyDelete() {

        Query q = em.createQuery("select c from Customer c");
        List results = q.getResultList();

        if (results == null || results.size() != 0) {
            throw new RuntimeException("Unexpected number of customers after delete");
        }

        q = em.createQuery("select o from Order o");
        results = q.getResultList();

        if (results == null || results.size() != 0) {
            throw new RuntimeException("Unexpected number of orders after delete");
        }

        return "OK";
    }

    private static Customer findCustomer(String name) {

        Query q = em.createQuery("select c from Customer c where c.name = :name");
        q.setParameter("name", name);
        return (Customer) q.getSingleResult();

    }

    private static void createTransactionalEntityManager() {

        // Create a new EntityManager
        em = emf.createEntityManager();

        // Begin transaction
        em.getTransaction().begin();
    }

    private static void closeTransactionalEntityManager() {

        // Commit the transaction
        em.getTransaction().commit();

        // Close this EntityManager
        em.close();
    }

    private static void createEntityManager() {

        // Create a new EntityManager
        em = emf.createEntityManager();
    }

    private static void closeEntityManager() {

        // Close this EntityManager
        em.close();
    }
}


Solution


The solutions to this exercise are provided as a ready-to-open-and-run NetBeans projects as part of hands-on lab zip file.
                                                                                                                     

Summary


In this exercise, you have learned how to use inheritance strategies and how database tables are created accordingly.  You learned that, in SINGLE_TABLE strategy, a single table gets created for the class hierarchy. 

                                                                                                                    return to the top

Exercise 2: Exercise Inheritance O/R Mapping of JPA


There is a bug in NetBeans 6.1 that prevents this exercise to work as described in the lab document below.  This is fixed in NetBeans 6.5.

In this exercise, you are going to exercise two inheritance strategies SINGLE_TABLE and JOINED.  Under SINGLE_TABLE strategy, a single table is created per class hierarchy.  Under JOINED strategy, fields that are specific to a subclass are mapped to a separate table than the fields that are common to the parent class, and a join is performed to instantiate the subclass.

The example scenario we are going to use is Person class which has name property and Student class which as school and grade properties. The Student class is a subclass of the Person class.
  1. Use "SINGLE_TABLE" inheritance strategy implicitly (as a default)
  2. Use "SINGLE_TABLE" inheritance strategy explicitly (by using annotation)
  3. Use "JOINED" inheritance strategy

(2.1) Use "SINGLE_TABLE" inheritance strategy implicitly (as a default)


In this step, you are going to use SINGLE_TABLE as a default inheritance strategy.  The SINGLE_TABLE strategy is a default strategy, which means, in the absence of inheritance strategy annotation, SINGLE_TABLE strategy is used.

1. Create a NetBeans project.


Figure-2.10: Create a new NetBeans project




2.  Create a Persistence Unit for the project.



Figure-2.13: Provider and Database

3. Write Person Entity class.


Figure-2.14: Name and Location

4. Add name field to the Person Entity class.

package mypackage;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 *
 * @author sang
 */
@Entity
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;

    // Add a name field to the Person class
    private String name;

    public void setId(Long id) {
        this.id = id;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public Long getId() {
        return id;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Person)) {
            return false;
        }
        Person other = (Person) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "mypackage.Person[id=" + id + "]";
    }

}
Code-2.15: Modified Person.java

5. Create getter and setter (accessor) methods of the name field.


Figure-2.16: Encapsulate Fields refactoring


6. Write Student Entity class by extending Person Entity class.


Figure-2.18: Student Java class
package mypackage;

/**
 *
 * @author sang
 */
@Entity
public class Student extends Person {
   
   
}
Code-2.19: Modified Student.java

7. Do "Fix Imports" for the Student.java. (If you did code completion by pressing CTRL+Space, you do not need to do this step since NetBeans automatically adds the imports.)
Figure-2.20: Fix Imports

Figure-2.21: Fix Imports

8. Add school and grade fields to the Student.java
package mypackage;

import javax.persistence.Entity;

/**
 *
 * @author sang
 */
@Entity
public class Student extends Person implements Serializable {

    String school;
    double grade;
   
}
Code-2.22: Add fields to Student.java

9. Create getter and setter methods of the school and grade fields.

Figure-2.23: Encapsulate both school and grade fields
10. Generate JavaServer Faces (JSF) pages from the Entity classes


Figure-2.24: Select Entity classes for JSF page generation

Figure-2.25: Generated JSF Pages and Classes

Trouble-shooting: If you are seeing a warning message "mypackage.Student: Could not find Id property." as shown below, it is because of a bug in NetBeans 6.1.  This is fixed in NetBeans 6.5

Work-around with NetBeans 6.1: On NetBeans 6.1, Make the Id field protected and duplicate getId method in the Student class exactly as it appears in the Person class, including the annotations. Then you can generate the JSF pages. Then you can go back and edit the entities to be as they should.



package mypackage;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 *
 * @author sang
 */
@Entity
public class Person implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    protected Long id;   
   
    // Add a name field to the Person class
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Person)) {
            return false;
        }
        Person other = (Person) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "mypackage.Person[id=" + id + "]";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
Person.java

package mypackage;

import javax.persistence.Entity;

/**
 *
 * @author sang
 */
@Entity
public class Student extends Person {

    String school;
    private double grade;
   
    // Temporary work-around.  Delete this after you create JSF pages.
    protected Long id;    // Add a name field to the Person class

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    // End


    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }

    public double getGrade() {
        return grade;
    }

    public void setGrade(double grade) {
        this.grade = grade;
    }
}
Student.java

11. Build and run the application


Figure-2.27: Click New Person

Figure-2.28: Create a new Person

Figure-2.29: A person instance is created

13. Create a Student persistence

Figure-2.30: Click List of Student.

Figure-2.31: Click New Student.

Figure-2.32: Create a student

Figure-2.33: Listing Students.

Figure-2.34: Create a second student

Figure-2.35: Listing Students

10. Verify the database table that is created.

In this step, you are going to verify the database table that is created.  Since the default strategy is SINGLE_TABLE, the PERSON database table should be the only table that gets generated and it should contain all the fields of both Person and Student Entity classes - name, school, and grade.

Figure-2.37: PERSON database table
                                                                                                                        return to top of the exercise

(2.2) Use SINGLE_TABLE as inheritance strategy explicitly


In this step, you are going to use SINGLE_TABLE strategy by explicitly specifying it.   The behavior of the application should be the same as you've seen in Exercise 4.1 because SINGLE_TABLE strategy is the default.

1. Modify Person.java to specify SINGLE_TABLE strategy explicitly as shown in Code-2.20 below.  The code fragment that needs to be added is high-lighted in bold and blue-colored font.  This is to specify the SINGLE_TABLE explicitly as inheritance strategy.

package mypackage;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * Entity class Person
 *
 * @author sang
 */
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class Person implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
   
    // Add a name field to the Person class
    private String name;
   
    /** Creates a new instance of Person */
    public Person() {
    }

    /**
     * Gets the id of this Person.
     * @return the id
     */
    public Long getId() {
        return this.id;
    }

    /**
     * Sets the id of this Person to the specified value.
     * @param id the new id
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Returns a hash code value for the object.  This implementation computes
     * a hash code value based on the id fields in this object.
     * @return a hash code value for this object.
     */
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    /**
     * Determines whether another object is equal to this Person.  The result is
     * <code>true</code> if and only if the argument is not null and is a Person object that
     * has the same id field values as this object.
     * @param object the reference object with which to compare
     * @return <code>true</code> if this object is the same as the argument;
     * <code>false</code> otherwise.
     */
    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Person)) {
            return false;
        }
        Person other = (Person)object;
        if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) return false;
        return true;
    }

    /**
     * Returns a string representation of the object.  This implementation constructs
     * that representation based on the id fields.
     * @return a string representation of the object.
     */
    @Override
    public String toString() {
        return "mypackage.Person[id=" + id + "]";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
   
}
Code-2.20: Use SINGLE_TABLE strategy explicitly


<NetBeans Tip> You might want to take advantage of the code-completion feature of NetBeans IDE on annotations. 

Figure-2.21: Code completion on @Inheritance

Figure-2.22: Code completion on strategy=

Figure-2.23: Code completion on SINGLE_TABLE


2. Right click the newly added line and select Fix Imports. (If you did code completion by pressing CTRL+Space, you do not need to do this step since NetBeans automatically adds the imports.)
3. See JavaDoc of the SINGLE_TABLE inheritance strategy.

Figure-2.25: Show Javadoc

Figure-2.26: Javadoc of SINGLE_TABLE and JOINED

4. Run the application
4. Verify the database table that is created.
6. Right click PERSON database table and select Delete.  (This is for the subsequent exercise.)

                                                                                                                        return to top of the exercise


(2.3) Use JOINED inheritance strategy explicitly


In this step, you are going to use JOINED inheritance strategy.  You are going to follow similar steps you've done in step 4.2 above.  Just to avoid some confusion, we are going to name the two classes as Person2 and Student2.

In JOINED strategy, fields that are specific to a subclass are mapped to a separate table than the fields that are common to the parent class, and a join is performed to instantiate the subclass.

1. Create a NetBeans project. (Same step as in 4.2 above)
2.  Create a Persistence Unit for the project. (Same step as in 4.2 above) 3. Write Person2 Entity class. (Same step as in 4.2 above) 4. Add name field to the Person2 Entity class. (Same step as in 4.2 above) 5. Write Student2 Entity class by extending Person2 Entity class.
package mypacakge;

/**
 *
 * @author sang
 */
@Entity
public class Student2 extends Person2 {
   
    /** Creates a new instance of Student2 */
    public Student2() {
    }
   
}
Code-2.62: Modified Student2.java

6. Do "Fix Imports" for the Student2.java. (If you did code completion by pressing CTRL+Space, you do not need to do this step since NetBeans automatically adds the imports.)
7. Add school and grade fields to the Student2.java
package mypacakge;

import javax.persistence.Entity;

/**
 *
 * @author sang
 */
@Entity
public class Student2 extends Person2 {
   
    String school;
    double grade;
   
    /** Creates a new instance of Student2 */
    public Student2() {
    }
   
}
Code-2.63: Modified Student2.java
8. Use JOINED inheritance strategy.
package mypacakge;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * Entity class Person2
 *
 * @author sang
 */
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Person2 implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
   
    private String name;
   
    /** Creates a new instance of Person2 */
    public Person2() {
    }

    /**
     * Gets the id of this Person2.
     * @return the id
     */
    public Long getId() {
        return this.id;
    }

    /**
     * Sets the id of this Person2 to the specified value.
     * @param id the new id
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * Returns a hash code value for the object.  This implementation computes
     * a hash code value based on the id fields in this object.
     * @return a hash code value for this object.
     */
    @Override
    public int hashCode() {
        int hash = 0;
        hash += (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    /**
     * Determines whether another object is equal to this Person2.  The result is
     * <code>true</code> if and only if the argument is not null and is a Person2 object that
     * has the same id field values as this object.
     * @param object the reference object with which to compare
     * @return <code>true</code> if this object is the same as the argument;
     * <code>false</code> otherwise.
     */
    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Person2)) {
            return false;
        }
        Person2 other = (Person2)object;
        if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) return false;
        return true;
    }

    /**
     * Returns a string representation of the object.  This implementation constructs
     * that representation based on the id fields.
     * @return a string representation of the object.
     */
    @Override
    public String toString() {
        return "mypacakge.Person2[id=" + id + "]";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
   
}
Code-2.64: Use JOINED inheritance strategy

9. Generate JavaServer Faces (JSF) pages from the Entity classes
10. Run the application
11. Verify the database tables that are created.

In this step, you are going to verify the database table that is created.  In JOINED strategy, there should be two tables, PERSON table for maintaining common data fields in the hierarchy and STUDENT table for maintaining only fields that are specific to the Student Entity.

Figure-2.65: PERSON2 table

Figure-2.66: SCHOOL2 table

                                                                                                          return to top of the exercise

Solution


The solutions to this exercise are provided as a ready-to-open-and-run NetBeans projects as part of hands-on lab zip file.
                                                                                                                     

Summary


In this exercise, you have learned how to use inheritance strategies and how database tables are created accordingly.  You learned that, in SINGLE_TABLE strategy, a single table gets created for the class hierarchy. 

                                                                                                                    return to the top


Homework Exercise (for people who are taking Sang Shin's "Java EE Programming online course")


There is a bug in NetBeans 6.1 that prevents this exercise to work as described in the lab document.  This is fixed in NetBeans 6.5.  Given that most people are using NetBeans 6.1 for now, I am waiving the homework.

1. The homework is to modify the ORInheritanceJOINED project as described below.  (You might want to create a new project by copying the ORInheritanceJOINED project.  You can name the homework project in any way you want but here I am going to call it MyORInheritanceJOINED.)

2. Send the following files to j2eehomeworks@sun.com with Subject as J2EEHomework-jpamapping.