Les clés primaires avec JPA

Université de Toulon

LIS UMR CNRS 7020

2025-01-30

Softwares

Source
Branch
  • develop (c88a48e)
  • 2024/03/08 11:41:10
Java
  • OpenJDK Temurin-21.0.5+11
  • Apache Maven 3.9.9
Docker
  • Client: 27.3.1 - buildx v0.18.0 - compose v2.30.3
  • Server: 27.3.1

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
Figure 2: An Employee with a composite Key/Id

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.

// 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 3: EmployeePK.java
Figure 4: An Employee with a composite Key/Id

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
@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;
}
Figure 5: EmployeePK.java
Figure 6: A Customer with a UUID

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()

Figure 7: An Employee with a composite Key/Id

@Getter
@Setter

@Entity(name="EmployeEmbeddedId")
@Table(name="EMPLOYE", schema = "EX_EMBEDDED_ID")
public class Employee {
    @EmbeddedId
    private EmployeePK id;
    private String name;
}
Figure 8: Employee.java
@EqualsAndHashCode
@Embeddable
@AllArgsConstructor
@NoArgsConstructor
public class EmployeePK implements Serializable {
    private String department;
    private int rankInDepartment;
}
Figure 9: 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

Figure 10: An Employee with a composite Key/Id

@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;
}
Figure 11: Employee.java
@EqualsAndHashCode
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class EmployeePK implements Serializable {
    @Id
    private String department;
    @Id
    private int rankInDepartment;
}
Figure 12: EmployeePK.java