Motifs JPA/Hibernate
À utiliser pour la modélisation des données, les repositories et l'optimisation des performances dans Spring Boot.
Quand activer
- Conception d'entités JPA et de mappings de tables
- Définition de relations (@OneToMany, @ManyToOne, @ManyToMany)
- Optimisation des requêtes (prévention N+1, stratégies de chargement, projections)
- Configuration des transactions, de l'audit ou des suppressions logiques
- Mise en place de la pagination, du tri ou de méthodes de repository personnalisées
- Réglage du pooling de connexions (HikariCP) ou du cache de second niveau
Conception d'une entité
@Entity
@Table(name = "markets", indexes = {
@Index(name = "idx_markets_slug", columnList = "slug", unique = true)
})
@EntityListeners(AuditingEntityListener.class)
public class MarketEntity {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 200)
private String name;
@Column(nullable = false, unique = true, length = 120)
private String slug;
@Enumerated(EnumType.STRING)
private MarketStatus status = MarketStatus.ACTIVE;
@CreatedDate private Instant createdAt;
@LastModifiedDate private Instant updatedAt;
}
Activer l'audit :
@Configuration
@EnableJpaAuditing
class JpaConfig {}
Relations et prévention N+1
@OneToMany(mappedBy = "market", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PositionEntity> positions = new ArrayList<>();
- Utiliser le chargement paresseux par défaut ; utiliser
JOIN FETCHdans les requêtes si nécessaire - Éviter
EAGERsur les collections ; utiliser des projections DTO pour les chemins de lecture
@Query("select m from MarketEntity m left join fetch m.positions where m.id = :id")
Optional<MarketEntity> findWithPositions(@Param("id") Long id);
Motifs de repository
public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
Optional<MarketEntity> findBySlug(String slug);
@Query("select m from MarketEntity m where m.status = :status")
Page<MarketEntity> findByStatus(@Param("status") MarketStatus status, Pageable pageable);
}
- Utiliser des projections pour les requêtes légères :
public interface MarketSummary {
Long getId();
String getName();
MarketStatus getStatus();
}
Page<MarketSummary> findAllBy(Pageable pageable);
Transactions
- Annoter les méthodes de service avec
@Transactional - Utiliser
@Transactional(readOnly = true)pour les chemins de lecture afin d'optimiser - Choisir la propagation avec précaution ; éviter les transactions de longue durée
@Transactional
public Market updateStatus(Long id, MarketStatus status) {
MarketEntity entity = repo.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Market"));
entity.setStatus(status);
return Market.from(entity);
}
Pagination
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<MarketEntity> markets = repo.findByStatus(MarketStatus.ACTIVE, page);
Pour une pagination de type curseur, inclure id > :lastId dans JPQL avec un tri.
Indexation et performances
- Ajouter des index pour les filtres courants (
status,slug, clés étrangères) - Utiliser des index composites correspondant aux motifs de requête (
status, created_at) - Éviter
select *; projeter uniquement les colonnes nécessaires - Écrire par lots avec
saveAllethibernate.jdbc.batch_size
Pooling de connexions (HikariCP)
Propriétés recommandées :
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.validation-timeout=5000
Pour la gestion des LOB PostgreSQL, ajouter :
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
Mise en cache
- Le cache de premier niveau est par EntityManager ; éviter de conserver des entités entre les transactions
- Pour les entités lues fréquemment, considérer le cache de second niveau avec prudence ; valider la stratégie d'éviction
Migrations
- Utiliser Flyway ou Liquibase ; ne jamais se fier au DDL automatique d'Hibernate en production
- Garder les migrations idempotentes et additives ; éviter de supprimer des colonnes sans plan
Test de l'accès aux données
- Privilégier
@DataJpaTestavec Testcontainers pour refléter la production - Vérifier l'efficacité SQL à l'aide des logs : définir
logging.level.org.hibernate.SQL=DEBUGetlogging.level.org.hibernate.orm.jdbc.bind=TRACEpour les valeurs des paramètres
Rappel : Gardez les entités simples, les requêtes intentionnelles et les transactions courtes. Prévenez N+1 avec des stratégies de chargement et des projections, et indexez pour vos chemins de lecture/écriture.