Chapter 4. Java Persistence API

The Java Persistence API (JPA) is defined as JSR 317, and the complete specification can be downloaded from http://jcp.org/aboutJava/communityprocess/final/jsr317/index.html.

JPA defines an API for the management of persistence and object/relational mapping using a Java domain model.

A database table, typically with multiple columns, stores the persistent state of an application. Multiple rows are stored in the database table to capture different states. A single column or combination of columns may define the uniqueness of each row using primary key constraint. Typically, an application accesses and stores data to multiple tables. These tables generally have relationships defined among them using foreign key constraint.

JPA defines a standard mapping between a database table and a POJO. It defines syntax to capture primary and foreign key constraints and how these rows can be created, read, updated, and deleted using these POJOs. Transactions, caching, validation, and other similar capabilities required by an application accessing a database are also defined by JPA.

This chapter will discuss the key concepts of JPA.

Entities

A POJO with a no-arg public constructor is used to define the mapping with one or more relational database tables. Each such class is annotated with @Entity, and the instance variables that follow JavaBeans-style properties represent the persistent state of the entity. The mapping between the table column and the field name is derived following reasonable defaults and can be overridden by annotations. For example, the table name is the same as the class name, and the column names are the same as the persistent field names.

Here is a simple entity definition describing a student:

@Entity 
public class Student implements Serializable {
  @Id 
  private int id;
  private String name;
  private String grade;
  @Embedded 
  private Address address;

  @ElementCollection
  @CollectionTable("StudentCourse")
  List<Course> courses;

  //. . . 
}

A few things to observe in this code:

  • This class has a no-arg constructor by default, as no other constructors are defined.

  • The entity’s persistent state is defined by four fields; the identity is defined by the field id and is annotated with @Id. A composite primary key may also be defined where the primary key corresponds to one or more fields of the entity class.

  • The class implements a Serializable interface, and that allows it to be passed by value through a remote interface.

  • Address is a POJO class that does not have a persistent identity of its own and exclusively belongs to the Student class. This class is called as an embeddable class and is identified by @Embedded on the field in the entity class and annotated with @Embeddable in the class definition:

    @Embeddable
    public class Address {
      private String street;
      private String city;
      private String zip;
      //. . .
    }

    This allows the database structure to be more naturally mapped in Java.

  • The @ElementCollection annotation signifies that a student’s courses are listed in a different table. By default, the table name is derived by combining the name of the owning class, the string “_,” and the field name. @CollectionTable can be used to override the default name of the table, and @AtttributeOverrides can be used to override the default column names. @ElementCollection can also be applied to an embeddable class.

The persistent fields or properties of an entity may be of the following types: Java primitive types, java.lang.String, java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time, java.sql.Timestamp, byte[], Byte[], char[], Character[], enums and other Java serializable types, entity types, collections of entity types, embeddable classes, and collections of basic and embeddable classes. The @Temporal annotation may be specified on fields of type java.util.Date and java.util.Calendar to specify the temporal type of the field.

An entity may inherit from a superclass that provides persistent entity state and mapping information, but which itself may or may not be an entity. An entity superclass is abstract and cannot be directly instantiated but can be used to create polymorphic queries.

The @Inheritance and @Discriminator annotations are used to specify the inheritance from an entity superclass. The @MappedSuperclass annotation is used to designate a nonentity superclass and captures state and mapping information that is common to multiple entity classes. Such a class has no separate table defined for it, so the mappings will only apply to its subclasses. An entity may inherit from a superclass that provides inheritance of behavior only. Such a class does not contain any persistent state.

The relationships between different entities are defined using @OneToOne, @OneToMany, @ManyToOne, and @ManyToMany annotation on the corresponding field of the referencing entity. A unidirectional relationship requires the owning side to specify the annotation. A bidirectional relationship also requires the nonowning side to refer to its owning side by use of the mappedBy element of the OneToOne, OneToMany, or ManyToMany annotation.

The FetchType.EAGER annotation may be specified on an entity to eagerly load the data from the database. The FetchType.LAZY annotation may be specified as a hint that the data should be fetched lazily when it is first accessed.

