Les clés primaires avec JPA

Université de Toulon

LIS UMR CNRS 7020

2026-10-14

Concepts fondamentaux

  • Clé primaire
    • sous-ensemble minimal K d’attributs A = {a₁, a₂, …, aₙ} d’une relation R qui satisfait simultanément :
      1. l’unicité, qui garantit que pour tous tuples t₁, t₂ ∈ R, si t₁ ≠ t₂ alors leurs projections sur K sont différentes (t₁[K] ≠ t₂[K]),
      2. la minimalité, qui assure qu’aucun sous-ensemble propre K’ ⊂ K ne peut satisfaire la propriété d’unicité,
      3. la non-nullité, qui impose que pour tout tuple t ∈ R et tout attribut k ∈ K, la valeur t[k] ne peut pas être nulle (t[k] ≠ null),
  • Types supportés: primitifs (int, long, …), wrappers (Integer, Long, …), String, Date (Date, LocalDate, …), UUID
  • Annotation @Id sur l’attribut de l’entité

Génération automatique des Id atomiques

  • Annotation @GeneratedValue
  • Stratégies disponibles
    • IDENTITY: auto-increment natif
    • SEQUENCE: séquence BD
    • TABLE: table de séquences
    • AUTO: choix automatique

Tableau comparatif

Stratégie Performance Portabilité Batch Scalabilité Usage recommandé
IDENTITY +++ + Non + MySQL/MariaDB
SEQUENCE ++ ++ Oui ++ PostgreSQL/Oracle
TABLE + +++ Oui +++ Multi-bases
AUTO ++ ++ Selon ++ Développement
UUID + +++ Non ++++ Systèmes distribués

Notes sur la scalabilité:

  • IDENTITY: problématique en sharding
  • SEQUENCE: possible avec sequences par shard
  • TABLE: adapté au clustering
  • UUID: optimal pour systèmes distribués

Les générateurs SEQUENCE et IDENTITY

  • Les générateurs SEQUENCE et IDENTITY spécifient l’utilisation d’une séquence de base de données ou d’une colonne d’identité (plus performant, mais pas portable). .
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Getter

@Entity(name = "CustomerSeqId")
@Table(name="CUSTOMER", schema = "EX_IDSEQ")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_customer")
    @SequenceGenerator(name = "seq_customer", sequenceName = "CUSTOMER_SEQUENCE", allocationSize = 10)
    private Long id;

    private String name;
}
Figure 1: EmployeePK.java

```aigogl execute: false //| output: true //| echo: false //| label: fig-rel-employe-seqid-PK //| fig-cap: An Employee with a composite Key/Id //| vscode: {languageId: java} // PRINT SCHEMA String script=“%”;

IJava.getKernelInstance().getMagics().applyCellMagic(“rdbmsSchema”, List.of(“ex_idseq”),script); return null;


:::
::::

### Sequence et Batch

La génération de clés avec une séquence peut être optimisée en utilisant un `allocationSize` plus grand que 1.
Cela permet de pré-allouer un bloc de valeurs pour les entités, réduisant ainsi le nombre d'appels à la séquence.

```java
// Exemple de configuration optimisée
@Entity
public class Order {
    @Id
    @GeneratedValue(
        strategy = GenerationType.SEQUENCE,
        generator = "order_seq"
    )
    @SequenceGenerator(
        name = "order_seq",
        sequenceName = "ORDER_SEQ",
        initialValue = 1,
        allocationSize = 50  // Optimisation batch
    )
    private Long id;
}

générateur TABLE

Le générateur TABLE demande d’attribuer des clés primaires à l’entité en utilisant une table de base de données, c’est le plus portable mais le moins performant.

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Getter

@Entity(name = "CustomerTableId")
@Table(name="CUSTOMER", schema = "EX_IDTABLE")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "customer_table_generator")
    @TableGenerator(name = "customer_table_generator", schema = "EX_IDTABLE", table = "ID_GENERATOR_TABLE")
    private Long id;

    private String name;
}
Figure 2: EmployeePK.java

