Project Oxygen & Ideo-LabIDEO LAB Dashboard 2026

☕ JPA – Mapping, Requêtes, Transactions & Performance

Guide IDEO-Lab : JPA (Jakarta Persistence) du modèle de données aux optimisations (fetch, cache, batching, N+1).

1.1

Vue d’ensemble JPA

Rôle, composants (EntityManager), contexte de persistance, providers.

JakartaORMEntityManager
1.2

Architecture & Cycle de vie

States (new/managed/detached/removed), flush, dirty checking.

LifecycleFlushDirty
1.3

Configuration

persistence.xml, datasource, dialect, ddl auto, logs SQL.

persistence.xmlDatasourceDDL
2.1

Entités & Annotations

@Entity, @Table, @Id, @GeneratedValue, contraintes & colonnes.

@Entity@Id@Column
2.2

Clés primaires

IDENTITY/SEQUENCE/TABLE, UUID, composite keys, embeddables.

SEQUENCEUUID@EmbeddedId
2.3

Mapping avancé

Embeddable, converters, enums, inherited mapping, audit.

@Embeddable@ConverterInheritance
3.1

Relations (Cardinalités)

@OneToMany, @ManyToOne, @ManyToMany, @OneToOne + owning side.

RelationsOwning sideJoin
3.2

Fetch, N+1 & Graphs

LAZY/EAGER, join fetch, entity graphs, batch size.

N+1JOIN FETCHEntityGraph
3.3

Cascade & Orphan Removal

CascadeType, orphanRemoval, pièges fréquents, delete semantics.

CascadeorphanRemovalDelete
4.1

JPQL

SELECT, joins, params, pagination, projections, DTO.

JPQLDTOPagination
4.2

Criteria API

Queries type-safe, dynamic filters, predicates, ordering.

CriteriaType-safeDynamic
4.3

SQL natif

NativeQuery, mapping résultats, performances, limites.

Native SQLMappingPerf
5.1

Transactions

JTA vs RESOURCE_LOCAL, propagation (Spring), isolation & locks.

TXIsolationLock
5.2

Optimistic/Pessimistic Locking

@Version, LockModeType, collisions, retries, deadlocks.

@VersionOptimisticPessimistic
5.3

Flush, Clear & Batching

Flush mode, clear, batch inserts/updates, stateless patterns.

FlushBatchClear
6.1

Caches (L1/L2)

First-level cache, second-level, query cache, invalidation.

L1L2Query cache
6.2

Schema / DDL

ddl-auto, migrations (Flyway/Liquibase), naming strategies.

DDLFlywayLiquibase
6.3

Tests & Debug

H2/Testcontainers, logs SQL, explain plans, assertions.

TestLogsExplain
7.1

Providers (Hibernate…)

Ce que JPA standardise vs ce que le provider ajoute.

HibernateEclipseLinkProvider
7.2

Spring Data JPA

Repositories, derived queries, paging, specs, projections.

RepositorySpecificationsPaging
8.1

Cheat-sheet JPA

Rappels express : mapping, fetch, transactions, queries.

cheatbest practices
1.1 Vue d’ensemble JPA
JPA = Standard de persistance

JPA (aujourd’hui Jakarta Persistence) est une spécification : elle définit les interfaces et les annotations pour mapper des objets Java vers des tables SQL. Le moteur concret est un provider (ex : Hibernate, EclipseLink).

Principes
  • Entity mapping : classes ↔ tables, champs ↔ colonnes.
  • Unit of Work : un contexte suit les entités et synchronise en base (flush).
  • Lazy loading : chargement différé des relations.
  • JPQL : requêtes orientées entités (portable).
Flux “request → DB” (conceptuel)
Client request
   |
Service layer
   |
Transaction boundary
   |
EntityManager (persistence context)
   |
Provider (Hibernate / EclipseLink)
   |
JDBC
   |
Database

JPA ne “remplace” pas SQL : il l’organise. Tu continues à penser index, plans, cardinalités.

Les briques indispensables
BriqueRôleÀ retenir
EntityObjet persistantIdentité stable + mapping propre
EntityManagerAPI centralepersist/find/merge/remove + queries
Persistence ContextCache L1 + suividirty checking, flush
TransactionAtomicitédélimite quand on flush/commit
ProviderImplémentationperf + fonctionnalités “vendor”

Si tu “sens” du flou : pense “un EntityManager = une session de travail” avec un cache L1, qui pousse vers la base lors du flush/commit.

