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:
    • EAGER
    • LAZY
  • @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> {}