
Définition des entités JPA (`@Entity`, `@Id`, `@GeneratedValue`, etc.)
Apprenez à mapper vos objets Java aux tables de base de données avec JPA et Spring Boot en utilisant les annotations essentielles : @Entity, @Id, @GeneratedValue, @Table, @Column.
Le pont entre objets et tables : qu'est-ce qu'une entité JPA ?
Au coeur de JPA (Jakarta Persistence API) et donc de Spring Data JPA, se trouve le concept d'entité. Une entité est une simple classe Java (un POJO - Plain Old Java Object) qui représente une table dans votre base de données relationnelle. Chaque instance de cette classe correspond à une ligne dans cette table, et les champs (ou propriétés) de la classe correspondent aux colonnes de la table.
Le rôle de JPA (et de son implémentation sous-jacente, généralement Hibernate dans un contexte Spring Boot) est d'assurer le mapping objet-relationnel (ORM), c'est-à-dire la traduction transparente entre vos objets Java et les enregistrements de la base de données. Pour indiquer à JPA qu'une classe est une entité et comment mapper ses champs aux colonnes, nous utilisons un ensemble d'annotations standard définies par la spécification JPA.
Ce chapitre se concentre sur les annotations les plus fondamentales nécessaires pour définir une entité : @Entity pour marquer la classe, @Id pour identifier la clé primaire, et @GeneratedValue pour gérer la génération automatique des clés.
Déclarer une entité : l'annotation `@Entity`
La première étape pour définir une entité est d'annoter la classe Java correspondante avec @jakarta.persistence.Entity (ou javax.persistence.Entity pour les anciennes versions de JPA). Cette annotation signale au fournisseur de persistance (Hibernate) que cette classe doit être gérée comme une entité et qu'il doit s'attendre à trouver une table correspondante dans la base de données.
Par défaut, JPA suppose que le nom de la table dans la base de données est le même que le nom de la classe (insensible à la casse selon les SGBD). Par exemple, une classe Product sera mappée par défaut à une table nommée PRODUCT.
package com.myapp.model;
import jakarta.persistence.Entity;
@Entity // Marque cette classe comme une entité JPA
public class Product {
// ... champs, constructeurs, getters, setters ...
}
Si le nom de votre table dans la base de données est différent du nom de la classe, ou si vous voulez spécifier d'autres détails au niveau de la table (comme le schéma ou des contraintes d'unicité), vous pouvez utiliser l'annotation @jakarta.persistence.Table en complément de @Entity.
package com.myapp.model;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
@Entity
@Table(name = "products_catalog", // Nom personnalisé de la table
schema = "inventory", // Schéma optionnel
uniqueConstraints = @UniqueConstraint(columnNames = {"sku"}) // Contrainte d'unicité sur la colonne 'sku'
)
public class Product {
// ...
}
Une classe entité doit respecter certaines règles : elle doit avoir un constructeur sans argument (qui peut être public ou protégé ; il est souvent généré implicitement si aucun autre constructeur n'est défini) et elle ne doit pas être déclarée final. Ses champs persistants ne doivent pas non plus être final.
Identifier chaque enregistrement : la clé primaire avec `@Id`
Chaque table dans une base de données relationnelle doit avoir une clé primaire (Primary Key - PK), une colonne (ou un ensemble de colonnes) dont la valeur identifie de manière unique chaque ligne de la table. De même, chaque entité JPA doit avoir un champ (ou un ensemble de champs) qui correspond à cette clé primaire.
Pour marquer un champ comme étant la clé primaire de l'entité, on utilise l'annotation @jakarta.persistence.Id.
package com.myapp.model;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
@Entity
public class User {
@Id // Marque ce champ comme clé primaire
private Long id;
private String username;
// ... autres champs ...
// Constructeur sans argument (requis par JPA)
public User() {}
// Getters et Setters...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// ...
}
Le type de la clé primaire est souvent un type numérique (Long, Integer) mais peut aussi être une chaîne (String), un UUID (java.util.UUID), ou même un objet composite (en utilisant @EmbeddedId ou @IdClass, des concepts plus avancés).
Génération automatique des clés : `@GeneratedValue`
Il est rare de vouloir attribuer manuellement la valeur de la clé primaire à chaque nouvelle entité. Il est beaucoup plus courant et sûr de laisser la base de données ou le framework générer automatiquement une valeur unique pour l'identifiant. JPA fournit l'annotation @jakarta.persistence.GeneratedValue pour cela, qui est généralement placée sur le même champ que @Id.
L'annotation @GeneratedValue prend un argument principal, strategy, qui définit la méthode de génération de la clé. Les stratégies principales (définies dans l'enum jakarta.persistence.GenerationType) sont :
AUTO(par défaut) : Laisse le fournisseur de persistance (Hibernate) choisir la stratégie la plus appropriée en fonction du dialecte de la base de données sous-jacente. C'est souventSEQUENCE(si supporté, comme avec PostgreSQL, Oracle) ouIDENTITY(si les séquences ne sont pas le choix préféré ou non supportées nativement pour les ID, comme avec MySQL). Bien que portable, ce choix manque d'expliciteté.IDENTITY: S'appuie sur une colonne d'identité spéciale de la base de données qui génère automatiquement une valeur séquentielle (par exemple,AUTO_INCREMENTdans MySQL,SERIALouIDENTITYdans PostgreSQL,IDENTITYdans SQL Server). Inconvénient : La valeur de l'ID n'est disponible qu'après l'exécution de l'instruction SQLINSERT, ce qui peut limiter certaines optimisations comme le batching d'inserts par Hibernate.SEQUENCE: Utilise une séquence de base de données pour générer les identifiants. Les séquences sont des objets de base de données distincts qui fournissent des valeurs uniques. C'est souvent la stratégie recommandée pour les bases de données qui la supportent bien (PostgreSQL, Oracle). Elle permet à Hibernate d'obtenir l'ID avant l'INSERT, ce qui est meilleur pour le batching. Vous pouvez personnaliser la séquence utilisée avec l'annotation@SequenceGenerator.TABLE: Utilise une table séparée dans la base de données pour simuler une séquence. Cette approche est portable mais généralement moins performante queIDENTITYouSEQUENCEet est rarement utilisée aujourd'hui.
Exemple avec IDENTITY (typique pour MySQL) :
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;Exemple avec SEQUENCE (typique pour PostgreSQL/Oracle, avec personnalisation) :
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq_gen")
@SequenceGenerator(name = "product_seq_gen", // Nom du générateur (référence interne JPA)
sequenceName = "product_id_seq", // Nom de la séquence DANS la base de données
allocationSize = 1) // Combien d'ID réserver à chaque appel à la séquence (1 par défaut)
private Long id;Le choix de la stratégie dépend fortement de la base de données cible et des besoins de performance (notamment si le batching d'inserts est important).
Mapper les autres champs : `@Column` et conventions
Pour les champs de l'entité qui ne sont pas la clé primaire, JPA applique également des conventions de mapping par défaut. Un champ Java (par exemple, private String productName;) sera généralement mappé à une colonne portant le même nom (product_name si une stratégie de nommage "snake_case" est active via la configuration Spring/Hibernate, ou productName sinon).
Pour personnaliser ce mapping ou ajouter des contraintes au niveau de la colonne, on utilise l'annotation @jakarta.persistence.Column.
@Entity
@Table(name = "app_users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "login", nullable = false, unique = true, length = 50)
private String username; // Mappé à la colonne 'login', non null, unique, max 50 chars
@Column(name = "email_address") // Mappé à la colonne 'email_address'
private String email;
@Column(nullable = true) // Explicitly allow nulls (default anyway)
private Integer age;
// Getters, Setters, etc.
}
L'annotation @Column possède de nombreux attributs utiles :
name: Spécifie le nom exact de la colonne dans la base de données.nullable: Booléen (true/false) indiquant si la colonne accepte les valeursNULL(par défauttrue).unique: Booléen indiquant si une contrainte d'unicité doit être appliquée à cette colonne (par défautfalse).length: Pour les colonnes de type chaîne (VARCHAR), spécifie la longueur maximale (par défaut souvent 255).precisionetscale: Pour les types numériques décimaux (DECIMAL,NUMERIC), définissent le nombre total de chiffres et le nombre de chiffres après la virgule.insertableetupdatable: Booléens (par défauttrue) permettant d'exclure la colonne des instructions SQLINSERTouUPDATEgénérées par JPA.
Il existe de nombreuses autres annotations pour des mappings plus spécifiques (@Transient pour ignorer un champ, @Temporal pour les anciens types Date/Calendar, @Lob pour les grands objets, @Enumerated pour les enums, et toutes celles liées aux relations entre entités comme @ManyToOne, @OneToMany, etc., qui seront vues plus tard).
Conclusion : la base du mapping ORM
La définition correcte des entités JPA à l'aide des annotations @Entity, @Id, @GeneratedValue et @Column est la pierre angulaire de l'utilisation de Spring Data JPA. Ces annotations fournissent les métadonnées essentielles permettant au fournisseur de persistance (Hibernate) de comprendre comment mapper vos objets Java aux structures de votre base de données relationnelle.
En maîtrisant ces annotations de base, vous êtes en mesure de définir la structure de persistance de votre application et de permettre à Spring Data JPA de prendre en charge la communication avec la base de données, vous libérant ainsi pour vous concentrer sur la logique métier et l'utilisation des interfaces Repository pour interagir avec vos données.