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.
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 EntityManager
. EntityManagerFactory
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 Customer
, Employee
, Order
, 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
ENTITY | TABLE NAME |
Customer |
CUSTOMER |
OnlineCustomer |
CUSTOMER |
The Customer
entity has custId
, firstName
, lastName
, custType
, 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
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
ENTITY | TABLE 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 Customer
is 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. referencedColumnName
denotes 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
ENTITY | TABLE 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.