The Java Persistence API (JPA) was created to provide a standard Object Relational Mapping (ORM) solution for Java by combining the Java Data Objects API (JDO) and Entity beans from the Enterprise JavaBeans (EJB) specification. JPA technology allows data management by converting regular Java classes into entities and using an EntityManager. An entity may be defined by annotating the class or providing an XML mapping. Queries may be defined using an annotation, XML markup file or using the Criteria API. Although JPA is a part of the Java Enterprise Edition (JEE) specification, it does not require an application server or client container. Hibernate and EclipseLink (formerly TopLink) provide implementations of the JPA specification and provide stand alone implementations.

Entites
Any class may become an entity as long as it has a no argument constructor, is not declared final, has no persistent fields declared final and persistable fields should only be accessed using methods. Here is a simple example of a class that would be a good candidate for an entity:

import java.io.Serializable;

public class Customer implements Serializable
{
	private Integer customerId;
	private String firstName;
	private String lastName;
	private String emailAddress;
	private String accountNumber;

	public Integer getCustomerId()
	{
		return customerId;
	}

	public void setCustomerId(Integer customerId)
	{
		this.customerId = customerId;
	}

	public String getFirstName()
	{
		return firstName;
	}

	public void setFirstName(String firstName)
	{
		this.firstName = firstName;
	}

	public String getLastName()
	{
		return lastName;
	}

	public void setLastName(String lastName)
	{
		this.lastName = lastName;
	}

	public String getEmailAddress()
	{
		return emailAddress;
	}

	public void setEmailAddress(String emailAddress)
	{
		this.emailAddress = emailAddress;
	}

	public String getAccountNumber()
	{
		return accountNumber;
	}

	public void setAccountNumber(String accountNumber)
	{
		this.accountNumber = accountNumber;
	}
}

This is a simple class that follows the JavaBean convention. The class implements java.io.Serializable, but is only required to do so in the case where the entity is detached and given to a client over a network connection. There are two methods to declare a class an entity, the first method requires that the class be annotated as in this example:

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

@Entity
@Table(schema="myschema", name="customer_table")
public class Customer implements Serializable
{
	@Id
	@Column(name="CUSTOMER_ID")
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_ID_SEQ")
	@SequenceGenerator(name = "CUSTOMER_ID_SEQ", schema = "myschema", sequenceName = "customer_id_seq", allocationSize = 1)
	private Integer customerId;
	@Column(name="FIRST_NAME")
	private String firstName;
	@Column(name="LAST_NAME")
	private String lastName;
	@Column(name="EMAIL_ADDRESS")
	private String emailAddress;
	@Column(name="ACCOUNT_NUMBER")
	private String accountNumber;

	//Begin accessor and mutator methods here...
}

The @Entity annotation is required to mark this class as an entity and the @Table annotation defines the database table where the data will be stored/retrieved. Each field in the class that is to be persisted will also require an @Column annotation with the name of the column that holds the data. The column that represents the primary key is required to use the @Id annotation or in the case of multiple fields, use a composite key class.

There may be an instance where the domain class should not be annotated, in this case JPA provides an XML syntax. The XML must be referenced from the persistence.xml file used to configure JPA. Here is an example of the XML required to configure the Customer domain object:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
				     version="2.0">
	<package>com.jtv.domain.customer</package>
	<entity class="com.jtv.domain.customer.Customer" access="FIELD" name="Customer">
		<attributes>
			<id name="customerId" access="FIELD">
				<column name="CUST_ID"/>
				<generated-value strategy="SEQUENCE" generator="CUSTOMER_ID_SEQ"/>
				<sequence-generator name="CUSTOMER_ID_SEQ" schema="myschema" sequence-name="customer_id_seq" allocation-size="1"/>
			</id>
			<basic name="emailAddress" access="FIELD">
				<column name="EMAIL_ADDRESS"/>
			</basic>
			<basic name="firstName" access="FIELD">
				<column name="FIRST_NAME"/>
			</basic>
			<basic name="lastName" access="FIELD">
				<column name="LAST_NAME"/>
			</basic>
			<basic name="accountNumber" access="FIELD">
				<column name="ACCOUNT_NUMBER"/>
			</basic>
		</attributes>
	</entity>
</entity-mappings>

Everything is defined the same as when annotations were used, but the definitions now reside in a separate XML file. The developer has been given control to decide which approach is best suited for the task at hand.

Queries
Once a class has been identified as an entity, queries will need to be defined to manage the data. There are three methods for defining queries: annotations, XML and the Criteria API. The Criteria API is beyond the scope of this article.

Here is an example of using annotations to define queries:

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

@Entity
@Table(schema="myschema", name="customer_table")
@NamedQueries(value = {
@NamedQuery(name = "customerQuery", query = "Select c from Customer c where c.emailAddress = :email")
})
public class Customer implements Serializable
{
	@Id
	@Column(name="CUSTOMER_ID")
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_ID_SEQ")
	@SequenceGenerator(name = "CUSTOMER_ID_SEQ", schema = "myschema", sequenceName = "customer_id_seq", allocationSize = 1)
	private Integer customerId;
	@Column(name="FIRST_NAME")
	private String firstName;
	@Column(name="LAST_NAME")
	private String lastName;
	@Column(name="EMAIL_ADDRESS")
	private String emailAddress;
	@Column(name="ACCOUNT_NUMBER")
	private String accountNumber;