```aigogl execute: false //| output: true //| echo: false //| label: fig-rel-employe-composite-idtable //| fig-cap: An Employee with a composite Key/Id //| vscode: {languageId: java} // PRINT SCHEMA String script=“%”;

IJava.getKernelInstance().getMagics().applyCellMagic(“rdbmsSchema”, List.of(“ex_idtable”),script); return null;


:::
::::

### UUIDs

  - Les UUIDs sont des identifiants universels uniques, générés aléatoirement.
  - Avantages: unicité globale, distribution, sans coordination centrale
  - Inconvénients: taille (16 octets), performance index, non séquentiel
    - Pour la pagination ajouter un index sur un attribut date de création
  - Usage: microservices, systèmes distribués, réplication multi-maîtres

:::: {.columns}
::: {.column}

::: {#cell-fig-class-employee-uuid-PK .cell filename='null' quarto-private-1='{"key":"vscode","value":{"languageId":"java"}}' execution_count=5}

::: {#fig-class-employee-uuid-pk .cell-output .cell-output-display .cell-output-markdown}
```Java
@Getter
@Setter
@ToString
@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PROTECTED)

@Entity
@Table(name = "CUSTOMER", schema = "EX_UUID")
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID uuid;

    @Column(length = 50, nullable = false)
    @NonNull
    private String name;
}

EmployeePK.java

```aigogl execute: false //| output: true //| echo: false //| label: fig-rel-customer-uuid //| fig-cap: A Customer with a UUID //| vscode: {languageId: java} // PRINT SCHEMA String script=“%”;

IJava.getKernelInstance().getMagics().applyCellMagic(“rdbmsSchema”, List.of(“ex_uuid”),script); return null;


:::
::: {.column}







:::
::::

## Les clés composites

- Types de clés
  - **Clé atomique**
    - **Natural** (Clé naturelle) : attribut métier unique (email, SIRET)
    - **Surrogate** (Clé artificielle) : identifiant technique unique (Long, UUID)

  - **Clé composite**
    - **Natural** : combinaison d'attributs métier (année + département + matricule)
    - **Surrogate** : combinaison d'identifiants techniques (shardId + sequenceId)

- JPA propose deux approches pour gérer les clés composites:
  - `@EmbeddedId` : encapsule les attributs dans une classe dédiée
  - `@IdClass` : distribue les attributs dans l'entité

### Embedded Id

 - Utilisation de l'annotation `@EmbeddedId`
 - Encapsulation des attributs de la clé dans une classe dédiée
 - La classe embarquée doit être annotée `@Embeddable`
    - Doit implémenter Serializable
    - Nécessite equals() et hashCode()

```{java} execute: false
//| output: true
//| echo: false
//| label: fig-rel-employe-composite-id
//| fig-cap: An Employee with a composite Key/Id
//| tags: [remove-input]
//| vscode: {languageId: java}
// PRINT SCHEMA
String script="%";

IJava.getKernelInstance().getMagics().applyCellMagic("rdbmsSchema",
  List.of("ex_embedded_id"),script);
return null;
@Getter
@Setter

@Entity(name="EmployeEmbeddedId")
@Table(name="EMPLOYE", schema = "EX_EMBEDDED_ID")
public class Employee {
    @EmbeddedId
    private EmployeePK id;
    private String name;
}
Figure 3: Employee.java
@EqualsAndHashCode
@Embeddable
@AllArgsConstructor
@NoArgsConstructor
public class EmployeePK implements Serializable {
    private String department;
    private int rankInDepartment;
}
Figure 4: EmployeePK.java

Id Class

  • Utilisation de l’annotation @IdClass dans l’entité.
  • Les attributs de la clé sont définis dans l’entité
  • Une classe séparée définit la structure de la clé
  • Les noms des attributs doivent correspondre
  • Plus simple pour les requêtes JPQL

```aigogl execute: false //| output: true //| echo: false //| label: fig-rel-employe-composite-id-idclass //| fig-cap: An Employee with a composite Key/Id //| tags: [remove-input] //| vscode: {languageId: java} // PRINT SCHEMA String script=“%”;

IJava.getKernelInstance().getMagics().applyCellMagic(“rdbmsSchema”, List.of(“ex_idclass”),script); return null;


::: {#cell-fig-class-employee-idclass .cell filename='null' quarto-private-1='{"key":"vscode","value":{"languageId":"java"}}' execution_count=11}

::: {#fig-class-employee-idclass .cell-output .cell-output-display .cell-output-markdown}
```Java
@Setter
@Getter

@Entity(name="EmployeIdClass")
@Table(name="EMPLOYE", schema = "EX_IDCLASS")
@IdClass(EmployeePK.class)
public class Employee {
    @Id
    private String department;
    @Id
    private int rankInDepartment;
    private String name;
}

Employee.java

@EqualsAndHashCode
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class EmployeePK implements Serializable {
    @Id
    private String department;
    @Id
    private int rankInDepartment;
}
Figure 5: EmployeePK.java