Java Persistence API (JPA)

Java Persistence API (JPA)

Elastic Path uses the Apache OpenJPA implementation of the Java Persistence API (JPA) to store and retrieve information from its databases.

JPA provides the following features:

Supports a POJO (Plain Old Java Object) persistence model.

Rich inheritance support.

Support for annotations or XML based mapping files.

Supports pluggable persistence providers.

Is supported by Spring.

Supports native SQL queries as well as the Java Persistence Query Language (JPQL).

Entities and Identities

Entity Characteristics

A JPA Entity is a class which represents persistent data stored in a database.

In most cases a concrete or an abstract class can be an Entity. Entity classes support mixed inheritance: the superclass and/or subclass of an Entity does not have to be an Entity.

Any application-defined object with the following characteristics can be an Entity:

It can be made persistent,

It has a persistent identity (i.e. a unique key),

It is not a primitive, a primitive wrapper of build-in object.

Entity Identification

All Entities must have a persistent id (i.e. a database primary key). This is known as the entity identity (or persistence identity). JPA supports the following entity identities:

Single field identity (like our long uidPk field).

Composite primary keys (provided by an Identity class),

The Entity identity must be defined on the root entity or mapped superclass of the hierarchy. Elastic Path's domain model defines the uidPk on the Persistable interface. Persistable classes need to implement the getUidPk() method with the @Id annotation and setUidPk() method.

JPA supports several strategies for primary key generation:

Auto - leave the decision up to the JPA implementation.

Identity - the database will assign an identity value on insert.

Sequence - use a datastore sequence to generate a value.

Table - use a sequence table to generate a field value.

Elastic Path's domain model uses the Table generator. Table works with all databases, and offers the best overall performance. Elastic Path's persistence classes defines the generator table JPA_GENERATED_KEYS with the columns ID and LAST_VALUE, a name and primary key value matching the name of the database table that the class will be persisted to. JPA uses this table to keep track of the last identifier value used to ensure the next one used is unique. You can configure the number of values to allocate in memory for each trip to the database (the default is 50). Allocating values in memory allows the JPA runtime to avoid accessing the database for every sequence request.

The following table gives a brief summary of OpenJPA properties and their purposes:

Property

Description

ConnectionFactoryName

Tells OpenJPA the JDNI location of the
Datasource file.

Log

Tells OpenJPA which logging library to use for issuing log messages.

ConnectionFactoryProperties

This is used to format the log output of SQL debug messages to a more readable form.

EagerFetchMode

Set to "parallel", the most efficient eager fetch mode for Elastic
Path's domain model.

SubclassFetchMode

Set to "parallel" , the most efficient eager
fetch mode for Elastic Path's domain model.

DetachState

Tells OpenJPA to take advantage of a
detached state field to make the attach process more efficient. This
field is added by the enhancer and is not visible to your application.
The loaded value tells OpenJPA to detach all fields and
relations that are already loaded, but not to include unloaded fields in
the detached graph. Setting DetachedStateField to true
tells OpenJPA to use a non-transient detached state field so that objects
crossing serialization barriers can still be attached efficiently. This
requires, however, that your client tier have the enhanced versions of
your classes and the OpenJPA libraries.

Multithreaded

Tells OpenJPA that persistent instances and OpenJPA components other
than the EntityManagerFactory will be accessed by
multiple threads at once.

AutoDetatch

Tells OpenJPA to detach entities on transaction commits, instances are
read non-transactionally, or when the OpenJPA interface closes.

DBDictionary

Provides additional database parameters.
The batchLimit parameter groups multiple insert and
delete operations into a single SQL query to optimize performance. The
SupportsSubselect and
SupportsCorrelatedSubselect parameters instruct
OpenJPA to support subselects in queries, but not correlated
subselects.

TransactionIsolation

Ensures the database transaction
isolation level is set to READ_COMMITTED.

DataCache

Enables OpenJPA data caching.

DataCacheManager

