Making JPA great again
Réda Housni Alaoui
- Coding Architect @Cosium
- OSS Contributor
- Github: reda-alaoui
Cosium
- SaaS editor, LIR, Health sector, International
- Engineers company, strong technical skills
- Java, Web, TypeScript, PostgreSQL, Linux, 3D, hardware,...
- Software factory: IntelliJ, Maven, Git, Jenkins, Jira, SonaQube, Archiva... Gerrit and Vet!
We're hiring
How to
- Tackle JPA verbosity
- Eliminate useless joins
- Avoid N+1 query issue
JPA
- Java Persistence API
- Version 1.0 released in 2006
- Implemented by Object-relational mapping libs:
- Hibernate
- EclipseLink
- OpenJPA
- ...
Our JPA playground
JPA |
SQL (Postgres) |
@Entity
public class Author {
@Id
private long id;
private String name;
}
|
create table Author (
id bigserial primary key,
name varchar
);
|
@Entity
public class Book {
@Id
private long id;
private String title;
// Association
@ManyToOne
private Author author;
}
|
create table Book (
id bigserial primary key,
title varchar,
author_id bigint references author(id) not null
);
|
JPA semantic is cumbersome
// Let's print all books titles
// Begin JPA plumbing
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Book.class);
entityManager
.createQuery(cq)
// End JPA plumbing
.getResultList()
.forEach(Book::printTitle);
// And we did not yet introduce pagination
Spring Data to the rescue !
[Spring Data] makes it easy to use data access technologies, relational and non-relational databases,
map-reduce frameworks, and cloud-based data services
Spring Data
- Supports JPA via Spring Data JPA project
- Centered around the concept of repository
- A repository is a facade over a data source
Introducing the BookRepository
public interface BookRepository extends CrudRepository<Book, Long> {}
bookRepository
.findAll()
.forEach(Book::printTitle);
// Pretty neat :)
Actual query |
|
Expected query |
select
book.id,
book.title,
author.name
from book
join author
on author.id = book.author_id;
|
!=
|
select
book.id,
book.title
from book;
|
JPA associations fetch type
-
Possible values:
- @ManyToOne defaults to EAGER fetching
@Entity
public class Book {
// ...
@ManyToOne(/* default to fetch = EAGER */)
private Author author;
// ...
}
Lazy loading Book's Author
@Entity
public class Book {
// id + title omitted
@ManyToOne(fetch = LAZY)
private Author author;
}
bookRepository
.findAll()
.forEach(Book::printTitle);
Actual query |
|
Expected query |
select
book.id,
book.title
from book;
|
==
|
select
book.id,
book.title
from book;
|
N+1 query issue
bookRepository
.findAll()
.forEach(book -> {
book.printTitle();
book.printAuthorName();
});
Actual query |
|
Expected query |
select book.id,
book.title,
book.author_id
from book;
select author.name
from author
where author.id = 1;
select author.name
from author
where author.id = 2;
|
!=
|
select
book.id,
book.title
author.name
from book
join author
on book.author_id = author.id;
|
We need to select fetching behaviour at runtime
JPA EntityGraph to the rescue !
- Introduced in JPA 2.1
- Runtime fetch type
- Describes a graph of the entities that will be eagerly fetched
- Can be defined programmatically or by annotation
book-with-author entity graph
@Entity
@NamedEntityGraphs({
@NamedEntityGraph(
name = "book-with-author",
attributeNodes = {
@NamedAttributeNode("author")
}
)
})
public class Book {
// id + title omitted
@ManyToOne(fetch = LAZY)
private Author author;
}
Applying book-with-author to the query
public interface BookRepository extends CrudRepository<Book, Long> {
@EntityGraph("book-with-author") // Can't be passed dynamically
List<Book> findAll();
}
bookRepository.findAll().forEach(book -> {
book.printTitle();
book.printAuthorName();
});
Actual query |
|
Expected query |
select
book.id,
book.title
author.name
from book
join author --...;
|
==
|
select
book.id,
book.title
author.name
from book
join author --...;
|
Spring Data neutralizes EntityGraph
spring-data-jpa-entity-graph to the rescue !
// The new BookRepository
public interface BookRepository
extends EntityGraphCrudRepository<Book, Long> {}
Printing all books titles and authors names one last time
bookRepository
.findAll(EntityGraphs.named("book-with-author"))
.forEach(book -> {
book.printTitle();
book.printAuthorName();
});
Actual query |
|
Expected query |
select
book.id,
book.title
author.name
from book
join author --...;
|
==
|
select
book.id,
book.title
author.name
from book
join author --...;
|
Printing all books titles one last time
bookRepository
.findAll()
.forEach(Book::printTitle);
Actual query |
|
Expected query |
select
book.id,
book.title
from book;
|
==
|
select
book.id,
book.title
from book;
|