Chan Chen Coding...

Understand JPA

Need three artifacts to implement a JPA-compliant program:

  • An entity class
  • A persistence.xml file
  • A class through which you will insert, update, or find an entity

JPA is all about data persistence, so let's begin our examination of how it works with the data store design. Assume you have a CUSTOMER table, as in Table 1 below.

Table 1. CUSTOMER table schema

NAMEPK?TYPENULL?
CUST_ID Y INTEGER NOT NULL
FIRST_NAME   VARCHAR(50) NOT NULL
LAST_NAME   VARCHAR(50)  
STREET   VARCHAR(50)  
APPT   VARCHAR(20) NOT NULL
CITY   VARCHAR(25)  
ZIP_CODE   VARCHAR(10) NOT NULL
CUST_TYPE   VARCHAR(10) NOT NULL
LAST_UPDATED_TIME   TIMESTAMP NOT NULL

The persistence object: Entity

Since JPA is all about entity-relationship mapping, next you need to take a look at the design of the Customer entity object. The entity object is nothing but a POJO class marked as an entity with the@Entity annotation, as you can see in Listing 1.

Listing 1. The Customer entity

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Entity(name = "CUSTOMER") //Name of the entity
public class Customer implements Serializable{
    private long custId;
    private String firstName;
    private String lastName;
    private String street;
    private String appt;
    private String city;    
    private String zipCode;
    private String custType;
    private Date updatedTime;

// Getters and setters go here 
.
}

The Customer entity needs to know how to map the attributes (or properties) to the CUSTOMER table. You can do this either through a configuration file called orm.xml (similar to a .hbm file in Hibernate) or, as shown in Listing 2, through JPA annotations.

Listing 2. The Customer entity with annotations

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@Entity(name = "CUSTOMER") //Name of the entity
public class Customer implements Serializable{
    @Id //signifies the primary key
    @Column(name = "CUST_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long custId;
    
    @Column(name = "FIRST_NAME", nullable = false,length = 50)
    private String firstName;
    
    @Column(name = "LAST_NAME", length = 50)
    private String lastName;
    
// By default column name is same as attribute name
    private String street;
    
    @Column(name = "APPT",nullable = false)  
    private String appt;
    
    // By default column name is same as attribute name
    private String city;
    
    @Column(name = "ZIP_CODE",nullable = false)  
    // Name of the corresponding database column
    private String zipCode;
    
    @Column(name = "CUST_TYPE", length = 10)
    private String custType;

    @Version
    @Column(name = "LAST_UPDATED_TIME")
    private Date updatedTime;

// Getters and setters go here 
.
}

Take a closer look at the annotations defined in Listing 2.

  • The annotations are defined in javax.persistence, so you'll need to import that package.
  • @Enitity signifies that a particular class is an entity class. If the entity name is different from the table name, then the @Table annotation is used; otherwise, it isn't required.
  • @Column provides the name of the column in a table if it is different from the attribute name. (By default, the two names are assumed to be the same.)
  • @Id signifies the primary key.
  • @Version signifies a version field in an entity. JPA uses a version field to detect concurrent modifications to a data store record. When the JPA runtime detects multiple attempts to concurrently modify the same record, it throws an exception to the transaction attempting to commit last. This prevents you from overwriting the previous commit with stale data.
  • By default, all the fields are of type @Basic, which are persisted as-is in the database.
  • @GeneratedValue signifies a strategy to assign a unique value to your identity fields automatically. The types of strategies available are IDENTITY, SEQUENCE, TABLE, and AUTO. The default strategy is auto, the implementation of which is left to the JPA vendor to implement. (OpenJPA implements it through a table sequence.)

There are a few points to keep in mind when creating an entity class:

