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
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
Cardinality | RDBMS |
---|---|
@OneToOne | EAGER |
@ManyToOne | EAGER |
@OneToMany | LAZY |
@ManyToMany | LAZY |
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
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”
query.setParameter(“cost”, unitCost);
Binding DateParameter
Query.setParameter(“pubDate”, LocalDateTime.now());
Binding Legacy Date Parameters
Query.setParameter(“pubDate”,new Date(),TIMESTAMP);
Pagination
Query.setMaxResults(10);
Named Queries
Are defined as annotations in the entity class
@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.
@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.
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.
@NamedEntityGraph(
name = "Post.full",
attributeNodes = {
@NamedAttributeNode(value = "writer", subgraph = "writerDetails")
},
subgraphs = {
@NamedSubgraph(
name = "writerDetails",
attributeNodes = @NamedAttributeNode("contact")
)
}
)
Using
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.
@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.