Tells OpenJPA to use Ehcache as its data
cache provider.

RemoteCommitProvider

Sets OpenJPA to use a single JVM.

QueryCompilationCache

Tells OpenJPA to cache parsed query strings. As a result, most queries are only parsed once in OpenJPA, and cached thereafter.

FinderCache

Tells OpenJPA not to use the FinderCache. The FinderCache currently has a bug which causes data corruption issues and must be disabled.

QueryCache

Tells OpenJPA to cache the object ids returned by query executions.

QuerySQLCache

Set to false so that OpenJPA does not
cache the SQL queries it generates.

Compatibility

Tells OpenJPA to convert positional
parameters to named parameters to support mixed queries, as well as to
use OpenJPA 1 behaviour for Detached state serialization.

MetaDataRepository

Tells OpenJPA to preload the metadata
repository as a runtime performance optimization.

Class Enhancement

OpenJPA uses an enhancer to provide optimal runtime performance, flexible lazy loading,
and efficient immediate dirty tracking. The enhancer post-processes the bytecode
generated by your Java compiler for your domain classes, adding the necessary fields and
methods to implement the required persistence features. This bytecode modification
perfectly preserves the line numbers in stack traces and is compatible with Java
debuggers.

Annotations

Overview

Most annotations are placed directly into the class files of the objects to be
persisted. This makes it easy to maintain consistency between the objects and their
persistence definition: there is no need to go find the "mapping file". Annotations
are generally quicker to type than XML mapping definitions and IDEs support
annotations including syntax checking and auto-completion. The exception to the rule
is annotations that define named queries (see below).

Annotations can be at the field level, where the persistence layer writes directly to
the field, or at the property level, where the persistence layer uses the getter and
setter methods. You cannot mix field and property level annotations within the same
class hierarchy.

As a standard, our object model uses property level annotations. The annotations in
the class files are written above the getter method for each property.

Tip:Read the full documentation

The OpenJPA User's Guide contains comprehensive documentation on all of the annotations available along with what parameters they take. You will need to refer to this often when doing your own mappings.

Simple annotations

Here's a list of some of the more common simple annotations:

Annotation

Purpose

@Entity

Denotes an entity class.

@Table(name = "tablename")

Denotes that the entity should persist to the
tablename table in the schema.

@Id

Denotes a simple identity field.

@Basic

Denotes a simple value that should be persisted as-is.

@Column(name = "columnname")

Denotes that the value should be persisted to the
columnname column in the schema.

@Temporal

Defines how to use Date fields at the JDBC
level.

@Enumerated

Controls how Enum fields are mapped.

@Transient

Specifies that a field is non-persistent.

Note that properties in a class defined as an Entity that do not have any annotations
may still be treated as persistable depending on the type. To avoid any confusion,
ensure you annotate all property getters including @Basic and
@Transient properties.

Relationship management

The following lists of some of the annotations commonly used for mapping
relationships in JPA:

Annotation

Purpose

@OneToOne

When an entity A references a single entity
B, and no other A's can
reference the same B, there is a one to one
relationship between A and B
.

@OneToMany

When an entity A references multiple
B entities, and no two A's
reference the same B, there is a one to many
relationship from A to B .

@ManyToOne

When an entity A references a single entity
B, and other A's might also
reference the same B, there is a many to one
relationship from A to B.

@ManyToMany

When an entity A references multiple
B entities, and other A's
might reference some of the same B's, there is a
many to many relationship between A and
B .

@Embedded

Embedded fields are mapped as part of the datastore record of the
declaring entity. A class can be marked as embeddable by adding the
@Embeddable annotation to the class.

Inheritance

JPA provides two annotations for supporting inheritance:

Annotation

Purpose

@MappedSuperclass

A non-entity class that can define persistent state and mapping
information for entity subclasses. Mapped superclasses are usually
abstract. Unlike true entities, you cannot query a mapped
superclass, pass a mapped superclass instance to any
EntityManager or Query
methods, or declare a persistent relation with a mapped superclass
target.

