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.
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 theStudent
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
Fetch
Type.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 Course
s 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
Course
s 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.
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 ofJTA
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 theexclude-unlist
ed-classes
element.properties
element is used to specify both standard and vendor-specific properties. In this case, theeclipselink.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;
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.createNamed
methods are used to create the JPQL statements. The query statements can be used to select, update, or delete rows from the database.XXX
- 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
andjavax.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. TheEntityManager.createNative
methods are used to create native queries.XXX
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 Student
s:
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.
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"/>
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.
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
Value | Description |
---|---|
ALL | All entities and entity-related state are cached. |
NONE | No entities or entity-related state is cached. |
ENABLE_SELECTIVE | Only cache entities marked with
@Cacheable(true) . |
DISABLE_SELECTIVE | Only cache entities that are not marked @Cacheable(false) . |
UNSPECIFIED | Persistence 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.persist
ence.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
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.