Cas d’usage typiques
  • Applications métiers CRUD avec règles de gestion complexes.
  • Domain model riche (relations, invariants, agrégats).
  • Accès DB standardisé + possibilité d’optimiser par provider.
Ce que JPA ne “fait pas” tout seul
  • Choisir les bons index, partitions, paramètres DB.
  • Éviter les N+1 si tu ne maîtrises pas fetch/joins.
  • Garantir la perf : tu dois profiler et lire des plans.
1.2 Architecture & Cycle de vie
Les 4 états (à connaître par cœur)
ÉtatDéfinitionImpact
NewObjet Java non persistéAucun SQL tant que persist()
ManagedAttaché au contexteModifs détectées → UPDATE au flush
DetachedHors contextePlus de suivi, merge() pour rattacher
RemovedMarqué pour suppressionDELETE au flush/commit
Diagramme simple
new --> persist() --> managed --> remove() --> removed
           |                 |
           +-- detach/clear --+--> detached -- merge() --> managed

Beaucoup de bugs viennent de “detached entities” + merge mal compris.

Flush vs Commit

Flush = synchroniser l’état mémoire vers la base (SQL envoyé), sans forcément valider la transaction. Commit = valider la transaction DB.

Dirty checking

En état managed, le provider compare l’état initial à l’état final et génère l’UPDATE si nécessaire.

// English-only example
em.getTransaction().begin();

Customer c = em.find(Customer.class, 10L); // managed
c.setStatus("ACTIVE"); // dirty

em.flush();  // sends UPDATE (still in TX)
em.getTransaction().commit();

À grande échelle : flush/clear périodiques + batching, sinon explosion mémoire.

Pièges fréquents
  • LazyInitializationException : accès à une relation lazy hors contexte.
  • N+1 : itération sur une collection lazy qui déclenche 1 requête par ligne.
  • merge() : peut dupliquer du travail (SELECT + UPDATE) si mal utilisé.
  • equals/hashCode : dangereux si basé sur ID non assigné ou mutable.
Règle de base

Décide explicitement se termine la transaction, et quelles relations doivent être chargées avant de sortir de ce scope.

1.3 Configuration (persistence.xml / Datasource / DDL)
Minimal persistence.xml

En Java SE (RESOURCE_LOCAL), c’est le point d’entrée historique. En Spring Boot, tu passes souvent par application.yml, mais comprendre persistence.xml reste utile.



  
    org.hibernate.jpa.HibernatePersistenceProvider

    com.example.Customer

    
      
      
      
      

      
      
    
  
Check-list configuration
PointPourquoi
Pool de connexionsPerf + stabilité (HikariCP etc.)
Dialect correctSQL généré + types + pagination
TimezoneDates/instants cohérents
ddl-autoJamais “create” en prod
BatchingInsert/update massifs

En prod : validate + migrations (Flyway/Liquibase).

Voir le SQL “utile”
  • Activer logs SQL (et paramètres bind), mais attention au bruit.
  • Mettre un slow query log côté DB, et corréler avec trace applicative.
  • Sur incidents : extraire les requêtes réelles et faire EXPLAIN (ANALYZE).
# English-only sample (Spring Boot)
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.orm.jdbc.bind=TRACE
DDL : stratégie saine
ModeUsageRisque
createPOC jetableDestruction schéma
updateDev localDrift silencieux
validatePré-prod/prodBloque si mismatch (bien)
noneProd strictÀ coupler migrations

Reco : Flyway ou Liquibase pour versionner le schéma, et JPA pour valider/mapper.

2.1 Entités & Annotations
Mapping minimal
// English-only sample
import jakarta.persistence.*;

@Entity
@Table(name = "customer")
public class Customer {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(nullable = false, length = 120)
  private String name;

  @Column(nullable = false)
  private String status;

  // getters/setters
}
Bonnes pratiques immédiates
  • @Table(name=...) explicite (évite surprises de naming).
  • @Column(nullable/length) pour aligner contrat métier.
  • Évite les entités “anémique” si le domaine est riche.
  • Ne surcharge pas l’entité avec de la logique I/O.

Le mapping doit refléter les contraintes DB, pas les masquer.

Contraintes : où les mettre ?

Les annotations JPA sont utiles, mais la DB reste la source d’autorité. Pour la validation métier : Bean Validation (jakarta.validation) + contraintes DB.

// English-only sample
@Column(unique = true, nullable = false)
private String email;

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private CustomerTier tier;

