JPA. Introduction to Java Persistence API

Introduction

The Java Persistence API (JPA) is responsible for performing CRUD operations and is built on top of Hibernate. JPA has been designed to replace EJB 2.1 entity beans and has started as a part of the EJB 3 specification. JPA is now outside of an EJB container and has its own specification, but it’s still part of the EJB specification, since a compliant EJB 3 container has to provide a JPA implementation, which integrates into the transaction handling of the container.

Entities

Entities are used in data query methods to perform CRUD (Create, Retrieve, Update and Delete) operations.

Entities are basically POJO classes that represent a single table or multiple tables in a database and each entity instance represents a single row in a table.

You can define an entity as follows:

@Entity
@IdClass(FooPK.class)
@Table(name = "FOO")
public class Foo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Id
    @Basic(fetch = FetchType.EAGER)
    private String name;

    // getters and setters
    // equals, hashCode, toString...
}

Entity class requirements:

Other entity annotations are:

Entity Relationships

One to One Relationship

You can have a bidirectional one to one relationship between two tables with @OneToOne and @JoinColumn annotations.

On one side we will have:

@Entity
@Table(name = "BAR")
public class Bar {
    
    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long id;
    
    // other fields

    // fetch strategy is eager by default
    @OneToOne
    @JoinColumn(name = "bar_child_id", referencedColumnName = "id")
    private BarChild barChild;
    
    // getters, setters...
}

where name = bar_child_id matches the foreign key column name on the BAR table; referencedColumnName = id matches the BAR_CHILD column name.

On the other side we will have:

@Entity
@Table(name = "BAR_CHILD")
public class BarChild {
    
    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long id;

    // other fields

    @OneToOne(mappedBy = "barChild")
    private Bar bar;
}

One to Many and Many to One Relationships

We will use the @OneToMany annotation on a collection pointing to another class that does not have a relationship field, or it has a field that is not a collection and is annotated with @ManyToOne

@Entity
@Table(name = "FOO")
public class Foo {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    // other attributes
    
    // fetch strategy is eager by default
    @ManyToOne
    @JoinColumn(name = "bar_id", nullable = false)
    private Bar bar;
    
    // getters, setters...
}

where name = "bar_id" matches the column name of the foreign key in the database.

@Entity
@Table(name = "BAR")
public class Bar {
    
    @Id
    @GeneratedValue
    @Column(name = "id")
    private Long id;
    
    // other attributes
    
    // fetch strategy is lazy by default
    @OneToMany(mappedBy = "bar")
    private List<Foo> foos;
    
    // getters, setters...
}

Many to Many Relationship

A many-to-many relationship is joining two collections from two different entities. This requires a @ManyToMany annotation in both sides.

On one side we will have:

@Entity
@Table(name = "FOO")
public class Foo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;
    
    // other attributes

    // lazy fetch strategy by default
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "foo_child",
            joinColumns = @JoinColumn(name = "id"),
            inverseJoinColumns = @JoinColumn(name = "foo_id"))
    private List<FooChild> fooChildren;
    
    // getters, setters...

}

Foo will own the associations. "foo_child" is the join table name; @JoinColumn(name = "id") is the foreign key column of the Foo table; @JoinColumn(name = "foo_id") is the foreign key column of the FooChild table which is referencing the id column in the Foo table .

On the other side we will have:

@Entity
@Table(name = "FOO_CHILD")
public class FooChild {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", unique = true, nullable = false)
    private Long id;
    
    // other attributes

    // lazy fetch strategy by default
    @ManyToMany(mappedBy = "fooChildren", fetch = FetchType.EAGER)
    private List<Foo> foos;
    
    // getters, setters...
}

Field Bindings

@OneToMany and @ManyToMany has FetchType.LAZY as the default value for the annotation attribute fetch, however, @ManyToOne, @OneToOne use an eager strategy as default, FetchType.EAGER. When an eager strategy is used it could trigger an N+1 query issue.

The N+1 query issue could also happen even with FetchType.LAZY if you do something like:

for(Foo foo: foos) {
	System.out.println(foo.getFooChild().getName());
}

You can use a JOIN FETCH clause to fix the N+1 query issue.

The JOIN FETCH clause is JPA-specific. It tells the persistence provider to not only join two tables within a single query but to also initialize the association on the returned entity, so you will not need to do new queries and hit the database to retrieve the associated entities.

Cascade types

JPA provides cascade types to propagate an operation from a parent to a child entity.

Persistence Unit

A persistence unit is the configuration required by JPA and allows you to instantiate an entity manager. Persistence units are declared in a file META-INF/persistence.xml where you can configure things like the name of each persistence unit, managed classes included in your persistence unit, how classes are mapped to database tables, the datasource use to connect to the database… however, you can also configure a persistence unit programatically by implementing the PersistenceUnitInfo interface.

There are two transaction types:

You can define a container-managed persistence unit as follows:

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="MyPersistenceUnit" transaction-type="JTA">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>jdbc/jpa_example</jta-data-source>
        <class>com.sergiomartinrubio.jpa.javaee8.model.Foo</class>
        <class>com.sergiomartinrubio.jpa.javaee8.model.FooChild</class>
        <class>com.sergiomartinrubio.jpa.javaee8.model.Bar</class>
        <class>com.sergiomartinrubio.jpa.javaee8.model.BarChild</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="show_sql" value="true"/>
            <property name="hibernate.transaction.jta.platform"
                      value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform"/>
        </properties>
    </persistence-unit>
</persistence>

You need to select the jta-data-source by using the JNDI name defined in the web server.

hibernate.transaction.jta.platform might be required to interact with a JTA system. If you use Payara as the web server you will have to set it to SunOneJtaPlatform. Other JtaPlatform implementations can be found here.

Managing Entities

The EntityManager instance is responsible for managing entities and provide support for CRUD operations. Each EntityManager instance is associated with a persistence-context. The persistence-context holds the fetched data and acts as a cache. You will usually have a persistence-context per transaction. When the data is modified in the persistence-context the changes will be saved in the database either manually or automatically with COMMIT or AUTO flush strategies.

You can get an instance of the EntityManager from the EJB container, from outside a the EJB container or via a JNDI lookup.

An EntityManager can be used like this:

@Stateless
public class FooRepository {

    @PersistenceContext(unitName = "MyPersistenceUnit")
    private EntityManager entityManager;
    
    public List<Foo> findAll() {
    return entityManager.createNamedQuery("Foo.findAll", Foo.class)
                .getResultList();
    }

    public void save(Foo foo) {
		entityManager.persist(foo);
    }
}

In the previous example we got an instance of the EntityManger that is bound to the MyPersistenceUnit unit.

Entity States

Entities go through different states:

JPA Queries

JPA provides three types of queries:

  1. Java Persistence Query Language (JPQL)
  2. Criteria API Query
  3. Native Query
  JPQL Criteria API Query Native Query
Readability Similiar to SQL syntax Requires objects SQL syntax
Typesafe No Yes No
Performance Worst Intermidiate Best
Verbosity Intermidiate Worst Best
Portability Portable Portable Not Portable
Versatility Restricted Restricted Full DB potential

JPQL

JPQL queries are invoked by the EntityManager on the persistence context. There are two types of JPQL queries:

TypedQuery<Foo> typedQuery = entityManager
        .createQuery("SELECT f FROM Foo f WHERE f.id = :id", Foo.class);
@Entity
@Table(name = "FOO")
@NamedQuery(name = "Foo.findAll", query = "SELECT f FROM Foo f")
public class Foo {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;
}

​ Invoke NamedQuery:

Query namedQuery = entityManager.createNamedQuery("Foo.findAll");

​ You can declare multiple named queries with @NamedQueries annotation.

Criteria API Query

Criteria API queries can be complex and difficult to understand but give you some safety advantages.

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> criteriaQuery = criteriaBuilder.createQuery(Foo.class); // create query object
Root<Foo> from = criteriaQuery.from(Foo.class); // get query root

CriteriaQuery<Foo> select = criteriaQuery.select(from); // set result type
TypedQuery<Foo> typedQuery = entityManager.createQuery(select); // prepare query for execution
List<Foo> foos = typedQuery.getResultList(); // execute query

Native Query

Query nativeQuery = entityManager
        .createNativeQuery("SELECT * FROM foo WHERE id=:id", Foo.class);
nativeQuery.setParameter("id", "1");

Binding Parameters

You can bind parameters with : followed by the parameter name or ? followed by an index.

Examples