Aller au contenu principal

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:

CoucheRôleExemple
ControllerGère les requêtes HTTP@RestController
ServiceLogique métierUserService
RepositoryAccès aux donnéesUserRepository
DatabaseStockagePostgreSQL, 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​

AnnotationRôleExemple
@EntityMarque la classe comme entité JPACrée une table
@TableSpécifie le nom de la tablename = "users"
@IdIdentifie la clé primaireChamp id
@GeneratedValueGénération automatique de l'IDAuto-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éthodeActionParamètreRetourSQL
findById(id)Lire unIDOptional<User>SELECT ... WHERE id = ?
findAll()Lire tousAucunList<User>SELECT *
save(user)Créer/ModifierEntitéUserINSERT ou UPDATE
deleteById(id)SupprimerIDvoidDELETE ... 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éthodeSQL
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éthodeSQL
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éthodeSQL
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:

  1. Créez une entité Product:
    • Long id
    • String name
    • Double price
    • int quantity
  2. Créez ProductRepository qui étend JpaRepository
  3. 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:

  1. Dans ProductRepository, ajoutez:
    • Recherche par nom
    • Recherche par prix
    • Recherche par nom ET prix
  2. Créez un service ProductService
  3. 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:

  1. Créez une entité Student:
    • Long id
    • String firstName
    • String lastName
    • String email
    • int grade
  2. Créez StudentRepository avec:
    • Recherche par email
    • Recherche par grade
    • Recherche par firstName et lastName
  3. Créez StudentService avec toutes les opérations CRUD
  4. Créez StudentController avec tous les endpoints

Questions de Révision​

  1. Qu'est-ce qu'un Repository dans Spring Data JPA?

  2. Quelles sont les 4 méthodes héritées automatiquement de JpaRepository?

  3. Quelle est la différence entre save() pour une création et pour une mise à jour?

  4. Comment Spring sait-il quelle requête SQL générer pour findByName?

  5. Quelle est la différence entre And et Or dans les noms de méthodes?

  6. Pourquoi utilise-t-on Optional<User> pour findById()?

  7. Que se passe-t-il si on appelle findById() avec un ID qui n'existe pas?

  8. 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
  • JpaRepository fournit 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:

Pour aller plus loin (modules suivants):

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

Retour au Module 2