Important : “unique=true” côté JPA n’est pas une stratégie d’indexing en soi : en prod, passe par migrations.

Dates / Instants
  • Préférer Instant / OffsetDateTime / LocalDate selon besoin.
  • Fixer la timezone (DB + JVM + app) pour éviter “heisenbugs”.
Enums

Toujours EnumType.STRING (stable) sauf cas particulier.

2.2 Clés primaires (IDENTITY / SEQUENCE / UUID / composites)
Choisir une stratégie
StratégieDB typiqueNote perf
IDENTITYMySQL/MariaDBBatching plus compliqué (id obtenu à l’insert)
SEQUENCEPostgreSQL/OracleTrès bon pour batching (allocation)
TABLEGénériqueSouvent moins performant
// English-only sample
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customer_seq")
@SequenceGenerator(name = "customer_seq", sequenceName = "customer_seq", allocationSize = 50)
private Long id;

allocationSize réduit les allers-retours DB pour générer des IDs.

UUID : quand et pourquoi
  • Utile en systèmes distribués (génération côté app).
  • Attention : index plus gros, locality faible, impact perf sur inserts.
  • Mitigation : UUID “ordered” / ULID / UUIDv7 (selon stack).
// English-only sample
@Id
@Column(columnDefinition = "uuid")
private java.util.UUID id;
Composite keys (prudence)

Souvent mieux : une clé technique + contrainte unique métier. Mais si tu dois : @EmbeddedId.

// English-only sample
@Embeddable
public class OrderLineId implements java.io.Serializable {
  private Long orderId;
  private Integer lineNo;
}

@Entity
public class OrderLine {
  @EmbeddedId
  private OrderLineId id;
}
2.3 Mapping avancé (Embeddables, Converters, Inheritance)
@Embeddable : regrouper proprement
// English-only sample
@Embeddable
public class Address {
  private String street;
  private String city;
  private String zip;
}

@Entity
public class Customer {
  @Embedded
  private Address billingAddress;
}

Utile pour valeur-objets, lisibilité, et réduction de duplication.

@Converter : types métier ↔ types DB
// English-only sample
@Converter(autoApply = false)
public class MoneyConverter implements AttributeConverter {
  public Long convertToDatabaseColumn(Money attribute) { return attribute == null ? null : attribute.cents(); }
  public Money convertToEntityAttribute(Long dbData) { return dbData == null ? null : Money.ofCents(dbData); }
}

Très pratique pour encapsuler un modèle (Money, Email, JsonMap, etc.).

Inheritance strategies
StrategyDB shapeTrade-off
SINGLE_TABLEOne table + discriminatorSimple, nullable columns
JOINEDOne table per classNormalized, joins cost
TABLE_PER_CLASSSeparate tablesUnions, often slow
3.1 Relations (OneToMany / ManyToOne / ManyToMany / OneToOne)
Owning side : règle clé

Dans une relation bidirectionnelle, un seul côté possède la clé étrangère (ou la table de jointure). C’est le owning side. L’autre est mappedBy.

// English-only sample
@Entity
class Order {
  @OneToMany(mappedBy = "order")
  private List lines = new ArrayList<>();
}

@Entity
class OrderLine {
  @ManyToOne(optional = false)
  @JoinColumn(name = "order_id")
  private Order order; // owning side (FK here)
}

Si tu updates uniquement le côté inverse, la FK ne bougera pas.

OneToMany : préfère “ManyToOne + collection inverse”

Le mapping “@OneToMany” côté parent seul peut créer une table de jointure implicite selon provider. Le pattern stable : FK côté enfant (ManyToOne) + collection inverse.

Helper methods (cohérence)
// English-only sample
public void addLine(OrderLine line) {
  lines.add(line);
  line.setOrder(this);
}

public void removeLine(OrderLine line) {
  lines.remove(line);
  line.setOrder(null);
}
ManyToMany : prudence
  • Bien en lecture simple, mais complexe dès que la jointure porte des attributs.
  • Si la table de jointure a des colonnes métier : modèle-la comme entité (ex: Membership).
// English-only sample
@Entity
class Membership {
  @ManyToOne User user;
  @ManyToOne Group group;
  @Column String role;
}
3.2 Fetch, N+1, JOIN FETCH & Entity Graphs
Le N+1 en une phrase

1 requête charge la liste (N lignes), puis N requêtes chargent une relation lazy par ligne. Résultat : latence + charge DB + saturation pool.

