Spring Data JPA simplifie considérablement l’accès aux données relationnelles dans les projets Spring Boot. Plutôt que d’implémenter manuellement des couches de persistance, il suffit de déclarer des interfaces reposant sur des conventions ou des annotations pour bénéficier d’opérations CRUD, de requêtes dynamiques, de pagination et de jointures complexes — le tout sans écrire de code boilerplate.
Configuration initiale
Pour démarrer, ajoutez la dépendance Maven suivante :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Dans application.properties, configurez la source de données et les options Hibernate :
spring.datasource.url=jdbc:mysql://localhost:3306/demo_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=devuser
spring.datasource.password=securepass
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
Modèle entité et repository
Voici une entité Personne annotée avec JPA :
@Entity
@Table(name = "personnes")
public class Personne implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "nom", nullable = false)
private String nom;
@Column(name = "age")
private Integer age;
// Constructeurs, getters et setters omis pour concision
}
Le repository associé étend JpaRepository, ce qui fournit immédiatement des méthodes comme save(), findById(), findAll(), etc. :
public interface PersonneRepository extends JpaRepository<Personne, Long> {
// Requêtes générées automatiquement par nom de méthode
List<Personne> findByNomStartingWith(String prefix);
List<Personne> findByAgeGreaterThanEqual(Integer minAge);
// Requête JPQL personnalisée
@Query("SELECT p FROM Personne p WHERE p.nom LIKE %:motCle% AND p.age BETWEEN :min AND :max")
List<Personne> rechercherParNomEtAge(@Param("motCle") String motCle,
@Param("min") Integer min,
@Param("max") Integer max);
// Mise à jour via JPQL avec @Modifying
@Modifying
@Query("UPDATE Personne p SET p.age = p.age + 1 WHERE p.id IN :ids")
int augmenterAge(@Param("ids") Collection<Long> ids);
}
Requêtes dynamiques avec Specification
Pour des filtres complexes (ex. : recherche avancée), utilisez JpaSpecificationExecutor. Exemple d’une spécification filtrant par intervalle d’âge et préfixe de nom :
public static Specification<Personne> avecFiltres(String prefixe, Integer minAge, Integer maxAge) {
return (root, query, builder) -> {
List<Predicate> predicates = new ArrayList<>();
if (prefixe != null && !prefixe.trim().isEmpty()) {
predicates.add(builder.like(root.get("nom"), prefixe + "%"));
}
if (minAge != null) {
predicates.add(builder.greaterThanOrEqualTo(root.get("age"), minAge));
}
if (maxAge != null) {
predicates.add(builder.lessThanOrEqualTo(root.get("age"), maxAge));
}
return builder.and(predicates.toArray(new Predicate[0]));
};
}
Utilisation dans un service :
@Service
public class PersonneService {
private final PersonneRepository repo;
public List<Personne> rechercher(String nom, Integer min, Integer max) {
Pageable pageable = PageRequest.of(0, 10, Sort.by("nom").ascending());
return repo.findAll(avecFiltres(nom, min, max), pageable).getContent();
}
}
Gestion des relations
Les associations sont définies via des annotations standard JPA. Par exemple, une relation *un-à-plusieurs* entre Departement et Personne :
@Entity
public class Departement {
@Id @GeneratedValue Long id;
private String libelle;
@OneToMany(mappedBy = "departement", fetch = FetchType.LAZY)
private List<Personne> membres = new ArrayList<>();
}
@Entity
public class Personne {
// ...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "departement_id")
private Departement departement;
}
Cela permet des requêtes telles que departement.getMembres() ou personne.getDepartement(), avec chargement différé par défaut.
Pagination et tri intégrés
L’interface PagingAndSortingRepository (impliquée par JpaRepository) offre nativement un support robuste :
// Pagination seule
Page<Personne> page = personneRepo.findAll(PageRequest.of(1, 25));
// Tri par âge décroissant
Sort sort = Sort.by(Sort.Direction.DESC, "age");
List<Personne> triees = personneRepo.findAll(sort);
// Combinaison pagination + tri
Pageable pageable = PageRequest.of(0, 20, Sort.by("nom").ascending());
Page<Personne> resultat = personneRepo.findAll(pageable);
Extensions personnalisées
Pour ajouter des comportements spécifiques non couverts par les interfaces standrads, implémentez une classe perosnnalisée :
public class PersonneRepositoryImpl implements PersonneRepositoryCustom {
@PersistenceContext private EntityManager em;
@Override
public List<Personne> trouverParCritereAvance(String champ, Object valeur) {
String jpql = "SELECT p FROM Personne p WHERE p." + champ + " = :" + champ;
return em.createQuery(jpql, Personne.class)
.setParameter(champ, valeur)
.getResultList();
}
}
// Interface dédiée
public interface PersonneRepositoryCustom {
List<Personne> trouverParCritereAvance(String champ, Object valeur);
}
// Repository principal
public interface PersonneRepository extends JpaRepository<Personne, Long>,
JpaSpecificationExecutor<Personne>,
PersonneRepositoryCustom { }