  • JPA allows persistent classes to inherit from non-persistent classes, persistent classes to inherit from other persistent classes, and non-persistent classes to inherit from persistent classes.
  • The entity class should have a default no-argument constructor.
  • The entity class should not be final.
  • Persistent classes cannot inherit from certain natively-implemented system classes such as java.net.Socket and java.lang.Thread.
  • If a persistent class inherits from a non-persistent class, the fields of the non-persistent super class cannot be persisted.

Persistence units

Now that the entity class is complete, you can move on to persistence.xml, shown in Listing 3. This is an XML file placed in the META-INF folder; it's used to specify the persistence provider name, entity class names, and properties like the database connection URL, driver, user, password, and so on.

Listing 3. A sample persistence.xml file

<?xml version="1.0"?>
<persistence>
    <persistence-unit name="testjpa" transaction-type="RESOURCE_LOCAL">
        <provider>
            org.apache.openjpa.persistence.PersistenceProviderImpl
        </provider>
        <class>entity.Customer</class>
        <properties>
            <property name="openjpa.ConnectionURL"          value="jdbc:derby://localhost:1527/D:\OpenJPA\Derby\testdb;create=true"/>
            <property name="openjpa.ConnectionDriverName" 
            value="org.apache.derby.jdbc.ClientDriver"/>
            <property name="openjpa.ConnectionUserName" value="admin"/>
            <property name="openjpa.ConnectionPassword" value="admin"/>
    <property name="openjpa.Log" value="SQL=TRACE"/>
        </properties>
    </persistence-unit>
</persistence>

Some important things to note about Listing 3 and the persistence.xml file:

  • persistence.xml can have multiple persistence units. Each unit can be used by different JPA vendor or can be used to persist to different databases.
  • The vendor-specific persistence provider name is specified in the<provider> tag. The persistence provider for OpenJPA isorg.apache.openjpa.persistence.PersistenceProviderImpl.
  • The entity class names are specified in the <class> tag.
  • The database connection properties can be specified within the<properties> tag. Note that the property name will differ for each vendor.
  • OpenJPA has its own default logging facility, the default level of which is INFO.

The real show

Now that the prep work is out of the way, you're ready to write a class that will insert a record into the CUSTOMER table. This is shown in Listing 4.

Listing 4. sample code for object persistence

public static void main(String[] args) {
    EntityManagerFactory entityManagerFactory =  Persistence.createEntityManagerFactory("testjpa");
    EntityManager em = entityManagerFactory.createEntityManager();
    EntityTransaction userTransaction = em.getTransaction();
    
    userTransaction.begin();
    Customer customer = new Customer();
    customer.setFirstName("Charles");
    customer.setLastName("Dickens");
    customer.setCustType("RETAIL");
    customer.setStreet("10 Downing Street");
    customer.setAppt("1");
    customer.setCity("NewYork");
    customer.setZipCode("12345");
    em.persist(customer);
    userTransaction.commit();
    em.close();
    entityManagerFactory.close();
}

Drill down to see what the code in Listing 4 actually does. The action starts with the Persistence class. The javadoc says, "Persistence is a bootstrap class that is used to obtain an EntityManagerFactory," like so:
EntityManagerFactory emf=Persistence.createEntityManagerFactory("testjpa");

The work of the Persistence class is pretty simple:

  • In the classpath resources, the Persistence class searches for javax.persistence.spi.PersistenceProvider files in META-INF/services/directory. It reads the PersistenceProvider implementation class names from each file.
  • It then calls createEntityManagerFactory() on each PersistenceProvider with the persistenceUnitName until it gets a anEntityManagerFactory back that isn't null. The provider name for OpenJPA is org.apache.openjpa.persistence.PersistenceProviderImpl.

How does PersistenceProvider get the right EntityManagerFactory? This is up to the vendor to implement.

EntityManagerFactory is a factory for creating an EntityManagerEntityManagerFactory should be cached and should ideally be called once for each persistence unit name in the whole application.

EntityManager manages entities; it is responsible for their addition, updating, and deletion. You can find an entity without a transaction; however, add, update, and delete operations need to be within a transaction.

If you ignore transaction management in a fetch operation, the entity does not become managed and hence the system will do a trip to the database every time you try to fetch a record from the data store; in such a scenario, you'd end up fetching a separate object every time.

The rest of the code is pretty self-explanatory. It creates a customer object, sets the values to the appropriate properties, and inserts the object to the data store, as you can see in Listing 5.

Listing 5. A code snippet for object persistence

EntityTransaction userTransaction = em.getTransaction();
    userTransaction.begin();
    em.persist(customer);
    userTransaction.commit();

You may have already noticed by now that the code does not set the custId and updatedTime to the customer object explicitly. Because the primary key generation strategy is AUTO, the JPA provider will take care of populating the primary key. Similarly, version fields (updatedTime, in this case) are also automatically populated by the JPA provider.

Now you need to find the record that's been inserted. Finding a record with a primary key is as simple as Customer cust = em.find(Customer.class, objId);, as you can see in Listing 6.

Listing 6. Fetching data as an object

.
OpenJPAEntityManager oem = OpenJPAPersistence.cast(em);
Object objId = oem.getObjectId(customer);
Customer cust = em.find(Customer.class, objId);
.

A composite primary key

Now, how do you find a record in the database with a primary key if the entity has a composite primary key? You'll see how that works in a moment. Assume that the CUSTOMER table doesn't have aCUST_ID field, but that FIRST_NAME and LAST_NAME together make up the primary key. To make this work, you need to create a separate class, generally called an identity class, with attributes the same as the IDs; then you reference the identity class in the entity class. This is shown in Listing 7.

Listing 7. An identity class

public class CustomerId {
    public String firstName;
    public String lastName;
    // override equal() method
    
//override hascode() method

}

The identity class can be a separate class or an inner class. If the class is an inner class, it must be static and should be referenced in the entity class, as in Listing 8. The code for finding records with a composite primary key is exactly the same as that for finding records with a single primary key.

Listing 8. Using an identity class

@Entity
@IdClass(Customer.CustomerId.class)
public class Customer implements Serializable{
    @Id
    @Column(name = "FIRST_NAME", nullable = false, length = 50)
    private String firstName;