// English-only sample
List orders = em.createQuery("select o from Order o", Order.class).getResultList();
for (Order o : orders) {
  o.getLines().size(); // triggers N queries if lines is LAZY
}
Symptômes
  • Explosion du nombre de requêtes.
  • Temps de réponse proportionnel au volume.
  • Logs SQL “mitraillette”.
Solution #1 : JOIN FETCH (ciblé)
// English-only sample
List orders =
  em.createQuery(
    "select distinct o from Order o " +
    "left join fetch o.lines " +
    "where o.status = :s", Order.class)
  .setParameter("s", "OPEN")
  .getResultList();

“distinct” évite les doublons d’objets racine quand la jointure multiplie les lignes.

Limites
  • Pagination + join fetch collection = souvent problématique.
  • Risque de charger trop de données (cartésien).
Solution #2 : EntityGraph (pilotage propre)
// English-only sample
EntityGraph graph = em.createEntityGraph(Order.class);
graph.addAttributeNodes("customer");
Subgraph sg = graph.addSubgraph("lines");
sg.addAttributeNodes("product");

Order o = em.find(Order.class, 10L, Map.of("jakarta.persistence.fetchgraph", graph));
Solution #3 : Batch fetching

L’idée : quand une collection lazy est demandée, le provider la charge pour plusieurs parents en une requête IN.

# English-only sample (Hibernate)
hibernate.default_batch_fetch_size=50
3.3 Cascade & Orphan Removal
Que propage “cascade” ?
TypePropagationNote
PERSISTpersist() sur enfantsCréation graphe
MERGEmerge() sur enfantsRattachement
REMOVEremove() sur enfantsSuppression cascade
ALLToutÀ manier avec prudence
// English-only sample
@OneToMany(mappedBy = "order", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List lines;
orphanRemoval = “remove when unlinked”
// English-only sample
@OneToMany(mappedBy = "order", orphanRemoval = true, cascade = CascadeType.ALL)
private List lines;

Si une ligne est retirée de la collection et n’est plus référencée, elle est supprimée au flush. Idéal pour “enfants owned” (agrégat).

Pièges
  • Cascade REMOVE sur relation partagée = catastrophe (effets de bord).
  • Supprimer un parent avec énorme collection = storm de DELETE (batch à prévoir).
  • Les helpers add/remove sont indispensables pour garder le graphe cohérent.
4.1 JPQL (select, joins, params, DTO, pagination)
JPQL = entités, pas tables
// English-only sample
List vip =
  em.createQuery(
      "select c from Customer c " +
      "where c.tier = :tier and c.status = :st " +
      "order by c.name asc",
      Customer.class)
    .setParameter("tier", CustomerTier.GOLD)
    .setParameter("st", "ACTIVE")
    .getResultList();
Joins
// English-only sample
List orders =
  em.createQuery(
      "select o from Order o join o.customer c " +
      "where c.email = :email", Order.class)
    .setParameter("email", "a@b.com")
    .getResultList();
Projection DTO
// English-only sample
public record CustomerSummary(Long id, String name, long orderCount) {}

List rows =
  em.createQuery(
      "select new com.example.CustomerSummary(c.id, c.name, count(o)) " +
      "from Customer c left join c.orders o " +
      "group by c.id, c.name",
      CustomerSummary.class)
    .getResultList();

DTO = moins de data, moins de lazy surprises, plus stable en API.

Pagination
// English-only sample
List page =
  em.createQuery("select o from Order o order by o.createdAt desc", Order.class)
    .setFirstResult(0)
    .setMaxResults(50)
    .getResultList();

Si pagination + join fetch collection : préférer deux requêtes (ids page -> fetch graph).

4.2 Criteria API (queries dynamiques type-safe)
Quand l’utiliser
  • Filtres multi-critères construits à runtime.
  • Recherche avancée (écran “admin” typique).
  • Garder du type-safety (évite concat string).

Alternative : Specifications Spring Data (souvent plus ergonomique).

Exemple minimal
// English-only sample
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery q = cb.createQuery(Customer.class);
Root c = q.from(Customer.class);

List ps = new ArrayList<>();
ps.add(cb.equal(c.get("status"), "ACTIVE"));
ps.add(cb.like(cb.lower(c.get("name")), "%ali%"));

q.select(c).where(ps.toArray(new Predicate[0])).orderBy(cb.asc(c.get("name")));

List res = em.createQuery(q).setMaxResults(50).getResultList();
4.3 SQL natif (NativeQuery) – quand & comment
Quand SQL natif est légitime
  • CTE complexes, window functions, hints DB.
  • Optimisations “à la DBA” (plans stables, index-only scans).
  • Rapports/BI (agrégations lourdes) → projection DTO.
Exemple : projection simple
// English-only sample
List rows = em.createNativeQuery(
  "select c.id, c.name, count(o.id) as cnt " +
  "from customer c left join orders o on o.customer_id = c.id " +
  "group by c.id, c.name " +
  "order by cnt desc")
.getResultList();

En pratique : map vers DTO (ou SqlResultSetMapping) pour garder un code propre.

Risques
  • Portabilité réduite (dialect).
  • Maintenance : SQL long dans le code (à versionner/tester).
  • Attention aux injections : toujours bind params, jamais concat.
5.1 Transactions (JTA / RESOURCE_LOCAL / Spring)
La frontière transactionnelle : décision d’architecture
  • TX trop grande : locks longs, contention, mémoire, timeouts.
  • TX trop petite : incohérences, lazy hors scope, répétition I/O.
  • Objectif : TX “métier” courte, cohérente, observable.
// English-only sample (Java SE)
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
  // work
  tx.commit();
} catch (Exception e) {
  tx.rollback();
  throw e;
} finally {
  em.close();
}
Isolation & verrous

L’isolation est DB-level. JPA n’invente pas la cohérence : elle s’appuie sur la DB.

NiveauProtège contreCoût
READ COMMITTEDdirty readsfaible
REPEATABLE READnon-repeatable readsmoyen
SERIALIZABLEphantomsélevé

En pratique : lock ciblé + optimistic locking + retries.

Spring (@Transactional) : points d’attention
  • Proxy-based : appels internes (self-invocation) ne passent pas toujours par le proxy.
  • Propagation : REQUIRED, REQUIRES_NEW, SUPPORTS, etc.
  • Read-only : utile pour hints perf (selon provider), mais pas magique.
// English-only sample
@Transactional
public void placeOrder(Long customerId) {
  // business work
}
5.2 Optimistic vs Pessimistic Locking
@Version : standard “scalable”
// English-only sample
@Version
private long version;

Chaque UPDATE vérifie la version : si quelqu’un a modifié entre temps → exception (OptimisticLockException). Très bon pour systèmes à faible contention.

Locks pessimistes : quand il faut bloquer
// English-only sample
Account a = em.find(Account.class, id, LockModeType.PESSIMISTIC_WRITE);
  • Protège fortement, mais peut générer contention et deadlocks.
  • À utiliser sur “hot rows” (solde, stock critique) avec TX courte.
Stratégie retry (recommandée)
  • Optimistic : retries applicatifs sur collisions.
  • Backoff exponentiel + jitter sur contention.
  • Mesure : taux de collisions, latence, timeouts.
// English-only pseudo
for (int attempt=1; attempt<=3; attempt++) {
  try { doWork(); break; }
  catch (OptimisticLockException e) { sleep(backoff(attempt)); }
}
5.3 Flush/Clear & Batching (mass updates)
Pourquoi flush/clear ?
  • Le contexte de persistance grandit (mémoire).
  • Dirty checking devient coûteux.
  • Le JDBC batching ne sert que si la charge est structurée.
Batch inserts (pattern)
// English-only sample
for (int i = 0; i < items.size(); i++) {
  em.persist(items.get(i));
  if (i % 100 == 0) {
    em.flush();
    em.clear();
  }
}
Config batching (ex provider)
# English-only sample (Hibernate)
hibernate.jdbc.batch_size=50
hibernate.order_inserts=true
hibernate.order_updates=true
Mass update : attention

Les bulk updates JPQL contournent le contexte (pas de dirty checking). Il faut souvent clear() ensuite pour éviter incohérences en mémoire.

// English-only sample
int n = em.createQuery("update Customer c set c.status='INACTIVE' where c.lastLogin < :d")
  .setParameter("d", cutoff)
  .executeUpdate();
em.clear();
6.1 Cache L1/L2 & Query Cache
Cache L1 (persistence context)
  • Par EntityManager / transaction scope.
  • Garantit identité : même ID → même instance.
  • Très utile, mais peut grossir (flush/clear).
Cache L2 (second-level)

Partagé entre EntityManagers (selon provider). Très efficace en lecture “reference data”, mais exige une stratégie d’invalidation.

# English-only sample (Hibernate-ish)
hibernate.cache.use_second_level_cache=true
hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory

Query cache : utile mais facile à rendre incohérent. À activer seulement si maîtrisé.

Invalidation : le sujet qui décide
  • Données peu volatiles : L2 excellent.
  • Données très mutables : L2 peut coûter plus qu’il ne rapporte.
  • En cluster : attention à cohérence (replication/invalidation events).
6.2 Schema / DDL (ddl-auto, migrations, naming)
Stratégie recommandée
  • Dev : update (toléré) + reset DB simple.
  • Pré-prod/prod : validate/none + migrations.
  • Index/constraints : via migrations (versionné, relu, testable).
Naming strategy

Fixer une stratégie évite les “surprises” de noms de tables/colonnes entre environnements.

Exécution DBA-friendly
  • Générer les scripts DDL (dry-run) pour review.
  • Tester migrations sur clone de prod (volumétrie réaliste).
  • Automatiser rollback/forward en CI/CD.
# English-only sample
flyway migrate
flyway info
flyway validate
6.3 Tests, Debug & Perf (H2/Testcontainers/EXPLAIN)
H2 vs Testcontainers
Option+-
H2Très rapideDifférences dialect/behaviors
TestcontainersDB “réelle”Plus lent

Pour bugs SQL/fetch/locks : Testcontainers est souvent indispensable.

Tracer ce qui compte
  • Nombre de requêtes par endpoint (détecter N+1).
  • Temps DB vs temps applicatif.
  • Top queries (slow log) + explain plans.
# English-only idea
request_id -> app logs -> SQL logs -> db slow logs -> explain analyze
Workflow perf (pro)
  1. Reproduire (dataset réaliste).
  2. Capturer SQL réel + binds.
  3. EXPLAIN ANALYZE + index review.
  4. Fix fetch strategy / queries / indexes.
  5. Re-bench + regression tests.
7.1 Providers (Hibernate/EclipseLink) – ce que JPA ne couvre pas
JPA = portable, Provider = performance + options
  • Batching avancé, caches, hints, fetch tuning.
  • Outils de statistiques/monitoring.
  • Extensions (filters, @Where, custom types, etc.).
Conseil

Rester JPA “pur” pour 80%, et assumer les extensions provider uniquement pour le 20% critique.

Checklist prod (provider)
PointImpact
Batch sizeInsert/update massifs
Default fetch sizeLecture bulk
Second-level cacheRead-heavy
StatisticsDiagnostic
7.2 Spring Data JPA – repositories, derived queries, specs
Repository : abstraction efficace
// English-only sample
public interface CustomerRepository extends JpaRepository {
}

Ça ne supprime pas la nécessité de comprendre fetch/transactions : ça la rend plus accessible.

Derived queries
// English-only sample
List findByStatusAndTierOrderByNameAsc(String status, CustomerTier tier);

Super pour CRUD, mais attention : sur du complexe, préfère @Query ou specs.

Specifications + paging
// English-only sample
Page findAll(Specification spec, Pageable pageable);

Très bon pour écrans de recherche avancée, surtout avec filtres optionnels.

8.1 Cheat-sheet JPA (à garder sous la main)
Mapping & relations
// English-only quick notes
@Entity @Table(name="t")
@Id @GeneratedValue

@ManyToOne(optional=false) @JoinColumn(name="parent_id")   // owning side
@OneToMany(mappedBy="parent")                              // inverse side

// Prefer EnumType.STRING
@Enumerated(EnumType.STRING)

// Optimistic locking
@Version long version;
Fetch
// English-only quick notes
Default: ManyToOne is EAGER (often change to LAZY if possible)
Fix N+1 with:
- JOIN FETCH (targeted)
- EntityGraph
- batch fetching (provider)

Avoid join fetch + pagination on collections -> use id page then fetch graph
Queries & transactions
// English-only quick notes
JPQL: "select e from Entity e where e.field = :p"
Bind params, never concat

Pagination:
setFirstResult(offset)
setMaxResults(limit)

Batch writes:
flush+clear every N
hibernate.jdbc.batch_size=50

Bulk update:
executeUpdate()
then clear() to avoid stale context
Prod checklist
// English-only checklist
- ddl-auto=validate/none
- migrations = Flyway/Liquibase
- SQL logs off (or sampled) + slow query log on DB
- measure query count per request
- cache only if invalidation is defined
- explain plans for top queries