	//Begin accessor and mutator methods here...
}

The @NamedQuery annotation is used to define the query that may be used at a later time. The name attribute is used to help identify the query. The query attribute defines the JPQL statement that will be executed. Note that the @NamedQuery annotation is within the @NamedQueries annotation. This is so that multiple queries may be grouped and is not required. In addition to the @NamedQuery annotation there is also the @NamedNativeQuery annotation which allows for the definition of database specific queries.

When annotations are not being used, queries may be added to the entity mapping XML file. Here is the entity mapping file from an earlier example with the customerQuery:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
version="2.0">
	<package>com.jtv.domain.customer</package>
	<entity class="com.jtv.domain.customer.Customer" access="FIELD" name="Customer">
		<named-query name="customerQuery">
			<query>
			  Select c from Customer c where c.emailAddress = :email
			</query>
		</named-query>
		<attributes>
			<id name="customerId" access="FIELD">
				<column name="CUST_ID"/>
				<generated-value strategy="SEQUENCE" generator="CUSTOMER_ID_SEQ"/>
				<sequence-generator name="CUSTOMER_ID_SEQ" schema="myschema" sequence-name="customer_id_seq"
														allocation-size="1"/>
			</id>
			<basic name="emailAddress" access="FIELD">
				<column name="EMAIL_ADDRESS"/>
			</basic>
			<basic name="firstName" access="FIELD">
				<column name="FIRST_NAME"/>
			</basic>
			<basic name="lastName" access="FIELD">
				<column name="LAST_NAME"/>
			</basic>
			<basic name="accountNumber" access="FIELD">
				<column name="ACCOUNT_NUMBER"/>
			</basic>
		</attributes>
	</entity>
</entity-mappings>

A third solution exists where the class may be annotated, but the queries exist only in the XML as in this example:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
version="2.0">
	<package>com.jtv.domain.customer</package>
	<entity class="com.jtv.domain.customer.Customer" access="FIELD" name="Customer">
		<named-query name="customerQuery">
			<query>
			  Select c from Customer c where c.emailAddress = :email
			</query>
		</named-query>
	</entity>
</entity-mappings>

This last example shows the flexibility that JPA provides when configuring entities and queries.

Usage with EJB
This section will provide high level instructions for using JPA with an EJB application (tested using GlassFish application server 3.1.x). The first step is to tell the EJB application that JPA will be used by creating a file named persistence.xml in the META-INF directory. Here is an example of the file:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
	<persistence-unit name="customer-db">
		<jta-data-source>jdbc/CustomerDB</jta-data-source>
		<mapping-file>/com/example/customer/persistence/jpa/customer.xml</mapping-file>
		<!-- Use this to log SQL statements -->
		<properties>
			<property name="eclipselink.logging.level" value="FINE"/>
		</properties>
	</persistence-unit>
</persistence>

Line 3 defines a persistence unit named customer-db that will be used later in the EJB class. Line 4 references the JDBC datasource that will be used when executing data queries. The container should already have defined the JDBC datasource prior to application deployment. Line 5 pulls in the external mapping file (ensure that the file exists on the classpath) discussed in the previous sections. This line is not required if the annotation method is used exclusively. Lines 7, 8 and 9 are used to log the SQL statements being issued to the database if the EclipseLink ORM tool is being used.
The application is now aware JPA will be used. The javax.persistence.EntityManager will need to be injected into the EJB as in this example:

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

@Stateless
public class CustomerDAO
{
	@PersistenceContext(unitName = "customer-db")
	private EntityManager em;

	public Customer getCustomerById(Integer customerId)
	{
	  return em.find(Customer.class, custId);
	}

	public Customer findCustomerByEmailAddress(String emailAddress)
	{
	  Query q = em.createNamedQuery("customerQuery");
	  q.setParameter("email", emailAddress.trim());
	  
	  return (Customer) q.getSingleResult();
	}
}

The @PersistenceContext annotation on line 9 tells the container that this class wishes to use JPA and specifies the persistence unit named customer-db that was defined in the persistence.xml file. Line 10 is the variable where the javax.persistence.EntityManager class will be injected. The getCustomerById method shows an example of finding an entity without using a query by using the Id field (this field was identified when the entity was defined). The findCustomerByEmailAddress method shows an example of using the customerQuery query that was defined in the Queries section. The call on line 20 sets the parameter named email. Parameters are named instead of positional which improves code readability. These methods are very simple examples, but should an entity be passed to a client, the entity is required to implement java.io.Serializable and detached from the EntityManager using this call: em.detach(customer). Changes to a detached item can be saved using this call: em.merge(customer).

Resources
Introduction to Java Enterprise Edition
Introduction to Enterprise JavaBeans
Build Java Enterprise Artifacts with Maven

Advertisements