    @Id
    @Column(name = "LAST_NAME", length = 50)
    private String lastName;

    private String street;

    @Column(name = "APPT",nullable = false)  
    private String appt;
    
.
}


The callbacks

JPA provides callback methods for performing actions at different stages of persistence operations. Imagine that you want to update a customer record, but, before you update, you want to remove the hyphen from the zip code if one is present. Or say that you want to populate some transient fields after a successful fetch. JPA provides listeners for these kinds of activities before and after each fetch, insert, or update operation. The callback methods can be annotated as any of the following:

  • @PostLoad
  • @PrePersist
  • @PostPersist
  • @PreUpdate
  • @PostUpdate
  • @PreRemove
  • @PostRemove

You can write the callback methods in the entity class itself, or you can write them in a separate class and reference them in the entity class with @EntityListeners, as shown in Listing 9.

Listing 9. implementing callback

@EntityListeners({CustListner.class})
@Entity(name = "CUSTOMER") //Name of the entity
public class Customer implements Serializable{


}
public class CustListner {
    @PreUpdate
    public void preUpdate(Customer cust) {
         System.out.println("In pre update");
    }
    @PostUpdate
    public void postUpdate(Customer cust) {
         System.out.println("In post update");
    }
}

Embedded objects

As you've seen so far, the Customer entity has the address information inline in the entity itself. What if you want to apply class normalization concepts and come up with a separate Address class and refer to that in the Customer entity? After all, an address object could be used with CustomerEmployeeOrder, or User entities.

All you need is an embedded object. You move the address information into a separate class and mark that class as being embeddable, as shown in Listing 10. Refer to this newly created class from the Customer entity with @Embedded annotations.

Listing 10. An embeddable class

@Embeddable
public class Address implements Serializable{
    
    private String street;
    
    @Column(name = "APPT",nullable = false)  
    private String appt;

    private String city;
..
..
}

Embedded classes are mapped together with their owning entity as part of the state of that entity. However, they cannot be queried separately. Listing 11 illustrates a sample entity that uses an embedded object.

Listing 11. A sample entity using an embedded object

@Entity
public class Customer {

    @Column(name = "FIRST_NAME", nullable = false,length = 50)
    private String firstName;
        
    @Embedded
    private Address address;
..
}

The power of inheritance

An entity can extend the following:

