Chapitre 5: Introduction aux Repositories Spring Data JPA
Introduction
Maintenant que vous maîtrisez les bases de Spring Boot (structure, configuration, annotations), il est temps d'apprendre à persister des données dans une base de données. Spring Data JPA simplifie considérablement cette tâche en vous permettant d'interagir avec la base de données sans écrire de code SQL.
Objectifs du Chapitre
À la fin de ce chapitre, vous serez capable de:
- Comprendre le rôle d'un Repository Spring Data JPA
- Utiliser les 4 méthodes de base pour le CRUD
- Créer des méthodes de recherche simples avec les conventions de nommage
- Comprendre comment Spring génère automatiquement les requêtes
Qu'est-ce qu'un Repository?
Définition
Un Repository est une interface qui permet d'interagir avec la base de données sans écrire de code SQL. Spring Data JPA génère automatiquement l'implémentation.
Analogie: Un repository est comme un bibliothécaire qui gère vos livres (données) pour vous.
Architecture Complète
Responsabilités:
| Couche | Rôle | Exemple |
|---|---|---|
| Controller | Gère les requêtes HTTP | @RestController |
| Service | Logique métier | UserService |
| Repository | Accès aux données | UserRepository |
| Database | Stockage | PostgreSQL, H2 |
Créer une Entité Simple
Exemple: Entité User
Avant de créer un repository, nous avons besoin d'une entité - une classe Java qui représente une table dans la base de données.
package com.example.demo.model;
import jakarta.persistence.*;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private int age;
// Constructeurs
public User() {
}
public User(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
// Getters et Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Annotations Essentielles
| Annotation | Rôle | Exemple |
|---|---|---|
@Entity | Marque la classe comme entité JPA | Crée une table |
@Table | Spécifie le nom de la table | name = "users" |
@Id | Identifie la clé primaire | Champ id |
@GeneratedValue | Génération automatique de l'ID | Auto-increment |
Les 4 Méthodes de Base
Interface JpaRepository
Spring Data JPA fournit l'interface JpaRepository qui contient déjà toutes les méthodes CRUD de base.
package com.example.demo.repository;
import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Pas besoin d'écrire de code!
// Les méthodes sont héritées automatiquement
}
Explication:
JpaRepository<User, Long>:User: Type de l'entitéLong: Type de l'ID
Méthodes Héritées Automatiquement
Voici les 4 méthodes essentielles que vous pouvez utiliser immédiatement:
1. findById() - Trouver par ID
Signature:
Optional<User> findById(Long id)
Utilisation:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
}
SQL généré:
SELECT * FROM users WHERE id = ?
2. findAll() - Trouver tous
Signature:
List<User> findAll()
Utilisation:
public List<User> getAllUsers() {
return userRepository.findAll();
}
SQL généré:
SELECT * FROM users
3. save() - Créer ou Mettre à jour
Signature:
User save(User user)
Utilisation (Création):
public User createUser(User user) {
return userRepository.save(user);
}
SQL généré (si ID est null):
INSERT INTO users (name, email, age) VALUES (?, ?, ?)
SQL généré (si ID existe):
UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?
4. deleteById() - Supprimer par ID
Signature:
void deleteById(Long id)
Utilisation:
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
SQL généré:
DELETE FROM users WHERE id = ?
Tableau Récapitulatif
| Méthode | Action | Paramètre | Retour | SQL |
|---|---|---|---|---|
findById(id) | Lire un | ID | Optional<User> | SELECT ... WHERE id = ? |
findAll() | Lire tous | Aucun | List<User> | SELECT * |
save(user) | Créer/Modifier | Entité | User | INSERT ou UPDATE |
deleteById(id) | Supprimer | ID | void | DELETE ... WHERE id = ? |
Conventions de Nommage: Les 3 Bases
Principe Général
Spring Data JPA peut générer automatiquement des méthodes de recherche basées sur le nom de la méthode.
Format:
findBy[Attribut]
1. findBy (Recherche Simple)
Syntaxe:
findBy + NomAttribut
Exemples:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Recherche par nom
User findByName(String name);
// Recherche par email
User findByEmail(String email);
// Recherche par age
List<User> findByAge(int age);
}
SQL généré:
| Méthode | SQL |
|---|---|
findByName("John") | SELECT * FROM users WHERE name = 'John' |
findByEmail("john@example.com") | SELECT * FROM users WHERE email = 'john@example.com' |
findByAge(25) | SELECT * FROM users WHERE age = 25 |
2. And (Combiner deux critères)
Syntaxe:
findBy + Attribut1 + And + Attribut2
Exemples:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Recherche par nom ET age
List<User> findByNameAndAge(String name, int age);
// Recherche par email ET age
User findByEmailAndAge(String email, int age);
}
SQL généré:
| Méthode | SQL |
|---|---|
findByNameAndAge("John", 25) | SELECT * FROM users WHERE name = 'John' AND age = 25 |
findByEmailAndAge("john@example.com", 25) | SELECT * FROM users WHERE email = 'john@example.com' AND age = 25 |
3. Or (Critère alternatif)
Syntaxe:
findBy + Attribut1 + Or + Attribut2
Exemples:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Recherche par nom OU email
List<User> findByNameOrEmail(String name, String email);
// Recherche par age 20 OU 30
List<User> findByAgeOrAge(int age1, int age2);
}
SQL généré:
| Méthode | SQL |
|---|---|
findByNameOrEmail("John", "john@example.com") | SELECT * FROM users WHERE name = 'John' OR email = 'john@example.com' |
Exemple Complet: Architecture à 3 Couches
1. Entité (Model)
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private int age;
// Constructeurs, Getters, Setters
}
2. Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Méthodes héritées automatiquement:
// - findById(Long id)
// - findAll()
// - save(User user)
// - deleteById(Long id)
// Méthodes personnalisées:
User findByEmail(String email);
List<User> findByAge(int age);
List<User> findByNameAndAge(String name, int age);
}
3. Service
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// CREATE
public User createUser(User user) {
return userRepository.save(user);
}
// READ - tous
public List<User> getAllUsers() {
return userRepository.findAll();
}
// READ - par ID
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found with id: " + id));
}
// READ - par email
public User getUserByEmail(String email) {
return userRepository.findByEmail(email);
}
// READ - par age
public List<User> getUsersByAge(int age) {
return userRepository.findByAge(age);
}
// UPDATE
public User updateUser(Long id, User userDetails) {
User user = getUserById(id);
user.setName(userDetails.getName());
user.setEmail(userDetails.getEmail());
user.setAge(userDetails.getAge());
return userRepository.save(user);
}
// DELETE
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
4. Controller
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
// POST /api/users
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}
// GET /api/users
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}
// GET /api/users/123
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
// GET /api/users/email/john@example.com
@GetMapping("/email/{email}")
public User getUserByEmail(@PathVariable String email) {
return userService.getUserByEmail(email);
}
// GET /api/users/age/25
@GetMapping("/age/{age}")
public List<User> getUsersByAge(@PathVariable int age) {
return userService.getUsersByAge(age);
}
// PUT /api/users/123
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.updateUser(id, user);
}
// DELETE /api/users/123
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}
Configuration H2 pour les Tests
application.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
h2:
console:
enabled: true
path: /h2-console
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
Accès à la console H2:
http://localhost:8080/h2-console
Exercices Pratiques
Exercice 1: Repository de Base
Objectif: Créer un repository simple
Instructions:
- Créez une entité
Product:Long idString nameDouble priceint quantity
- Créez
ProductRepositoryqui étendJpaRepository - Testez les 4 méthodes de base
Questions:
- Quelle méthode utiliser pour créer un produit?
- Comment récupérer tous les produits?
- Comment supprimer un produit par son ID?
Exercice 2: Méthodes de Recherche
Objectif: Utiliser les conventions de nommage
Instructions:
- Dans
ProductRepository, ajoutez:- Recherche par nom
- Recherche par prix
- Recherche par nom ET prix
- Créez un service
ProductService - Testez dans un contrôleur
Questions:
- Quel SQL est généré pour
findByName? - Comment chercher les produits d'un prix donné?
Exercice 3: Service Complet
Objectif: Créer une architecture complète
Instructions:
- Créez une entité
Student:Long idString firstNameString lastNameString emailint grade
- Créez
StudentRepositoryavec:- Recherche par email
- Recherche par grade
- Recherche par firstName et lastName
- Créez
StudentServiceavec toutes les opérations CRUD - Créez
StudentControlleravec tous les endpoints
Questions de Révision
-
Qu'est-ce qu'un Repository dans Spring Data JPA?
-
Quelles sont les 4 méthodes héritées automatiquement de JpaRepository?
-
Quelle est la différence entre
save()pour une création et pour une mise à jour? -
Comment Spring sait-il quelle requête SQL générer pour
findByName? -
Quelle est la différence entre
AndetOrdans les noms de méthodes? -
Pourquoi utilise-t-on
Optional<User>pourfindById()? -
Que se passe-t-il si on appelle
findById()avec un ID qui n'existe pas? -
Peut-on combiner plus de 2 critères avec
And? Donnez un exemple.
Résumé du Chapitre
Points Clés
- Un Repository est une interface pour accéder aux données
JpaRepositoryfournit 4 méthodes de base :findById,findAll,save,deleteById- Les conventions de nommage permettent de créer des méthodes automatiquement
- 3 mots-clés essentiels :
findBy,And,Or - Spring génère le SQL automatiquement basé sur le nom de la méthode
- Architecture: Controller → Service → Repository → Database
Ce que Vous Savez Maintenant
✅ Créer une entité JPA
✅ Créer un repository Spring Data JPA
✅ Utiliser les 4 méthodes CRUD de base
✅ Créer des méthodes de recherche simples (findBy, And, Or)
✅ Construire une architecture à 3 couches complète
Ce que Vous Apprendrez Plus Tard (Module 6)
🔜 Conventions de nommage avancées (15+ mots-clés)
🔜 Jointures entre entités
🔜 Requêtes JPQL personnalisées
🔜 Performance et optimisation
🔜 Transactions
Bonnes Pratiques
DO (À Faire)
✅ Utiliser l'injection par constructeur pour les repositories
✅ Nommer les méthodes selon les conventions Spring Data JPA
✅ Gérer les cas où findById() ne trouve rien avec Optional
✅ Séparer la logique métier (Service) de l'accès aux données (Repository)
DON'T (À Éviter)
❌ Mettre de la logique métier dans les repositories
❌ Appeler directement le repository depuis le contrôleur
❌ Ignorer le Optional retourné par findById()
❌ Créer des noms de méthodes qui ne suivent pas les conventions
Prochaines Étapes
Mettre en Pratique
Maintenant que vous connaissez les bases des repositories, il est temps de pratiquer!
Passez aux TPs du Module 2:
- TP2.2: Base de Données H2 - Utilisez H2 et les 4 méthodes de base
Pour aller plus loin (modules suivants):
- Module 3: TP3.1: Architecture CRUD - Application CRUD complète avec validation
- Module 4: TP4.1: PostgreSQL - Migration vers PostgreSQL en production
Pour Aller Plus Loin
Intéressé par des requêtes plus avancées? Consultez:
Module 6: Spring Data JPA Avancé (à venir)
- 15+ mots-clés de recherche
- Jointures entre tables
- JPQL et requêtes personnalisées
- Optimisation des performances