2. Définition
Spring Data est un projet Spring qui a pour objectif de simplifier l’interaction avec
différents systèmes de stockage de données : qu’il s’agisse d’une base de
données relationnelle, d’une base de données NoSQL, d’un système Big Data ou
encore d’une API Web.
Le principe de Spring Data est d’éviter aux développeurs de coder les accès à
ces systèmes. Pour cela, Spring Data utilise une convention de nommage des
méthodes d’accès pour exprimer la requête à réaliser.
2
3. Interrogation dans Spring Data JPA
Spring Data JPA fournit 3 mécanismes pour interroger les données dans une
base de données :
Méthodes de requête
Annotation @Query
Implémentation de référentiel personnalisé
3
4. Ajout de Spring Data JPA dans un projet Maven
Spring Data se compose d’un noyau central et de plusieurs sous modules dédiés
à un type de système de stockage et une technologies d’accès. 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>
<artifactd>spring-data-jpa</artifactd>
<version>spring-data-jpa</version>
</dependency>
4
5. Notion de repository
Spring Data s’organise autour de la notion de repository. Il fournit une interface marqueur
générique Repository<T, ID>. Le type T correspond au type de l’objet géré par le repository.
Le type ID correspond au type de l’objet qui représente la clé d’un objet.
Différents types d'interfaces de référentiel Spring Data et leurs fonctionnalités. Nous
aborderons:
- CrudRepository fournit des fonctions CRUD
- PagingAndSortingRepository fournit des méthodes pour effectuer la pagination
et trier les enregistrements
- JpaRepository fournit des méthodes liées à JPA telles que le vidage du
contexte de persistance et la suppression d'enregistrements dans un lot.
JpaRepository extends PagingAndSortingRepository extends CrudRepository extends Repository
5
7. Pour créer un repository, il suffit de créer une interface qui hérite d’une des
interfaces ci-dessus.
Package com.dao.repository;
Import org.springframework.data.jpa.repository.JpaRepository;
Public interface UserRepository extends JpaRepository<User, Long> { }
7
8. Si vous avez dans votre projet une interface héritant de Repository<T, ID> mais que vous
ne souhaitez pas que Spring Data génère de classe concrète, alors vous devez ajouter
l’annotation @NoRepositoryBean sur cette interface.
Cette annotation est utilisée pour éviter de créer des proxys de référentiel
Annotation @Modifying
L‘Annotation @Modifying est utilisée pour améliorer l' annotation @Query afin d'exécuter
non seulement des requêtes SELECT mais également des
requêtes INSERT , UPDATE , DELETE et même DDL .
@Modifying
@Query("delete User u where u.active = false")
int deleteDeactivatedUsers();
@Modifying
@Query(value = "alter table USERS.USERS add column deleted int(1) not null default 0",
nativeQuery = true)
void addDeletedColumn();
Astuce!
8
9. Gestion du contexte de persistance
Si notre requête de modification change les entités contenues dans le contexte
de persistance, ce contexte devient obsolète. Une façon de gérer cette
situation consiste à effacer le contexte de persistance . Ce faisant, nous nous
assurons que le contexte de persistance récupérera les entités de la base de
données la prochaine fois.
Cependant, nous n'avons pas à appeler explicitement la méthode clear
() sur EntityManager . Nous pouvons simplement utiliser
la propriété clearAutomatically de l' annotation @Modifying :
@Modifying(clearAutomatically = true)
9
10. De cette façon, nous nous assurons que le contexte de persistance est effacé
après l'exécution de notre requête.
Mais, que se passe-t-il si notre contexte de persistance contenait des
changements non vides? Par conséquent, l'effacer signifierait supprimer les
modifications non enregistrées. Heureusement, il existe une autre propriété de
l'annotation que nous pouvons utiliser - flushAutomatically :
@Modifying(flushAutomatically = true)
Maintenant, EntityManager est vidé avant l'exécution de notre requête.
10
12. Ajout de méthodes dans une interface de repository
L’interface JpaRepository<T, ID> déclare beaucoup de méthodes mais elles
suffisent rarement pour implémenter les fonctionnalités attendues d’une
application. Spring Data utilise une convention de nom pour générer
automatiquement le code sous-jacent et exécuter la requête. La requête est
déduite de la signature de la méthode (on parle de query methods).
La convention est la suivante : Spring Data JPA supprime du début de la
méthode les prefixes find, read, query, count and get et recherche la présence
du mot By pour marquer le début des critères de filtre. Chaque critère doit
correspondre à un paramètre de la méthode dans le même ordre.
12
13. Spring Data JPA générera une implémentation pour chaque méthode de ce repository.
Public interface UserRepository extends JpaRepository<User, Login>
User getByLogin(String login);
Pour la méthode getByLogin, l’implémentation sera de la forme :
Return entityManager.createQuery(" select u from User u where u.login = :login, User.class)
.setParameter("login" , login)
.getSingleResult();
13
14. Il est même possible de donner des critères sur des entités liées, Ainsi, si la classe
contient une association vers une entité
! Note
@Entity
Public class User {
@OneToOne
Private Adresse adresse; }
@Entity
Public class Adresse{
Private String city;
}
List<User> findByAdresseCity(String city);
14
15. Pour une description complète des règles de nommage existantes pour
les query methods, vous pouvez vous reporter à la documentation officielle.
https://docs.spring.io/spring-
data/jpa/docs/2.0.8.RELEASE/reference/html/#jpa.query-methods.query-
creation
15
16. Requêtes nommées JPA
Avec JPA, il est possible de définir des requêtes nommées grâce à
l’annotation @NamedQuery.
Spring Data JPA utilise une convention pour rechercher les requêtes nommées avec JPA.
La requête doit porter comme nom, le nom de l’entité suivi de . suivi du nom de la
méthode. Ainsi si on définit une requête nommée sur une entité User :
@Entity
@NamedQuery(name="User.findByLogin", query="select u from User u where u.login = :login")
public class User { }
Il faut ensuite déclarer la méthode dans le repository assigné à l’entité User :
Public interface UserRepository extends JpaRepository<User, Login>
User findByLogin(@Param("login") String login);
16
17. Remarquez la présence de l’annotation @Param qui permet d’associer le
paramètre de la méthode au paramètre de la requête nommée.
17
18. Utilisation de @Query
L’annotation @Query permet de préciser la requête directement sur la méthode elle-même,
son attribut value contient le JPQL ou SQL à exécuter, et par défaut la définition de requête utilise JPQL :
Public interface UserRepository extends JpaRepository<User, Login>
@Query("select u from User where u.login = :login")
User findByLogin(@Param("login« ) String login);
Pour des requêtes avec peu de paramètres, il est possible d’utiliser la notation pour désigner un paramètre
par un numéro d’ordre dans la requête. Cela évite un usage de l’annotation @Param :
Public interface UserRepository extends JpaRepository<User, Login>
@Query("select u from User where u.login = ?1", nativeQuery = true)
User findByLogin(@Param("login" ) String login);
! Note
18
19. Paramètres de requête indexés
@Query("SELECT u FROM User u WHERE u.status = ?1")
User findUserByStatus(Integer status);
@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")
User findUserByStatusAndName(Integer status, String name);
Paramètres nommés
@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")
User findUserByStatusAndNameNamedParams(
@Param("status") Integer status,
@Param("name") String name);
Paramètre de collecte
@Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(@Param("names") Collection<String> names);
19
20. Tri
userRepository.findAll(new Sort(Sort.Direction.ASC, "name"));
Le comportement par défaut de Spring Data JPA est de chercher la présence de
l’annotation @Query ou la présence d’une requête nommée JPA. S’il n’en existe
pas alors Spring Data JPA analyse la signature de la méthode pour essayer d’en
déduire la requête à exécuter.
L' annotation @Query a priorité sur les requêtes nommées, qui sont annotées
avec @NamedQuery ou définies dans un fichier orm.xml .
! Note
20
21. Implémentation des méthodes de repository
Il est parfois nécessaire de fournir une implémentation d’une ou de plusieurs
méthodes d’un repository.
Dans ce cas, il faut isoler les méthodes que l’on souhaite implémenter dans une
interface spécifique. Par exemple, on peut créer
l’interface UserCustomRepository :
Public interface UserCustomRepository {
Void doSomething(User u);
}
Public interface UserRepository extends UserCustomRepository, JpaRepository<User,
Login>
21
22. Comme Spring Data JPA détecte une interface parente qui n’hérite pas elle-
même de l’interface Repository<T, ID>, il recherche une classe Java portant le
même nom que l’interface avec le suffixe Impl dans le même package ou un sous-
package. Si une telle classe existe alors Spring Data JPA tente de créer un bean de
cette classe.
La classe d’implémentation ne doit pas porter de stéréotype Spring
comme @Component ou @Repository. Par contre, elle peut utiliser toutes les autres
annotations autorisées par le Spring Framework si le contexte d’application est
configuré correctement.
Le repository fonctionnera ainsi par délégation. Lorsque la
méthode UserRepository.doSomethingComplicated sera appelée, elle déléguera
le traitement à la méthode UserCustomRepositoryImpl.doSomethingComplicated.
! Note
22
23. Il est tout à fait possible de fournir une implémentation pour une méthode déclarée
dans l’interface JpaRepository<T, ID> ou une des interfaces parentes. Pour cela, il
suffit de déclarer dans l’interface d’implémentation une méthode avec la même
signature.
! Note
23
24. Spring Data JPA et paramètres Null
IS NULL Query
List<Customer> findByNameAndEmail(String name, String email);
Maintenant, si nous transmettons un e-mail nul , le JPQL généré inclura la condition IS
NULL : email is null
Évitez les paramètres nuls
Parfois, nous voulons ignorer certains paramètres et ne pas inclure leurs champs
correspondants dans la clause WHERE .
List<Customer> findByNameAndEmail(String name, String email);
Nous pouvons ajouter plus de méthodes ça vous dire on fait un test sur les
paramètres : if(name == null) { List<Customer> findByeMail(String email); }
eles if { (email == null) List<Customer> findByName(String name); }
else {List<Customer> findByNameAndEmail(String name, String email);}
24
25. Ignorer les paramètres nuls à l' aide de l' annotation @Query
Nous pouvons éviter de créer des méthodes supplémentaires en utilisant
l' annotation @Query et en ajoutant une petite complication à l'instruction JPQL:
@Query("SELECT c FROM Customer c WHERE (:name is null or c.name = :name) and
(:email is null" + " or c.email = :email)")
List<Customer> findCustomerByNameAndEmail(@Param("name") String name,
@Param("email") String email);
Peut-être qu'à l'avenir, nous pourrons spécifier comment
interpréter les paramètres nuls à l'aide de l' annotation @NullMeans . Notez que
c'est une fonctionnalité proposée en ce moment et est toujours à l'étude.
! Note
25
26. Appel de procédures stockées à partir de
référentiels Spring Data JPA
Il existe quatre façons équivalentes de procéder. Par exemple, nous pouvons utiliser le
nom de la procédure stockée directement comme nom de méthode:
Nous pouvons définir une méthode de procédure stockée à l'aide de
l' annotation @Procedure et mapper directement le nom de la procédure stockée.
@Procedure
int GET_TOTAL_CARS_BY_MODEL(String model);
Si nous voulons définir un nom de méthode différent, nous pouvons mettre le nom de
la procédure stockée comme élément de l' annotation @Procedure :
@Procedure("GET_TOTAL_CARS_BY_MODEL")
int getTotalCarsByModel(String model);
26
27. Nous pouvons également utiliser l' attribut procedureName pour mapper le nom de la
procédure stockée :
@Procedure(procedureName = "GET_TOTAL_CARS_BY_MODEL")
int getTotalCarsByModel(String model);
De même, nous pouvons utiliser l' attribut value pour mapper le nom de la procédure
stockée :
@Procedure(value = "GET_TOTAL_CARS_BY_MODEL")
int getTotalCarsByModel(String model);
Nous pouvons également utiliser l' annotation @NamedStoredProcedureQuery pour
définir une procédure stockée dans la classe d'entité:
@Entity
@NamedStoredProcedureQuery(name = "Car.getTotalCardsbyModelEntity",
procedureName = "GET_TOTAL_CARS_BY_MODEL", parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "model_in", type =
String.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "count_out", type =
Integer.class)})
public class Car {
// class definition}
Ensuite, nous pouvons référencer cette définition dans le référentiel :
@Procedure(name = "Car.getTotalCardsbyModelEntity")
int getTotalCarsByModelEntiy(@Param("model_in") String model);
27
28. Nous utilisons l' attribut name pour référencer la procédure stockée définie dans la classe
d'entité. Pour la méthode du référentiel, nous utilisons @Param pour faire correspondre le
paramètre d'entrée de la procédure stockée. En outre, nous adaptons le paramètre de
sortie de la procédure stockée à la valeur de retour de la méthode du référentiel.
Nous pouvons également appeler une procédure stockée directement avec
l' annotation @Query :
@Query(value = "CALL FIND_CARS_AFTER_YEAR(:year_in);", nativeQuery = true)
List<Car> findCarsAfterYear(@Param("year_in") Integer year_in);
Dans cette méthode, nous utilisons une requête native pour appeler la procédure
stockée. Nous stockons la requête dans l' attribut value de l'annotation.
De même, nous utilisons @Param pour faire correspondre le paramètre d'entrée de la
procédure stockée. De plus, nous mappons la sortie de la procédure stockée à la liste
des objets Car de l'entité .
28
29. Interface ReadOnlyRepository
Pour une raison de sécurité ou à empêcher le développeur à manipuler quelque
méthodes de spring data ou pour une vision de séparation on peut créer une interface qui
contient seulement les méthodes qu’on veut.
@NoRepositoryBean
Public interface publique ReadOnlyRepository < T , ID > extends Repository< T , ID > {
T findOne(ID id);
Iterable<T> findAll(); }
Public interface EmployeeRepository extends ReadOnlyRepository < Employé , Entier > { }
29
30. Différence entre save () et saveAndFlush ()
La méthode save ()
La méthode save () nous permet d' enregistrer une entité dans la base de données .
Il appartient à l' interface CrudRepository définie par Sp
Normalement, Hibernate conserve l'état persistant en mémoire. Le processus de
synchronisation de cet état avec la base de données sous-jacente est appelé vidage.
Lorsque nous utilisons la méthode save () , les données associées à l'opération de sauvegarde
ne seront pas vidées dans la base de données sauf et jusqu'à ce qu'un appel explicite à
la méthode flush () ou commit () soit effectué.
Le saveAndFlush ()
Contrairement à save () , la méthode saveAndFlush () vide les données immédiatement
pendant l'exécution. Cette méthode appartient à l' interface JpaRepository de Spring Data
JPA.
30
31. Différence entre delete () et deleteInBatch ()
La méthode delete() :
supprimer votre entité en une seule opération.
La méthode deleteInBatch () :
regrouper plusieurs instructions de suppression et les supprimer en une seule
opération.
Si vous avez besoin de nombreuses opérations de suppression, la suppression
par lots peut être plus rapide.
31