Spring Data JPA

adriencaubel.fr

Introduction

  • Nous avons étudié les concepts suivants
    • La notion @Entity
    • Les associations @OneToMany ... et mappedBy (bidirectionnelle)
    • L'héritage @Inheritance
    • L'optimisation des lectures (Fetch.LAZY / Fetch.EAGER)
    • L'API Criteria
    • Les projections

Spring Data JPA c'est quoi ?

Implementing a data access layer for an application can be quite cumbersome. Too much boilerplate code has to be written to execute the simplest queries. Add things like pagination, auditing, and other often-needed options, and you end up lost.

Spring Data JPA est une abstraction qui nous permet d'aller plus vite :

  • Pagination et tri intégrés
  • Requêtes avancées via JPQL, SQL natif, Criteria API et Specifications
  • Gestion des transactions automatique

Spring Data JPA c'est quoi ?

alt text

Plusieurs type de base de données

https://docs.spring.io/spring-data/jpa/reference/jpa.html

Dépendance Maven

Dans un projet Maven, pour utiliser Spring Data pour une base de données relationnelles avec JPA, il faut déclarer la dépendance suivante

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>3.4.4</version>
</dependency>

L'interface Repository

L'interface Repository

The central interface in the Spring Data repository abstraction is Repository.

L'interface Repository

Les implémentations de cette interfaces nous permettent de pouvoir accéder facilement aux opérations de CRUD et de Pagination par exemple. Évitant ainsi à tous les DAO de recoder ce même boilerplate (comme dans le TP5).

public interface CrudRepository<T, ID> extends Repository<T, ID> {
  <S extends T> S save(S entity);      
  Optional<T> findById(ID primaryKey); 
  Iterable<T> findAll();               
  long count();                        
  void delete(T entity);               
  boolean existsById(ID primaryKey);   
}
public interface PagingAndSortingRepository<T, ID>  {
 
  Iterable<T> findAll(Sort sort);
 
  Page<T> findAll(Pageable pageable);
}

Ainsi JPARepository qui est la dernière implémentation bénéficie :

  • des méthodes génériques CRUD
  • de la pagination

Créer notre Repository

public interface StudentRepository extends JPARepository<Student, Long> {
    // pas besoin de coder les opérations de CRUD
}

Nous n’avons pas besoin de coder les opérations de CRUD, elles sont hérités.

Derived Query Methods

C'est quoi ?

Spring Data JPA agit comme une couche au-dessus de JPA et vous offre deux façons de définir votre requête :

  • Vous pouvez laisser Spring Data JPA dériver la requête à partir du nom d’une méthode.
  • Vous pouvez définir votre propre requête JPQL ou native en utilisant une annotation @Query

Depuis nom d'une méthode

Supposons une classe Produit qui dispose du champs nom et puis nous définissons la méthode suivante

public interface ProduitRepository extends JpaRepository<Produit, Long> {
    List<Produit> findByNom(String nom);
}

Spring Data JPA implémente automatiquement cette méthode sans aucune écriture SQL manuelle en

SELECT * FROM produit WHERE nom = :nom

Respecter les conventions

Nous devons juste prêter garde à respecter les conventions définies par Spring Data JPA.
alt text

Depuis @Query

public interface UserRepository extends JpaRepository<User, Long> {
 
  @Query("select u from User u where u.emailAddress = ?1")
  User findByEmailAddress(String emailAddress);
}

Pagination

@GetMapping
public Page<Product> getProducts(
  @PageableDefault(size = 10, sort = "name", direction = Sort.Direction.ASC) 
  Pageable pageable) {
    return productRepository.findAll(pageable);
}
  • Dans le contrôleur on configure une pagination par défaut
  • Via l’interface PagingAndSortingRepository nous bénéficions directement de Page<T> findAll(Pageable pageable)

Specifications

public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

}
  • JpaSpecificationExecutor nous donne la méthode List<T> findAll(Specification<T> spec)

Et l'interface Specification est la suivante

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, 
                        CriteriaQuery<?> query,
                        CriteriaBuilder builder);
}

Avec seulement JPA

LocalDate today = new LocalDate();

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);

Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);
Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2);

query.where(builder.and(hasBirthday, isLongTermCustomer));

em.createQuery(query.select(root)).getResultList();

Avec Spring Data JPA

public CustomerSpecifications {

  public static Specification<Customer> customerHasBirthday() {
    return new Specification<Customer> {
      public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
        return cb.equal(root.get(Customer_.birthday), today);
      }
    };
  }

  public static Specification<Customer> isLongTermCustomer() {
    return new Specification<Customer> {
      public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
        return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
      }
    };
  }
}

Utilisation dans les services

customerRepository.findAll(CustomerSpecifications.hasBirthday());
customerRepository.findAll(CustomerSpecifications.isLongTermCustomer());

Et on peut les combiner

customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));