  • Another entity -- either concrete or abstract.
  • Another non-entity, supplying behavior or non-persistence state. The attributes you inherit from a non-entity are not persisted.
  • Mapped superclasses, supplying common entity state. Tables in a database have similar fields, but tables are not related to each other.

Let's have a look into the various types of inheritance JPA offers. For this scenario, assume that there are two types of customer: a normal customer who buys products from a physical store and an online customer who buys products over the Internet.

Single-table inheritance

In single-table inheritance, all the entities in the hierarchy are stored in a single table. Single-table inheritance is the default strategy. Thus, you could omit the @Inheritance annotation in the example code in Listing 12 and get the same result.

In the example application, both ordinary and online customers are stored in the CUSTOMER table, as shown in Table 2.

Table 2. Single-table inheritance mapping strategy

ENTITYTABLE NAME
Customer CUSTOMER
OnlineCustomer CUSTOMER

The Customer entity has custIdfirstNamelastNamecustType, and address information, whereas the OnlineCustomer entity has only awebsite attribute and otherwise extends all features of Customer. This strategy should be reflected in the superclass, as in Listing 12.

Listing 12. A sample superclass in single-table inheritance

@Entity(name = "CUSTOMER") 
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="CUST_TYPE", discriminatorType=DiscriminatorType.STRING,length=10)
@DiscriminatorValue("RETAIL")
public class Customer implements Serializable{
    @Id 
    @Column(name = "CUST_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long custId;
    
    @Column(name = "FIRST_NAME", nullable = false,length = 50)
    private String firstName;
    
    @Column(name = "LAST_NAME", length = 50)
    private String lastName;
    
    @Embedded
    private Address address = new Address();
    
    @Column(name = "CUST_TYPE", length = 10)
    private String custType;
.
}

For the time being, ignore the DiscriminatorColumn and DiscriminatorValue annotations; you'll see how those work later. TheOnlineCustomer entity will be a normal entity class that extends Customer class, as shown in Listing 13.

Listing 13. A sample subclass in single-table inheritance

@Entity(name = "ONLINECUSTOMER") //Name of the entity
@DiscriminatorValue("ONLINE")
public class OnlineCustomer extends Customer{
    @Column(name = "WEBSITE", length = 100)
    private String website;
    
}

Now you must create a Customer object and an OnlineCustomer object and persist them, as in Listing 14.

Listing 14. Persisting objects in single-table inheritance

.
userTransaction.begin();
//inserting Customer
Customer customer = new Customer();
customer.setFirstName("Charles");
customer.setLastName("Dickens");
customer.setCustType("RETAIL");
customer.getAddress().setStreet("10 Downing Street");
customer.getAddress().setAppt("1");
customer.getAddress().setCity("NewYork");
customer.getAddress().setZipCode("12345");
em.persist(customer);
//Inserting Online customer
OnlineCustomer onlineCust = new OnlineCustomer();
onlineCust.setFirstName("Henry");
onlineCust.setLastName("Ho");
onlineCust.setCustType("ONLINE");
onlineCust.getAddress().setStreet("1 Mission Street");
onlineCust.getAddress().setAppt("111");
onlineCust.getAddress().setCity("NewYork");
onlineCust.getAddress().setZipCode("23456");
onlineCust.setWebsite("www.amazon.com");
em.persist(onlineCust);
userTransaction.commit();
.

If you have a look into the CUSTOMER table now, you will find two records. The query in Listing 15 should return you the list of online customers from the data store.

Listing 15. Fetching only the subclass in single-table inheritance

..
Query query = em.createQuery("SELECT customer FROM ONLINECUSTOMER customer");
List<OnlineCustomer> list= query.getResultList();
..

If the CUSTOMER table stores both the Customer and the OnlineCustomer data, how will JPA distinguish one from the other? How will it fetch only the online customers? In fact, JPA cannot do this unless you provide it with a hint. That's the significance of the @DiscriminatorColumn. It tells the CUSTOMER table which column distinguishes a CUSTOMER from an ONLINE CUSTOMER. @DiscriminatorValue indicates what value identifies a CUSTOMER and an ONLINE CUSTOMER. The@DiscriminatorValue annotation needs to be provided in the superclass as well as in all the subclasses.

When you want to fetch online customers, JPA silently queries the data store as in Listing 16.

Listing 16. Distinguishing objects stored in a single table

SELECT t0.CUST_ID, t0.CUST_TYPE, t0.LAST_UPDATED_TIME, t0.APPT, t0.city, t0.street, t0.ZIP_CODE, t0.FIRST_NAME, t0.LAST_NAME, t0.WEBSITE FROM CUSTOMER t0 
WHERE t0.CUST_TYPE = 'ONLINE'

Joined-table inheritance

In the joined-table inheritance strategy, the common states of the class are stored in one table, and the state of the subclass is stored in another table that is joined to the first table, as shown in Table 3.

Table 3. Joined-table inheritance mapping strategy

ENTITYTABLE NAME
Customer CUSTOMER
OnlineCustomer ONLINECUSTOMER (only Website information is stored here; the rest of the information is stored in the CUSTOMER table)

The common data that the OnlineCustomer entity shares with Customeris stored in the CUSTOMER table; OnlineCustomer-specific data is stored in the ONLINECUSTOMER table, connected by a foreign key constraint. From the JPA implementation standpoint, the only change needed in the OnlineCustomer entity is that the JOINED strategy must be provided, as in Listing 17.

Listing 17. A superclass in joined-table inheritance

@Entity(name = "CUSTOMER") //Name of the entity
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="CUST_TYPE", discriminatorType=DiscriminatorType.STRING,length=10)
@DiscriminatorValue("RETAIL")