@Inheritance

Indicates the inheritance the strategy for a hierarchy of entities.
There are 3 strategies to chose from:

SINGLE_TABLE - maps all classes in the
hierarchy to the base class' table.

JOINED - uses a different table for each class
in the hierarchy. Each table only includes state declared in its
class. Thus to load a subclass instance, the JPA implementation
must read from the subclass table as well as the table of each
ancestor class, up to the base entity class.

TABLE_PER_CLASS - uses a different table for
each class in the hierarchy. Unlike the JOINED
strategy, however, each table includes all state for an instance
of the corresponding class. To load a subclass instance, the JPA
implementation must only read from the subclass table; it does
not need to join to superclass tables.

OpenJPA-specific annotations

OpenJPA provides some useful annotations in addition to those provided by the JPA
specification. Many of these are used by Elastic Path due to the complexity of the
domain object model. Commonly used OpenJPA-specific are as follows:

Annotation

Purpose

@ForeignKey

Defines a foreign key. This annotation is present when there is a
database foreign key, and allows OpenJPA to calculate the correct
order to issue database statements without violating key
constraints.

@Dependent

Marks a direct relation as dependent. This means the referenced
object is deleted whenever the owning object is deleted, or
whenever the relation is severed by nulling or resetting the owning
field.

@ElementJoinColumn

Array, collection, or map element join column.

@ElementForeignKey

Defines a foreign key constraint to the columns of a collection
element.

@ElementDependent

Marks the entity elements of a collection, array, or map field as
dependent. This means the referenced object is deleted whenever the
owning object is deleted, or whenever the relation is severed by
nulling or resetting the owning field.

@Factory

Contains the name of a method that will be invoked to instantiate the field from the external form stored in the database.

@Externalizer

Sets the name of a method that will be invoked to convert the field
into its external form for database storage.

@DataCache

If @DataCache(enabled = false) is present, then
the entity will not be cached. All other combinations will enable
caching, assuming that openjpa.DataCache = true in
the jpa-persistence.xml file of the entity.

Example

Below is an example of an annotated class file which shows many of the annotation
types in use:

Java Persistence Query Language (JPQL)

JPQL overview