The entities may display a collection of elements and entity relationships as java.util.Map collections. The map key may be the primary key or a persistent field or property of the entity. @MapKey is used to specify the key for the association. For example, all the Courses by a Student can be modeled as:

public class Student {
  @MapKey
  private Map<Integer, Course> courses;
  //. . .
}

In this code, specifying @MapKey on the Map indicates that the map key is the primary key as well.

The map key can be a basic type, an embeddable class, or an entity. If a persistent field or property other than the primary key is used as a map key, then it is expected to have a uniqueness constraint associated with it. In this case, @MapKeyColumn is used to specify the mapping for the key column of the map:

public class Student {
  @MapKeyColumn(name="year")
  private Map<Integer, Course> courses;
  //. . .
}

In this code, Map represents all the Courses taken by a Student in a year. If the name element is not specified, it defaults to the concatenation of the following: the name of the referencing relationship field or property, “_,” and “KEY.” In this case, the default name will be COURSES_KEY.

@MapKeyClass can be used to specify the map key for the association. If the value is an entity, then @OneToMany and @ManyToMany may be used to specify the mapping:

public class Student {
  @OneToMany
  @MapKeyClass(PhoneType.class)
  private Map<PhoneType, Phone> phones;
  //. . .
}

@MapKeyClass and @MapKey are mutually exclusive.

If the value is a basic type or embeddable class, then @ElementCollection is used to specify the mapping.

Persistence Unit, Persistence Context, and Entity Manager

An entity is managed within a persistence context. Each entity has a unique instance for any persistent entity identity within the context. Within the persistence context, the entity instances and their lifecycles are managed by the entity manager. The entity manager may be container-managed or application-managed.

A container-managed entity manager is obtained by the application directly through dependency injection or from JNDI:

@PersistenceContext
EntityManager em;

The persistence context is propagated across multiple transactions for a container-managed entity manager, and the container is responsible for managing the lifecycle of the entity manager.

An application-managed entity manager is obtained by the application from an entity manager factory:

@PersistenceUnit
EntityManagerFactory emf;
//. . .
EntityManager em = emf.createEntityManager();

A new isolated persistence context is created when a new entity manager is requested, and the application is responsible for managing the lifecycle of the entity manager.

A container-managed entity manager is typically used in a Java EE environment. The application-managed entity manager is typically used in a Java SE environment and will not be discussed here.

An entity manager and persistence context are not required to be threadsafe. This requires an entity manager to be obtained from an entity manager factory in Java EE components that are not required to be threadsafe, such as servlets.

The entity managers, together with their configuration information, the set of entities managed by the entity managers, and metadata that specifies mapping of the classes to the database, are packaged together as a persistence unit. A persistence unit is defined by a persistence.xml and is contained within an ejb-jar, .war, .ear, or application-client JAR. Multiple persistence units may be defined within a persistence.xml.

A sample persistence.xml for the entity can be defined:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" 
    xmlns="http://java.sun.com/xml/ns/persistence" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation=
        "http://java.sun.com/xml/ns/persistence 
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="MyPU" transaction-type="JTA">
    <provider>
        org.eclipse.persistence.jpa.PersistenceProvider
    </provider>
        <jta-data-source>jdbc/sample</jta-data-source>
    <exclude-unlisted-classes>
       false
    </exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" 
       value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>

In this code:

  • Persistence unit’s name is "MyPU".

  • transaction-type attribute’s value of JTA signifies that a JTA data source is provided.

  • <provider> element is optional and specifies the name of the persistence provider.

  • jta-data-source element defines the global JNDI name of the JTA data source defined in the container. In a Java EE environment, this ensures that all the database configuration information, such as host, port, username, and password, are specified in the container, and just the JTA data source name is used in the application.

  • Explicit list of entity classes to be managed can be specified using multiple class elements, or all the entities may be included (as above) by specifying the exclude-unlisted-classes element.

  • properties element is used to specify both standard and vendor-specific properties. In this case, the eclipselink.ddl-generation property is specified and the property value indicates to generate the tables using the mappings defined in the entity class. The following standard properties may be specified: javax.persistence.jdbc.driver, javax.persistence.jdbc.url, javax.persistence.jdbc.user, javax.persistence.jdbc.password.