public class Customer implements Serializable{
    @Id //signifies the primary key
    @Column(name = "CUST_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long custId;
    
    @Column(name = "FIRST_NAME", nullable = false,length = 50)
    private String firstName;
    
    @Column(name = "LAST_NAME", length = 50)
    private String lastName;
    
    @Embedded
    private Address address = new Address();
    
    @Column(name = "CUST_TYPE", length = 10)
    private String custType;
    ..
}

In the OnlineCustomer entity, shown in Listing 18, you specify the subclass-specific attributes and a foreign key join column with the@PrimaryKeyJoinColumn annotation, which maps to the primary key of the parent table.

Listing 18. A sample subclass in joined-table inheritance

@Table(name="ONLINECUSTOMER")
@Entity(name = "ONLINECUSTOMER") //Name of the entity
@DiscriminatorValue("ONLINE")
@PrimaryKeyJoinColumn(name="CUST_ID",referencedColumnName="CUST_ID")
public class OnlineCustomer extends Customer{

    @Column(name = "WEBSITE", length = 100)
    private String website;
    .
}

In Listing 18, the name property of @PrimaryKeyJoinColumn denotes the primary key of the subclass table. referencedColumnNamedenotes the name of the superclass table column to which this subclass table column joins. Nothing changes in the way in which you persist and fetch the Customer and OnlineCustomer objects.

Table per class inheritance

In the table per class inheritance strategy, each class state in a hierarchy is stored in a separate table, as illustrated in Table 4.

Table 4. Table per class inheritance mapping strategy

ENTITYTABLE NAME
Customer CUSTOMER
OnlineCustomer ONLINECUSTOMER

You don't need to provide the @DiscriminatorColumn annotation here, as the entities are stored in separate tables altogether. Also, no @PrimaryKeyJoinColumn annotation is required, as no relationship exists between subclass and superclass tables. TheCustomer superclass will look like Listing 19.

Listing 19. A sample superclass in table per class inheritance

@Entity(name = "CUSTOMER")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Customer implements Serializable{
    @Id //signifies the primary key
    @Column(name = "CUST_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long custId;
    
    @Column(name = "FIRST_NAME", nullable = false,length = 50)
    private String firstName;
    
    @Column(name = "LAST_NAME", length = 50)
    private String lastName;
    
    @Embedded
    private Address address = new Address();
    ..
}


OnlineCustomer will be like a normal subclass, as shown in Listing 20. Nothing changes in the way in which you persist and fetch the Customer and OnlineCustomer objects.

Listing 20. A sample subclass in table per class inheritance

@Entity(name = "ONLINECUSTOMER") //Name of the entity
public class OnlineCustomer extends Customer{
        
    @Column(name = "WEBSITE", length = 100)
    private String website;
    ..
}

Toward a more object-oriented world

In this first half of "Understanding the Java Persistence API," you saw how you can apply object-oriented concepts like inheritance and event callbacks using JPA. The object-oriented possibilities in JPA go far beyond what has been covered in this article: JPA also lets you write JPQL queries (similar to Hibernate's HQL queries) or native SQL queries, and has transaction management facilities.

In the second half of this article, you'll have the opportunity to explore data relationships the JPA way -- that is, with object-oriented grace. Look for that article next week. In the meantime, have fun on your own, exploring JPA's object-oriented paradigm of data persistence!

About the author

Aditi Das is a technical architect with Infosys Technologies and has seven years of specialized experience in Java and JEE. She is a Sun-certified enterprise architect (SCEA), Web component developer (SCWCD), business component developer (SCBCD), and Web service developer (SCDJWS). She is very much inspired by the Head First philosophy of learning new technologies, and hopes that someday a book will come out on the past, present, and future of SOA.

 



-----------------------------------------------------
Silence, the way to avoid many problems;
Smile, the way to solve many problems;

posted on 2012-12-24 17:12 Chan Chen 阅读(475) 评论(0)  编辑  收藏 所属分类: Scala / Java


只有注册用户登录后才能发表评论。


网站导航: