Skip to content

Java Persistence API

JPA (Java Persistence API) is a standard specification in Java for managing relational data in Java applications. It's an ORM (Object-Relational Mapping) for Java.

It’s basically a set of rules and interfaces that tell you how to map Java objects (entities) to database tables, and how to perform CRUD operations (Create, Read, Update, Delete) using Java code instead of SQL.

It's an standard interface without the implementation. For an implementation you need aprovider like Hibernate, Open JPA or EclipseLink

Further information

Link: Java Persistence API (JPA)

Entity Manager

Manager to create, update, delete or finding of entities

Persistence Context

Set von managed entities

Persistence Unit

Configuration of context in persistence.xml

Persistence Context

  • Persistence unit defines an EntityFactory.
  • Can exist multiple EntityFactories.
  • Contains managed entities.
  • Each entity with a defined ID can only exist once in a persistence unit.
  • A type of first-level cache.

Properties

Javax.persistence properties are independent of the used database.

Persisting an entity

A transaction is required to create, modify, or delete changes. EntityManager.merge attaches an unattached entity so that changes can be made to it. No transaction is required.

Entity Mapping

@javax.persistence.Entity – Defines an entity @javax.persistence.Id – Defines the unique Id of the entity(table)

  • Requires No-arg constructor.
  • Concrete or abstract class.
  • Interfaces, or enums do not work
  • All attributes get persited

For a minimal approach, you only need:

  • Mapping via class name-> table name
  • Propertyname->column name
  • Customizing Mapping

@Table(name = „T_AUTHOR“) (on class) – If the tablename does not match the convention

@GeneratedValue (id property) - Id gets generated automatically. (e.g. strategy= GenerationType.AUTO)

@Column(name = „first_name“, length =50) – Specific columnname. Length max 50 (default 255)

@Temporal(TemporalType.TIMESTAMP) – Overrides the default behaviour (e.g. only time required)
Private Date dateOfBirth;

@Transient – Property gets not persisted.

@Enumerated(EnumType.ORDINAL) – Use orinalnumber
Private Language language

Annotations can be added to getter or setter too.

Mapping Metadata

Mapping can also be defined in XML. It has higher priority than annotations.

Must be referenced in persistence.xml.

Can be used, for example, if there are different persistence units in different environments.

Relations

JPA recognizes how the mapping works by default. However, it can be overridden with annotations.

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany

@JoinTable() – If a mapping table is to be used

Cascading

@OneToMany(cascade= PERSIST,REMOVE,MERGE,ALL)

Fetching

  • Eager - Immediately
  • Lazy – Only when accessing

Default

CardinalityRDBMS
@OneToOneEAGER
@ManyToOneEAGER
@OneToManyLAZY
@ManyToManyLAZY

Can be overwritten:
@OneToMany(fetch = EAGER)

@Embedded Another object is inserted into this object. It is persisted in the same table. The class is annotated with @Embeddable and inserted into the entity with @Embedded.

Inheritance

There are three types of inheritance in JPA

Single Table per class (Default)

All attributes and all derivatives are stored in one table. A discriminator column is also added.

  • Simple
  • Good for a small number of attributes

Joined Subclass

Each subclass has its own table, which is joined.

  • The deeper the hierarchy, the more joins are required.
  • Can get slow.

Table-per-concrete-class

Each subclass has a table including all root attributes.

  • Redundant data storage.
  • IDs in subclasses and derivatives should match.

Configuration

@Inheritance(startegy = SINGLE_TABLE/JOIN/TABLE_PER_CLASS)
Annotation is added to the root entity

Discriminator

Default: DTYPE
@DiscriminatorColumn(name = „DISC“, discriminatorType=CHAR) on root entity

On subclass
@DiscriminatorValue(“B”)

Inheritance Hierarchy

Root class can be

  • Abstract – abstract keyword
  • Non-Entity class: No @Entity annotation -> transient
  • @MappedSupperclass
  • MappedSuperclass
  • No persistent root class (table)
  • Root properties appear in every subtable
  • @MappedSuperclass annotation on the root class

Querying entities

JPQL - Java Persistence query languag

  • Is object-oriented
  • Database independent
  • Translated to SQL -> JDBC call
  • Refers to objects, not tables

Syntax

SELECT <select clause>
FROM <from clause>
[WHERE <where clause>]
[ORDER BY <order by clause>]
[GROUP BY <group by clause>]
[HAVING <having clause>]

Queries

Queries are executed via the EntityManager. Queries and named queries can be executed via this.

Query-Interface

  • getSingleResult
  • getResultList
  • setMaxResults
  • setFirstReult
  • setParameter

Dynamic Queries

Typed queries still require the class to be passed as a parameter.
The statement is generated dynamically as a string.
They are slower than named queries.

