Spring Data JPA & Hibernate
Entités, relations, transactions, performance, locking, cascade, mapping professionnel : maîtrisez Spring Data JPA & Hibernate comme en entreprise.
Introduction
Spring Data JPA permet de manipuler des données de manière simple, tout en s’appuyant sur Hibernate, le moteur ORM le plus utilisé dans l’écosystème Java. Pour produire des applications fiables et performantes, il est essentiel de comprendre les mécanismes internes, la gestion des transactions et l’impact des relations.
Ce chapitre vous donne une maîtrise avancée de Spring Data JPA : mapping, relations, cascade, transactions, performance, pagination, verrouillages et bonnes pratiques.
Lexique essentiel
Objet métier persistant géré par Hibernate.
Interface Spring Data pour manipuler les entités.
Chargement à la demande pour éviter les surcoûts.
Contexte d’exécution garantissant la cohérence des données.
Mécanismes de concurrence : pessimistic / optimistic.
Propagation des opérations (persist, remove, merge…).
1. Créer une entité propre et professionnelle
@Entity
@Table(name = "orders")
public class Order {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String customerId;
@Enumerated(EnumType.STRING)
private OrderStatus status;
private BigDecimal total;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> items = new ArrayList<>();
}
Bonnes pratiques : toujours utiliser EnumType.STRING, éviter les relations bidirectionnelles sauf si nécessaire, et préférer orphanRemoval pour un modèle propre.
Repository minimaliste
public interface OrderRepository extends JpaRepository<Order, Long> {
List<Order> findByCustomerId(String customerId);
@Query("select o from Order o where o.status = :status")
List<Order> findByStatus(@Param("status") OrderStatus status);
}
Service transactionnel
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository repository;
@Transactional
public Order create(Order order) {
return repository.save(order);
}
@Transactional(readOnly = true)
public List<Order> listByCustomer(String id) {
return repository.findByCustomerId(id);
}
}
Règle : toujours annoter les services avec @Transactional. Ne jamais mettre @Transactional dans le controller.
Lazy vs Eager : les choix qui tuent les perfs
Par défaut, les relations @OneToMany et @ManyToOne doivent rester en LAZY. L’utilisation de EAGER entraîne un chargement massif imprévisible (anti-pattern fréquent chez les débutants).
DTO d’exposition propre
public record OrderResponse(
Long id,
BigDecimal total,
String status
) {}
- Jamais d’entité dans les controllers.
- DTO → mapper → entité → service → repository.
- Toujours lire en mode LAZY, charger explicitement quand nécessaire.
- Utiliser des transactions lisibles, cohérentes et centrées métier.
2. Performance & optimisation Hibernate
Pagination obligatoire sur les grandes collections
Page<Order> orders = repository.findAll(PageRequest.of(page, size));
Projections pour réduire l’overfetching
public interface OrderSummary {
Long getId();
BigDecimal getTotal();
}
Les projections réduisent drastiquement la quantité de données lues.
Optimistic Locking
@Version
private Long version;
Empêche les mises à jour concurrentes silencieuses.
Métriques indispensables
- Nombre de requêtes SQL
- Temps moyen d’une transaction
- Pourcentage de lazy loading déclenché dans le controller (❌)
Quiz : Spring Data JPA & Hibernate
-
Q1. Où doit se trouver l’annotation @Transactional ?
- A. Dans le controller
- B. Dans le service ✔
- C. Dans l’entité
-
Q2. Quelle stratégie de fetch privilégier ?
- A. EAGER
- B. LAZY ✔
- C. Aucune
Exercice d’application
Implémentez un module complet « Produits » avec :
— Entité propre
— Repository
— Service transactionnel
— Relations OneToMany pour les catégories
— Pagination
— Projection optimisée
— DTO de sortie
Résumé
Spring Data JPA & Hibernate offrent un cadre puissant pour manipuler des données, mais exigent une compréhension fine des relations, transactions, performances et patterns. Une pratique rigoureuse garantit un code propre, stable et scalable.