By default, a container-managed persistence context is scoped to a single transaction, and entities are detached at the end of a transaction. For stateful session beans, the persistence context may be marked to span multiple transactions and is called extended persistence context. The entities stay managed across multiple transactions in this case. An extended persistence context can be created:

@PersistenceContext(type=PersistenceContextType.EXTENDED)
EntityManager em;

Create, Read, Update, and Delete Entities

An entity goes through create, read, update, and delete (CRUD) operations during its lifecycle. A create operation means a new entity is created and persisted in the database. A read operation means querying for an entity from the database based upon a selection criteria. An update operation means updating the state of an existing entity in the database. And a delete operation means removing an entity from the database. Typically, an entity is created once, read and updated a few times, and deleted once.

The JPA specification allows the following ways to perform the CRUD operations:

Java Persistence Query Language (JPQL)

The Java Persistence Query Language is a string-based typed query language used to define queries over entities and their persistent state. The query language uses an SQL-like syntax and uses the abstract persistence schema of entities as its data model. This portable query language syntax is translated into SQL queries that are executed over the database schema where the entities are mapped. The EntityManager.createNamedXXX methods are used to create the JPQL statements. The query statements can be used to select, update, or delete rows from the database.

Criteria API

The Criteria API is an object-based, type-safe API and operates on a metamodel of the entities. Typically, the static metamodel classes are generated using an annotation processor, and model the persistent state and relationships of the entities. The javax.persistence.criteria and javax.persistence.metamodel APIs are used to create the strongly typed queries. The Criteria API allows only querying the entities.

Native SQL statement

Create a native SQL query specific to a database. @SQLResultSetMapping is used to specify the mapping of the result of a native SQL query. The EntityManager.createNativeXXX methods are used to create native queries.

A new entity can be persisted in the database using an entity manager:

Student student = new Student();
student.setId(1234);
//. . .
em.persist(student);

In this code, em is an entity manager obtained as explained earlier. The entity is persisted to the database at transaction commit.

A simple JPQL statement to query all the Student entities and retrieve the results looks like:

em.createNamedQuery("SELECT s FROM Student s").
   getResultList();

@NamedQuery and @NamedQueries are used to define a mapping between a static JPQL query statement and a symbolic name. This follows the “Don’t Repeat Yourself” design pattern and allows you to centralize the JPQL statements:

@NamedQuery(
  name="findStudent"
  value="SELECT s FROM Student s WHERE p.grade = :grade")
//. . .
Query query = em.createNamedQuery("findStudent");
List<Student> list = (List<Student>)query
                          .setParameter("grade", "4")
                          .getResultList();

This code will query the database for all the students in grade 4 and return the result as List<Student>.

The usual WHERE, GROUP BY, HAVING, and ORDER BY clauses may be specified in the JPQL statements to restrict the results returned by the query. Other SQL keywords such as JOIN and DISTINCT and functions like ABS, MIN, SIZE, SUM, and TRIM are also permitted. The KEY, VALUE, and ENTRY operators may be applied where map-valued associations or collections are returned.

The return type of the query result list may be specified:

TypedQuery<Student> query = em.createNamedQuery(
                                   "findStudent", 
                                   Student.class);
List<Student> list = query
                          .setParameter("grade", "4")
                          .getResultList();

Typically a persistence provider will precompile the static named queries. A dynamic JPQL query may be defined by directly passing the query string to the corresponding createQuery methods:

TypedQuery<Student> query = em.createQuery(
                          "SELECT s FROM Student s", 
                          Student.class);

The query string is dynamically constructed in this case.

JPA also allows dynamic queries to be constructed using a type-safe Criteria API. Here is sample code that explains how to use the Criteria API to query the list of Students:

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery criteria = builder.createQuery
                         (Student.class);

Root<Student> root = criteria.from(Student.class);
criteria.select(root);
TypedQuery<Student> query = em.createQuery(criteria);
List<Student> list = query.getResultList();

The static @NamedQuery may be more appropriate for simple use cases. In a complex query where SELECT, FROM, WHERE, and other clauses are defined at runtime, the dynamic JPQL may be more error prone, typically because of string concatenation. The type-safe Criteria API offers a more robust way of dealing with such queries. All the clauses can be easily specified in a type-safe manner, providing the advantages of compile-time validation of queries.