BindingParameters

Parameters in the statement “?1”,”?2”,”?3”… Then set the parameters in the query.

Example

java
TypedQuery<Book> query=em.createQuery(“SELECT b FROM Book b WHERE b.unitCost >?1 AND b.nbOfPage < ?2”, Book.class);
query.setParameter(1, unitCost);
query.setParameter(2, nbOfPage);
List<Book> books = query.getResultList();

NamedBased Parameters

Instead of numbering “?1” “:cost”

java
query.setParameter(“cost”, unitCost);

Binding DateParameter

java
Query.setParameter(“pubDate”, LocalDateTime.now());

Binding Legacy Date Parameters

java
Query.setParameter(“pubDate”,new Date(),TIMESTAMP);

Pagination

java
Query.setMaxResults(10);

Named Queries

Are defined as annotations in the entity class

java
@NamedQuery(name = „ExpensiveBooks“, query=
„SELECT b FROM Book b
WHERE b.unitCost > 29 AND b.nbOfPage < 700”)

TypedQuery<Book> query= em.createNamedQuery(“ExpensiveBooks”, Book.class);

The name must be unique for all entities. Tips:

  • Name with the entity prefix "Book.ExpensiveBooks"
  • Define a constant for the name

Entity Lifecycle

  • As soon as an entity is loaded or persisted, it is "managed."
  • "Managed" entities are automatically synchronized until the EntityManager is closed or the
  • From Detached->managed via em.merge()

Callback Methods

  • Annotations on any entity methods
  • Public, private, protected, or package
  • Cannot be static or final
  • Throw unchecked exceptions (no checked exceptions possible)

Entity Listener

@EntityListener-Annotation

For reusing pre/post methods

The listener class is a POJO

Any callback annotations are set on a method in the listener.

This method requires the entity as a parameter.

The EntityListener is registered on the entity that will use the listener using an annotation.

java
@Entity
@EntityListeners({
AgeCalculationListener.class,
ValidationListener.class
})
Public class Book

Default Listeners

  • Applied to all listeners
  • Must be defined in XML
  • Entities can be excluded on the class using @ExcludeDefaultListeners.
  • The callback annotations are set again on the desired method. An object is passed as a parameter.

Additonal Annotations

Some additional useful annotations for JPA

EntityGraph

Is used to define fetch plans — specifically, to control which related entities are loaded eagerly as part of a single query.

  • Relationships like @OneToMany, @ManyToOne, etc., are lazy-loaded by default.
  • This can lead to multiple extra queries being fired off for each related entity (N+1 problem).
  • @EntityGraph tells JPA what to fetch eagerly in one go.
  • Cleaner and more flexible than writing custom JPQL with joins.
java
public interface PostRepository extends JpaRepository<Post, Long> {

    @EntityGraph(attributePaths = {"writer"})
    List<Post> findAll();

}

This will generate a single SQL query that does a JOIN on writer and retrieves everything together.

NamedEntityGraph / NamedSubgraph

Similar to @EntityGraph. Instead of specifying the fetch plan on a repository method, you predefine it on the entity class and then refer to it by name when needed. Useful when you want to reuse the same fetch structure in multiple places. You can even go deeper using @NamedSubgraph for nested relationships

  • Define reusable fetch plans on entity classes.
  • Pair with @EntityGraph(value = "...") in the repository to use.
  • Keeps code clean, DRY, and flexible.
java
@NamedEntityGraph(
  name = "Post.full",
  attributeNodes = {
    @NamedAttributeNode(value = "writer", subgraph = "writerDetails")
  },
  subgraphs = {
    @NamedSubgraph(
      name = "writerDetails",
      attributeNodes = @NamedAttributeNode("contact")
    )
  }
)

Using

java
public interface PostRepository extends JpaRepository<Post, Long> {

    @EntityGraph(value = "Post.author", type = EntityGraph.EntityGraphType.LOAD)
    List<Post> findAll();

}

Batch fetching

This is a fetching strategy to fetch more data in LAZY loading.

java
@Entity
@BatchSize(size = 10)
public class Author {
    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
    private List<Book> books;
}

Performance optimization

  • Use indexing — This will be very useful for frequently used data.
  • Use the WHERE clause to filter data early.
  • Use INNER JOIN instead of LEFT JOIN — for performance if no missing data is allowed.
  • Use EXISTS instead of IN — It's more efficient than IN
  • Avoid unnecessary GROUP BY — It uses more resources of the database to process data.
  • Use LIMIT — Limit the result with LIMIT for large amount of data.
  • Use EXPLAIN — Shows the query execution plan. Used to identify issues.

Contact: M_Bergmann AT gmx.at