Jpa Mappings
Jpa Mappings
jim stafford
1.1. Goals
The student will learn:
• to identify the underlying JPA constructs that are the basis of Spring Data JPA Repositories
1.2. Objectives
At the conclusion of this lecture and related exercises, the student will be able to:
5. map a simple @Entity class to the database using JPA mapping annotations
1
Chapter 2. Java Persistence API
The Java Persistence API (JPA) is an object/relational mapping (ORM) layer that sits between the
application code and JDBC and is the basis for Spring Data JPA Repositories. JPA permits the
application to primarily interact with plain old Java (POJO) business objects and a few standard
persistence interfaces from JPA to fully manage our objects in the database. JPA works off
convention and customized by annotations primarily on the POJO, called an Entity. JPA offers a rich
set of capabilities that would take us many chapters and weeks to cover. I will just cover the very
basic setup and @Entity mapping at this point.
• Hibernate was one of the original implementations and the default implementation within
Spring Boot
• DataNucleus
The last release of the "Java Persistence API" (officially known as "JPA") was 2.2 in ~2017. You will
never see a version of the "Java Persistence API" newer than 2.2.x. "Jakarta Persistence"
(unofficially known as "JPA") released a clone of the "Java Persistence API" version 2.2.x under the
jakarta Maven package naming to help in the transition. "Jakarta Persistence" 3.0 was released in
~2020 with just package renaming from javax.persistence to jakarta.persistence. Enhancements
were not added until JPA 3.1 in ~2022. That means the feature set remained idle for close to 5 years
during the transition.
• jakarta.persistence:jakarta.persistence-api 2.2.x
2
◦ used javax.persistence Java package naming
◦ released in 2018
• jakarta.persistence:jakarta.persistence-api 3.x
◦ released in 2020
• Spring Boot 3
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
The following shows a subset of the dependencies brought into the application by declaring a
dependency on the JPA starter.
+- org.springframework.boot:spring-boot-starter-data-jpa:jar:3.3.2:compile
| +- org.springframework.boot:spring-boot-starter-jdbc:jar:3.3.2:compile
...
| | \- org.springframework:spring-jdbc:jar:6.1.11:compile
| +- org.hibernate.orm:hibernate-core:jar:6.5.2.Final:compile ②
| | +- jakarta.persistence:jakarta.persistence-api:jar:3.1.0:compile ①
3
| | +- jakarta.transaction:jakarta.transaction-api:jar:2.0.1:compile
...
② a JPA provider module is required to access extensions and for runtime implementation of the
standard JPA constructs
+- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.7.0:compile
| +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.7.0:compile
| | \- org.springframework:spring-jdbc:jar:5.3.20:compile
| +- jakarta.persistence:jakarta.persistence-api:jar:2.2.3:compile ①
| +- org.hibernate:hibernate-core:jar:5.6.9.Final:compile ②
...
② a JPA provider module is required to access extensions and for runtime implementation of the
standard JPA constructs
From these dependencies, we can define and inject various JPA beans.
Spring Boot JPA automatically configures a default persistence unit and other related beans when
the @EnableJpaRepositories annotation is provided. @EntityScan is used to identify packages for
@Entities to include in the persistence unit.
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@SpringBootApplication
@EnableJpaRepositories ①
// Class<?>[] basePackageClasses() default {};
// String repositoryImplementationPostfix() default "Impl";
// ...(many more configurations)
@EntityScan ②
// Class<?>[] basePackageClasses() default {};
public class JPASongsApp {
① triggers and configures scanning for JPA Repositories (the topic of the next lecture)
4
By default, this configuration will scan packages below the class annotated with the @EntityScan
annotation. We can override that default using the attributes of the @EntityScan annotation.
spring.datasource.url=jdbc:h2:mem:songs
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=secret
Spring Boot will automatically enable runtime schema generation for in-memory database URLs.
We can also explicitly enable runtime schema generation using the following Hibernate property.
spring.jpa.hibernate.ddl-auto=create
The following configuration snippet instructs the JPA provider to generate a create and drop
commands into the same drop_create.sql file based on the metadata discovered within the
Persistence Context. Hibernate has the additional features to allow for formatting and line
termination specification.
spring.jpa.properties.jakarta.persistence.schema-generation.scripts.action=drop-and-
5
create
spring.jpa.properties.jakarta.persistence.schema-generation.create-source=metadata
spring.jpa.properties.jakarta.persistence.schema-generation.scripts.create-
target=target/generated-sources/ddl/drop_create.sql
spring.jpa.properties.jakarta.persistence.schema-generation.scripts.drop-
target=target/generated-sources/ddl/drop_create.sql
spring.jpa.properties.hibernate.hbm2ddl.delimiter=; ①
spring.jpa.properties.hibernate.format_sql=true ②
[1]
action can have values of none, create, drop-and-create, and drop
$ cat target/generated-sources/ddl/drop_create.sql ③
6
id integer not null,
released date,
artist varchar(255),
title varchar(255),
primary key (id)
);
① starting Postgres DB
The first two property settings both functionally produce logging of SQL statements.
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=DEBUG
• The spring.jpa.show-sql property controls raw text output, lacking any extra clutter. This is a
good choice if you want to see the SQL command and do not need a timestamp or threadId.
spring.jpa.show-sql=true
• The logging.level.org.hibernate.SQL property controls SQL logging passed through the logging
framework. This is a good choice if you are looking at a busy log with many concurrent threads.
The threadId provides you with the ability to filter the associated SQL commands. The
timestamps provide a basic ability to judge performance timing.
logging.level.org.hibernate.SQL=debug
7
from reposongs_song s1_0 where s1_0.id=?
An additional property can be defined to show the mapping of data to/from the database rows. The
logging.level.org.hibernate.orm.jdbc.bind property controls whether Hibernate prints the value of
binding parameters (e.g., "?") input to or returning from SQL commands. It is quite helpful if you
are not getting the field data you expect.
logging.level.org.hibernate.orm.jdbc.bind=TRACE
The following snippet shows the result information of the activated debug. We can see the
individual SQL commands issued to the database as well as the parameter values used in the call
and extracted from the response. The extra logging properties have been redacted from the output.
logging.level.org.hibernate.orm.jdbc.bind=trace
The following shows an example of using a String package specification to a root package to scan
for @Entity classes.
@EntityScan example
import org.springframework.boot.autoconfigure.domain.EntityScan;
...
@EntityScan(value={"info.ejava.examples.db.repo.jpa.songs.bo"})
The following example, instead uses a Java class to express a package to scan. We are using a
8
specific @Entity class in this case, but some may define an interface simply to help mark the
package and use that instead. The advantage of using a Java class/interface is that it will work
better when refactoring.
import info.ejava.examples.db.repo.jpa.songs.bo.Song;
...
@EntityScan(basePackageClasses = {Song.class})
import jakarta.persistence.EntityManagerFactory;
...
@Autowired
private EntityManagerFactory emf;
It is rare that you will ever need an EntityManagerFactory and could be a sign that you may not
understand yet what it is meant for versus an injected EntityManager. EntityManagerFactory is used
to create a custom persistence context and transaction. The caller is responsible for beginning and
commiting the transaction — just like with JDBC. This sometimes is useful to create a well-scoped
transaction within a transaction. It is a manual process, outside the management of Spring.
9
Persistance Context/EntityManager Injection Example
import jakarta.persistence.EntityManager;
...
@Autowired
private EntityManager em;
Injected EntityManagers reference the same Persistence Context when called within the same
thread. That means that a Song loaded by one client with ID=1 will be available to sibling code when
using ID=1.
Use/Inject EntityManagers
Normal application code that creates, gets, updates, and deletes @Entity data
should use an injected EntityManager and allow the transaction management to
occur at a higher level.
[1] "JavaEE: The JavaEE Tutorial, Database Schema Creation", Oracle, JavaEE 7
10
Chapter 3. JPA Entity
A JPA @Entity is a class mapped to the database that primarily represents a row in a table. The
following snippet is the example Song class we have already manually mapped to the REPOSONGS_SONG
database table using manually written schema and JDBC/SQL commands in a previous lecture. To
make the class an @Entity, we must:
• identify one or more columns to represent the primary key using the @Id annotation
@jakarta.persistence.Entity ①
@Getter
@AllArgsConstructor
@NoArgsConstructor ②
public class Song {
@jakarta.persistence.Id ③ ④
private int id;
@Setter
private String title;
@Setter
private String artist;
@Setter
private java.time.LocalDate released;
}
③ class must have one or more fields designated as the primary key
④ annotations can be on the field or property and the choice for @Id determines the default
• have the entity name "Song" (important when expressing JPA queries; ex. select s from Song s)
11
• be mapped to the SONG table to match the entity name
• have columns id integer, title varchar, artist varchar, and released (date)
• use id as its primary key and manage that using a provider-default mechanism
• supply a table name that matches our intended schema (i.e., select * from REPOSONGS_SONG vs
select * from SONG)
• select which primary key mechanism is appropriate for our use. JPA 3 no longer allows an
unnamed generator for a SEQUENCE.
• identify which properties are optional, part of the initial INSERT, and UPDATE -able
• supply other parameters useful for schema generation (e.g., String length)
@Entity
@Table(name="REPOSONGS_SONG") ①
@NoArgsConstructor
...
@SequenceGenerator(name="REPOSONGS_SONG_SEQUENCE", allocationSize = 50)③
public class Song {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "REPOSONGS_SONG_SEQUENCE")②
@Column(name = "ID") ④
private int id;
@Column(name="TITLE", length=255, nullable=true, insertable=true, updatable=true
)⑤
private String title;
private String artist;
private LocalDate released;
}
② overriding the default primary key mechanism with SEQUENCE. JPA 3 no longer permits an
unnamed sequence generator.
12
Properties like length and nullable are only used during optional JPA schema
generation and are not used at runtime.
13
Chapter 4. Basic JPA CRUD Commands
JPA provides an API for implementing persistence to the database through manipulation of @Entity
instances and calls to the EntityManager.
@Component
@RequiredArgsConstructor
public class JpaSongDAO {
private final EntityManager em; ①
② stage for insertion into database; all instance changes managed from this point forward
A database INSERT SQL command will be queued to the database as a result of a successful call and
the @Entity instance will be in a managed state.
In the managed state, any changes to the @Entity will result in either the changes be part of:
14
• a future UPDATE SQL command if changes occur after the INSERT
Updates are issued during the next JPA session "flush". JPA session flushes can be triggered
manually or automatically prior to or no later than the next commit.
If the instance is not yet loaded into the Persistence Context, SELECT SQL command(s) will be issued
to the database to obtain the persisted state. The following snippet shows the SQL generated by
Hibernate to fetch the state from the database to realize the @Entity instance within the JVM.
Hibernate: select
s1_0.id,
s1_0.artist,
s1_0.released,
s1_0.title
from reposongs_song s1_0
where s1_0.id=?
From that point forward, the state will be returned from the Persistence Context without the need
to get the state from the database.
Find by identity is resolved first through the local cache and second through a
database query. Once the @Entity is managed, all explicit find() by identity calls
will be resolved without a database query. That will not be true when using a
query (discussed next) with caller-provided criteria.
• JPA Query Language (officially abbreviated "JPQL"; often called "JPAQL") - a very SQL-like String
syntax expressed in terms of @Entity classes and relationship constructs
• Criteria Language - a type-safe, Java-centric syntax that avoids String parsing and makes
dynamic query building more efficient than query string concatenation and parsing
15
The following snippet shows an example of executing a JPQL Query.
The following shows how our JPQL snippet mapped to the raw SQL issued to the database. Notice
that our Song @Entity reference was mapped to the REPOSONGS_SONG database table.
Hibernate: select
count(s1_0.id)
from reposongs_song s1_0
where s1_0.id=?
The following snippet shows an example of flushing the contents of the cache after changing the
state of a managed @Entity instance.
Whether it was explicitly issued or triggered internally by the JPA provider, the following snippet
shows the resulting UPDATE SQL call to change the state of the database to match the Persistence
16
Context.
The following snippet shows how a managed @Entity instance can be used to initiate the removal
from the database.
The following snippet shows how the remove command was mapped to a SQL DELETE command.
I only bring these up because you may come across class examples where I am calling flush() and
clear() in the middle of a demonstration. This is purposely mimicking a fresh Persistence Context
17
within scope of a single transaction.
em.clear();
em.detach(song);
Calling clear() or detach() will evict all managed entities or targeted managed @Entity from the
Persistence Context — loosing any in-progress and future modifications. In the case of returning
redacted @Entities — this may be exactly what you want (you don’t want the redactions to remove
data from the database).
18
Chapter 5. Transactions
All commands require some type of transaction when interacting with the database. The
transaction can be activated and terminated at varying levels of scope integrating one or more
commands into a single transaction.
@Autowired
private EntityManager em;
...
@Test
void transaction_missing() {
//given - an instance
Song song = mapper.map(dtoFactory.make());
This next example annotates the calling @Test method with the @Transactional annotation to cause a
transaction to be active for the three (3) contained EntityManager calls.
import org.springframework.transaction.annotation.Transactional;
19
...
@Test
@Transactional ①
void transaction_present_in_caller() {
//given - an instance
Song song = mapper.map(dtoFactory.make());
//then
then(em.find(Song.class, song.getId())).isNotNull(); ②
} ③
① @Transactional triggers an Aspect to activate a transaction for the Persistence Context operating
within the current thread
③ the end of the method will trigger the transaction-initiating Aspect to commit (or rollback) the
transaction it activated
Nested calls annotated with @Transactional, by default, will continue the current transaction.
tx = em.getTransaction();
try {
tx.begin();
//call code ②
} catch (RuntimeException ex) {
tx.setRollbackOnly(); ①
} finally { ②
if (tx.getRollbackOnly()) {
tx.rollback();
} else {
tx.commit();
}
}
20
5.4. Activating Transactions in @Components
We can alternatively push the demarcation of the transaction boundary down to the @Component
methods.
The snippet below shows a DAO @Component that designates each of its methods being
@Transactional. This has the benefit of knowing that each of the calls to EntityManager methods will
have the required transaction in place. Whether a new one is the right one is a later topic.
@Transactional Component
@Component
@RequiredArgsConstructor
@Transactional ①
public class JpaSongDAO {
private final EntityManager em;
@Test
void transaction_present_in_component() {
//given - an instance
Song song = mapper.map(dtoFactory.make());
//then
then(jpaDao.findById(song.getId())).isNotNull(); ②
}
21
① INSERT is completed in a separate transaction
①
Hibernate: insert into reposongs_song (artist,released,title,id) values (?,?,?,?)
②
Hibernate: select
s1_0.id,
s1_0.artist,
s1_0.released,
s1_0.title
from reposongs_song s1_0
where s1_0.id=?
① transaction 1
② transaction 2
• Unmanaged/Detached entities have either never been or no longer associated with a Persistence
Context
The following snippet shows an example of where a follow-on method fails because the
EntityManager requires that @Entity be currently managed. However, the end of the create()
transaction made it detached.
Unmanaged @Entity
@Test
void transaction_common_needed() {
//given a persisted instance
Song song = mapper.map(dtoFactory.make());
jpaDao.create(song); //song is detached at this point ①
22
① the first transaction starts and ends at this call
② the EntityManager.remove operates in a separate transaction with a detached @Entity from the
previous transaction
The following text shows the error message thrown by the EntityManager.remove call when a
detached entity is passed in to be deleted.
Shared Transaction
@Test
@Transactional ①
void transaction_common_present() {
//given a persisted instance
Song song = mapper.map(dtoFactory.make());
jpaDao.create(song); //song is detached at this point ②
//then
then(jpaDao.findById(song.getId())).isNull(); ②
}
① @Transactional at the calling method level is shared across all lower-level calls
② Each DAO call is executed in the same transaction and the @Entity can still be managed across all
calls
23
transaction
◦ NOT_SUPPORTED - nothing within the called method will honor transaction semantics
◦ NESTED - may not be supported, but permits nested transactions to complete before
returning to calling transaction
• readOnly - defaults to false; hints to JPA provider that entities can be immediately detached
24
Chapter 6. Summary
In this module, we learned:
• to configure a JPA project, include project dependencies, and required application properties
25