The JPA2 metamodel classes capture the metamodel of the persistent state and relationships of the managed classes of a persistence unit. This abstract persistence schema is then used to author the type-safe queries using the Criteria API. The canonical metamodel classes can be generated statically using an annotation processor following the rules defined by the specification. The good thing is that no extra configuration is required to generate these metamodel classes.

To update an existing entity, you need to first find it, change the fields, and call the EntityManager.merge method:

Student student = (Student)query
                          .setParameter("id", "1234")
                          .getSingleResult();
//. . .
student.setGrade("5");
em.merge(student);

The entity may be updated using JPQL:

Query query = em.createQuery("UPDATE Student s" 
+ "SET s.grade = :grade WHERE s.id = :id");
query.setParameter("grade", "5");
query.setParameter("id", "1234");
query.executeUpdate();

To remove an existing entity, you need to find it and then call the EntityManager.remove method:

Student student = em.find(Student.class, 1234);
em.remove(student);

The entity may be deleted using JPQL:

Query query = em.createQuery("DELETE FROM Student s" 
+ "WHERE s.id = :id");
query.setParameter("id", "1234");
query.executeUpdate();

Removing an entity removes the corresponding record from the underlying datastore as well.

Validating the Entities

Bean Validation 1.0 is a new specification in the Java EE 6 platform that allows you to specify validation metadata on JavaBeans. For JPA, all managed classes (entities, managed superclasses, and embeddable classes) may be configured to include Bean Validation constraints. These constraints are then enforced when the entity is persisted, updated, or removed in the database. Bean Validation has some predefined constraints like @Min, @Max, @Pattern, and @Size. A custom constraint can be easily defined by using the mechanisms defined in the Bean Validation specification and explained in this book.

The automatic validation is achieved by delegating validating to the Bean Validation implementation in the pre-persist, pre-update, and pre-remove lifecycle callback methods. Alternatively, the validation can also be achieved by the application by calling the Validator.validate method upon an instance of a managed class. The lifecycle event validation only occurs when a Bean Validation provider exists in the runtime.

The Student entity with validation constraints can be defined as:

@Entity 
public class Student implements Serializable {
  @NotNull
  @Id private int id;

  @Size(max=30)
  private String name;

  @Size(min=2, max=5)
  private String grade;

  //. . . 
}

This ensures that the id field is never null, the size of the name field is at most 30 characters with a default minimum of 0, and the size of the grade field is a minimum of 2 characters and a maximum of 5. With these constraints, attempting to add the following Student to the database will throw a ConstraintViolationException, as the grade field must be at least 2 characters long:

Student student = new Student();
student.setId(1234);
student.setName("Joe Smith");
student.setGrade("1");
em.persist(student);

Embeddable attributes are validated only if the Valid annotation has been specified on them. So the updated Address class will look like:

@Embeddable
@Valid
public class Address {
  @Size(max=20)
  private String street;

  @Size(max=20)
  private String city;

  @Size(max=20)
  private String zip;
  //. . .
}

By default, the validation of entity beans is automatically turned on. The behavior can be controlled using the validation-mode element in the persistence.xml file. This element can take the values AUTO, CALLBACK, or NONE. If the entity manager is created using Persistence.createEntityManager, the validation mode can be specified using the javax.persistence.validation.mode property.

By default, all the entities in a web application are in the Default validation group. This ensures that constraints are enforced when a new entity is inserted or updated, while no validation takes place by default if an entity is deleted. This default behavior can be overridden by specifying the target groups using the following validation properties in persistence.xml:

  • javax.persistence.validation.group.pre-persist

  • javax.persistence.validation.group.pre-update

  • javax.persistence.validation.group.pre-remove

A new validation group can be defined by declaring a new interface:

public interface MyGroup { }

A field in the Student entity can be targeted to this validation group:

@Entity
public class Student implements Serializable {
  @Id @NotNull int id;

  @AssertTrue(groups=MyGroup.class)
  private boolean canBeDeleted;
    
}

And persistence.xml needs to have the following property defined:

//. . .
<property
   name="javax.persistence.validation.group.pre-remove" 
   value="org.sample.MyGroup"/>

Transactions and Locking