JPQL executes over the abstract persistence schema (the entities you've created) defined by your persistence unit, rather than over the database like traditional SQL. JPQL supports a SQL-like syntax, and dynamic and static (named) queries.

Multiple fields may be specified in separate join fetch declarations. You may want to use other join types depending on the data, e.g. left outer join fetch.

Named queries

JPA supports named queries. While named queries can be defined in the annotations of a class – usually the class in which the named query is most relevant – they can also be defined external to the implementation class source files in XML files. In this case, each Java package has its own named queries XML file in the following location: META-INF/ <packagename>-orm.xml.. This is useful for keeping all the named queries specific to a package in the same place while allowing easier extensibility, so that queries can be modified without requiring the application to be recompiled.

Persistence coding

Entity Manager

One of the key interfaces in the Java Persistence API is the EntityManager. This is similar to Hibernate's Session interface, providing services for CRUD operations (persist, remove, find, merge etc), creating Query instances and interfacing to the Transaction API.

Spring integration

Spring provides JPA integration, implementing JPA Container responsibilities. It includes beans like LocalEntityManagerFactoryBean which make it easy to configure the Entity Manager Factory through Spring configuration files, and SharedEntityManagerBean which provides a shared EntityManager reference.

Core Engine Persistence model integration

Elastic Path integrates JPA with its core persistence model. In general, Elastic Path uses Spring Inversion of Control and Dependency Injection when possible.

Elastic Path provides the following classes for persistence. You can find these in the com.elasticpath.persistence.openjpa.impl package of ep-persistence-openjpa.

Class

Implements core interface

Purpose

How it works

JpaPersistenceEngineImpl

PersistenceEngine

The main persistence engine class. This is what you would set as the persistenceEngine property in your Spring configuration files for the service beans.

This class uses a shared EntityManager provided by the Spring configuration.

JpaSessionImpl

PersistenceSession

This is used by code such as the Import Manager which uses the PersistenceSession interface to run queries on the session in a transaction under it's control.

This class uses the EntityManager methods directly.

JpaQueryImpl

Query

This is used by code such as the Import Manager which uses the Query interface to manipulate queries within it's own transaction.

This class uses the javax.persistence.Query methods directly.

JpaTransactionImpl

Transaction

This is used by code such as the Import Manager which uses the Transaction interface control it's own transactions.

This class uses the EntityTransaction methods directly.

The majority of the time you'll just use the persistence-api PersistenceEngine interface, and configure Spring to inject the JPA implementation, so your code generally doesn't need to know anything about JPA.

Eager vs. Lazy loading

You can define fields to load lazily or eagerly the annotations. Be aware that using eager loading for everything can seriously affect performance and is unnecessary in cases when you don't need every linked object.

Lazy loading will defer loading of a field's associated entity until it is accessed, but it must be accessed while the connection is still open. Trying to access that field after the connection is closed will throw a IllegalStateException.

Best practice is to define all (non-basic) fields as lazy loading (the default for @OneToMany and @ManyToMany mappings) and then define named queries with fetch join to eagerly load the relation fields. When you need just the basic data you use a query without the fetch join and when you need everything you use the fetch join query.

For example, the following named query should be used when you want basic Category information:

Fetch Groups

OpenJPA provides the concept of Fetch Groups - sets of fields that load together. They can be used to pool together associated fields in order to provide performance improvements over standard data fetching. Specifying fetch groups allows for tuning of lazy loading and eager fetching behavior. Fetch groups are used in our system for objects that are indexed by our Search Server to define exactly what fields need to be loaded by the indexing jobs.

Problems and Workarounds

JPA is designed to be simple and easy to use, so it is less complex than Hibernate. Sometimes this means that mappings that were possible in Hibernate are not possible to do in JPA without some modifications to the object model.

Locale and Currency

JPA does not support the Locale and Currency types so some special OpenJPA annotations are required when using these datatypes.

For example, here are annotated getters for Locale and Currency fields:

This tells JPA the externalizer method to use to convert the field into its external form for database storage (eg getCurrencyCode()) for Currency), and to the full class and method to instantiate the field from the external form stored in the database.

Extensible Enums

Like Locale and Currency, extensible enums use the Externalizer and Factory
annotations for conversion:

The above is an old snippet from ShoppingCartImpl which has been re-implemented as a JPA Entity. Before this class was annotated as a JPA Entity, the constructor wasn't being called until the ElasticPathImpl bean factory added a bean for ShoppingCartImpl, so the call to getElasticPath() would always work. However, once this class was added to the list of persistence entities, the constructor was being called beforeElasticPathImpl was being instantiated, so the call to getElasticPath() would fail.

The work around is to put any code in the constructor that relies on other objects in a more appropriate place. In this particular case the code to get the viewHistory bean was moved into the getViewHistory method of ShoppingCartImpl.

Useful tools

The OpenJPA Maven plugin includes some useful tools that can help with using JPA.

Mapping Tool

The mapping tool can generate a schema from your object model. It is very useful for checking if your annotations map the object(s) to the current schema, or for generating the SQL if you add a new object.

You can run the tool by calling the following Maven command in the ep-core directory:

mvn -Dopenjpa.ConnectionDriverName=com.mysql.jdbc.Driver openjpa:sql

The generated file, database.sql, can be found in ep-core/target.

Logging

Detailed logging for JPA can be enabled by adding the following to your
log4j.properties files:

Extensibility

MergingPersistenceUnitPostProcessor - Allows multiple
jpa-persistence.xml files defining the same persistence unit
name to exist in your project's classpath. The list of mapping files and managed
classes are merged at runtime. For details on adding references to an extension
project's jpa-persistence.xml, see Add references to the Persistence Unit