The EntityManager.persist, .merge, .remove, and .refresh methods must be invoked within a transaction context when an entity manager with a transaction-scoped persistence context is used. The transactions are controlled either through JTA or through the use of the resource-local EntityTransaction API. A container-managed entity manager must use JTA and is the typical way of having transactional behavior in a Java EE container. A resource-local entity manager is typically used in a Java SE environment.

A transaction for a JTA entity manager is started and committed external to the entity manager:

@Stateless
public class StudentSessionBean {
  @PersistenceContext
  EntityManager em;

  public void addStudent(Student student) {
    em.persist(student);
  }
}

In this Enterprise JavaBean, a JTA transaction is started before the addStudent method and committed after the method is completed. The transaction is automatically rolled back if an exception is thrown in the method.

The resource-local EntityTransaction API can be used:

EntityManagerFactory emf = 
    Persistence.createEntityManagerFactory("student");
em.getTransaction().begin();
Student student = new Student();
//. . .
em.persist(student);
em.getTransaction().commit();
em.close();
emf.close();

The transaction may be rolled back using the EntityTransaction.rollback method.

In addition to transactions, an entity may be locked when the transaction is active. By default, optimistic concurrency control is assumed. The @Version attribute on an entity’s field is used by the persistence provider to perform optimistic locking. A pessimistic lock is configured using PessimisticLockScope and LockModeType enums. In addition, the javax.persistence.lock.scope and javax.persistence.lock.timeout properties may be used to configure pessimistic locking.

Caching

JPA provides two levels of caching. The entities are cached by the entity manager at the first level in the persistence context. The entity manager guarantees that within a single persistence context, for any particular database row, there will only be one object instance. However, the same entity could be managed in another transaction, so appropriate locking should be used as explained above.

Second-level caching by the persistence provider can be enabled by the value of the shared-cache-mode element in persistence.xml. This element can have the values defined in Table 4-1.

Table 4-1. shared-cache-mode values in persistence.xml

ValueDescription
ALLAll entities and entity-related state are cached.
NONENo entities or entity-related state is cached.
ENABLE_SELECTIVEOnly cache entities marked with @Cacheable(true).
DISABLE_SELECTIVEOnly cache entities that are not marked @Cacheable(false).
UNSPECIFIEDPersistence provider specific defaults apply.

The exact value can be specified:

<shared-cache-element>ALL</shared-cache-element>

This allows entity state to be shared across multiple persistence contexts.

The Cache interface can be used to interface with the second-level cache as well. This interface can be obtained from EntityManagerFactory. It can be used to check whether a particular entity exists in the cache or invalidate a particular entity, an entire class, or the entire cache:

@PersistenceUnit
EntityMangagerFactory emf;

public void myMethod() {
    //. . .
    Cache cache = emf.getCache();
    boolean inCache = cache.contains(Student.class, 1234);
    //. . . 
}

A specific entity can be cleared:

cache.evict(Student.class, 1234);

All entities of a class can be invalidated:

cache.evict(Student.class);

And the complete cache can be invalidated as:

cache.evictAll();

A standard set of query hints are also available to allow refreshing or bypassing the cache. The query hints are specified as javax.persistence.cache.retrieveMode and javax.persistence.cache.storeMode properties on the Query object. The first property is used to specify the behavior when data is retrieved by the find methods and by queries. The second property is used to specify the behavior when data is read from the database and committed into the database:

Query query = em.createQuery("SELECT s FROM Student s");
query.setHint("javax.persistence.cache.storeMode", 
              CacheStoreMode.BYPASS);

The property values are defined on CacheRetrieveMode and CacheStoreMode enums and explained in Table 4-2.

Table 4-2. CacheStoreMode and CacheRetrieveMode values

Cache query hintDescription
CacheStoreMode.BYPASSDon’t insert into cache.
CacheStoreMode.REFRESHInsert/update entity data into cache when read from database and when committed into database.
CacheStoreMode.USEInsert/update entity data into cache when read from database and when committed into database; this is the default behavior.
CacheRetrieveMode.BYPASSBypass the cache: get data directly from the database.
CacheRetrieveMode.USERead entity data from the cache; this is the default behavior.

Get Java EE 6 Pocket Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.