Introduction à Jakarta Persistence API (JPA)

Université de Toulon

LIS UMR CNRS 7020

2026-10-01

Magics

registered line magics: 
    - listLineMagic
    - jars
    - cmd
    - read
    - git-graph-mermaid
    - where, which
    - commonshellcmd
    - printWithName
    - listMagic, list
    - addMavenDependencies, maven, addMavenDependency
    - pom, loadFromPOM
    - tableSchema
    - load
    - classpath
    - javadoc-html
    - write
    - class-info
    - reload-class
    - listCellMagic
    - classpath-snapshot
    - printerPrefix
    - addMavenRepo, mavenRepo
    - classDiagram
registered cell magics: 
    - commonshell
    - javasrcList
    - javasrcClassByName
    - classDiagram
    - mycompile
    - timeit, timeIt, time
    - plantUMLFile
    - tableSchema
    - javasrcMethodByAnnotationName
    - sqlAsTable
    - javasrcJavadoc
    - javasrcMethodByName
    - javasrcInterfaceByName
    - write
    - rdbmsSchema
    - compile
    - shell
    - benchmark
    - pom, loadFromPOM
    - plantUML
    - javasrcConstructorByName
    - javasrcFieldByName

Exemples

Les exemples présentés sont disponibles dans l’entrepôt: https://github.com/ebpro/sample-hellojpa/

Un exemple complet de base est disponible dans l’entrepôt: https://github.com/ebpro/minimal-jpa/

Introduction à Jakarta Persistence API (JPA)

  • Jakarta Persistence API (JPA) est une spécification Java pour la gestion de la persistance des données dans les applications Jakarta EE.
  • Objectif : faciliter le mapping entre les objets Java et les tables de base de données relationnelles.
  • Simplifie les opérations CRUD (Create, Read, Update, Delete) sur les objets persistants.
  • Avantages : portabilité des applications, abstraction de la couche de persistance, réduction de la dépendance au code SQL spécifique au fournisseur de base de données.
  • Exemple : utilisation de JPA avec des outils populaires tels que Hibernate, EclipseLink, ou OpenJPA pour simplifier le développement d’applications Jakarta EE.

Spécification

  • JPA : spécification Java pour la liaison entre le modèle orienté objet de Java et le modèle relationnel (ORM).
  • Version actuelle : JPA 3.1.
  • Repose sur JDBC (Java Database Connectivity): génère automatiquement les requêtes SQL et leur mise en oeuvre.
  • Offre une solution normalisée et robuste pour la gestion des données dans les applications Java.

En pratique

Il faut donc ajouter les éléments suivant au pom.xml de Maven.

  <!-- L'API Standard -->
  <!-- a priori inclue par transitivité avec l'une des suivantes -->
  <dependency>
    <groupId>jakarta.persistence</groupId>
    <artifactId>jakarta.persistence-api</artifactId>
    <version>3.1.0</version>
  </dependency>

  <dependency>
    <groupId>org.hibernate.orm</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>6.3.1.Final</version>
  </dependency>

  <!-- L'implantation de référence -->
  <!--
  <dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>eclipselink</artifactId>
    <version>4.0.1</version>
  </dependency>
  -->

JPA possède trois composantes principales

  • La description des “entités” persistentes.
  • L’interaction de base avec la persistence via les les opérations CRUD (Create, Read, Update, Delete).
  • L’interrogation native ou via un langage dédié.

Pour utiliser JPA, il faut une base de données relationnelle. Nous utiliserons ici PostgreSQL via l’image Docker officielle.

Les exemples sont disponibles sur ce repository GitHub.

Les Entités dans Jakarta Persistence API (JPA)

  • Entité : Une classe dont les instances doivent être persistantes.
  • Critères pour être une entité :
    • Annotée avec @Entity.
    • Au moins un attribut annoté avec @Id.
    • Doit avoir au moins un constructeur sans paramètre (au plus protected).
  • Association avec une relation :
    • La classe est associée à une relation du même nom.
    • Cette relation possède une clé primaire, atomique ou composite.

Premier exemple d’entité

La classe suivante est donc entité JPA :

@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Setter
@ToString
@Getter

@Entity
public class Customer {
    @Id
    @GeneratedValue
    private long id;

    @NonNull
    private String email;

    private String name;
}

associée à la relation :

et à la table :

Column Type Nullable Default Remarks
id (PK) (UNIQUE) int8(19) NOT NULL
email varchar(255)
name varchar(255)

Primary key: id

Indexes: - customer_pkey (id) UNIQUE

CREATE TABLE ex_simple.customer (
    id int8(19) NOT NULL,
    email varchar(255),
    name varchar(255),
    PRIMARY KEY (id)
);
id email name

Exemple étendu d’entité

Le mapping peut être contrôllé finement par un ensemble d’annotations. La classe suivante illustre celles de base.

@Getter
@Setter
@ToString
@Builder
@AllArgsConstructor
@RequiredArgsConstructor(staticName = "of")
@NoArgsConstructor(access = AccessLevel.PROTECTED)

@Entity
@Table(name = "CUSTOMER",
       indexes = {@Index(name = "idx_email", columnList = "email", unique = true)})
public class Customer {
    @Builder.Default
    @Column(updatable = false)
    private LocalDateTime creationDate = LocalDateTime.now();
    
    @Id
    @Column(name = "ID")
    @GeneratedValue
    private long id;

    @Column(length = 50, nullable = false, unique = true)
    @NonNull
    private String email;

    @Column(length = 50, nullable = false)
    private String firstname;

    @Column(length = 50, nullable = false)
    private String lastname;

    @Transient
    private String displayName;

    @Setter
    private LocalDate birthDate;

    @Lob
    @Basic(fetch = FetchType.LAZY)
    @ToString.Exclude

    private byte[] photo;

    @Builder.Default
    //@Enumerated(EnumType.STRING)
    private Status status = Status.LEAD;

    @Version
    protected Integer version;

    public enum Status {ACTIVE, LEAD}
}

La table correspondante

Column Type Nullable Default Remarks
id (PK) int8(19) NOT NULL
name varchar(50) NOT NULL
bio_brief varchar(255)
bio_extended oid(10)
id (PK) int8(19) NOT NULL
name varchar(255)
id (PK) int8(19) NOT NULL
name varchar(255)
id (PK) int8(19) NOT NULL
name varchar(255)
id (PK) int8(19) NOT NULL
name varchar(255)
id (PK) int8(19) NOT NULL
email (UNIQUE) varchar(255)
name varchar(255)
uuid (PK) uuid(2147483647) NOT NULL
name varchar(50) NOT NULL
birthdate date(13)
status int2(5)
version int4(10)
id (PK) int8(19) NOT NULL
creationdate timestamp(29)
email (UNIQUE) varchar(50) NOT NULL
firstname varchar(50) NOT NULL
lastname varchar(50) NOT NULL
photo oid(10)

Primary key: id, id, id, id, id, id, uuid, id

Indexes: - customer_email_key (email) UNIQUE - customer_pkey (id, id, id, id, id, uuid, id, id) UNIQUE

CREATE TABLE customer (
    id int8(19) NOT NULL,
    name varchar(50) NOT NULL,
    bio_brief varchar(255),
    bio_extended oid(10),
    id int8(19) NOT NULL,
    name varchar(255),
    id int8(19) NOT NULL,
    name varchar(255),
    id int8(19) NOT NULL,
    name varchar(255),
    id int8(19) NOT NULL,
    name varchar(255),
    id int8(19) NOT NULL,
    email varchar(255),
    name varchar(255),
    uuid uuid(2147483647) NOT NULL,
    name varchar(50) NOT NULL,
    birthdate date(13),
    status int2(5),
    version int4(10),
    id int8(19) NOT NULL,
    creationdate timestamp(29),
    email varchar(50) NOT NULL,
    firstname varchar(50) NOT NULL,
    lastname varchar(50) NOT NULL,
    photo oid(10),
    PRIMARY KEY (id, id, id, id, id, id, uuid, id)
);
birthdate status version id creationdate email firstname lastname photo

Annotations de base pour la Persistance

  • @Entity: Indique une classe entité.
  • @Id: Définit la clé primaire.
  • @GeneratedValue: Contrôle la génération de clés.
  • @Table: Contrôle le mapping de la table.
  • @Column: Contrôle les colonnes.
  • @Basic: Pour les attributs simples.
  • @Transient: Non persistant.
  • @Lob: Pour les grands objets.
  • @Version: Verrouillage optimiste.

Le gestionnaire d’entités

Exemple de persistence.xml

EM et persistence d’une entité

  • Utilisation de l’EntityManagerFactory
    • Obtention d’un EntityManager via l’EntityManagerFactory, en spécifiant le nom de la persistence unit.
    • La génération automatique du schéma a lieu lors de la première utilisation.
  • Opérations avec l’EntityManager
    • Commencer une transaction avec begin, persister une instance avec persist, terminer avec commit ou rollback.
    • L’EM met automatiquement à jour l’entity avec l’ID généré par la base de données.
    • L’EM : utilisé localement et fermé après utilisation (AutoCloseable).
try (EntityManagerFactory emf = Persistence.createEntityManagerFactory("hellojpa-pu"))
{
    try (EntityManager entityManager = emf.createEntityManager()) {
    }
}

Usage avec un Singleton EMF

EntityManagerFactory emf = DatabaseManager.getEntityManagerFactory();
Logger log = LoggerFactory.getLogger("notebook");

ce singleton retourne l’instance de l’EMF qui n’est qu’à la première demande.

import fr.univtln.bruno.demos.jpa.hello.samples.ex_simple.*;

try (EntityManager entityManager = emf.createEntityManager();) {
        entityManager.getTransaction().begin();

        Customer customer = Customer.of("pierre.durand@ici.fr");
        log.info("BEFORE PERSIST "+customer.toString());

        entityManager.persist(customer);
        log.info("AFTER PERSIST: "+customer.toString());

        entityManager.getTransaction().commit();
}
12:24:22.739 [IJava-executor-0] INFO  notebook -- BEFORE PERSIST Customer(id=0, email=pierre.durand@ici.fr, name=null)
12:24:22.779 [IJava-executor-0] INFO  notebook -- AFTER PERSIST: Customer(id=1, email=pierre.durand@ici.fr, name=null)

Mise à jour

  • Mise à jour implicite
  • Transaction nécessaire pour les mises à jour
long newId;
Customer customer = Customer.of("marie.dupond@la.fr");

try (EntityManager entityManager = emf.createEntityManager();) {
    entityManager.getTransaction().begin();
    entityManager.persist(customer);

    customer.setName("Marie Dupond");

    entityManager.getTransaction().commit();
    log.info("AFTER COMMIT: "+customer.toString());
}

newId=customer.getId();
12:24:22.944 [IJava-executor-0] INFO  notebook -- AFTER COMMIT: Customer(id=2, email=marie.dupond@la.fr, name=Marie Dupond)

Find by Id

try (EntityManager entityManager = emf.createEntityManager();) {
    Optional<Customer> customer = Optional.ofNullable(entityManager.find(Customer.class, newId));

    if (customer.isEmpty())
        log.info("Id %d not in database.".formatted(newId));
    else {
        log.info("Search cust. Id %d: %s".formatted(newId,customer));
     }
}
12:24:23.126 [IJava-executor-0] INFO  notebook -- Search cust. Id 2: Optional[Customer(id=2, email=marie.dupond@la.fr, name=Marie Dupond)]

Suppression

  • remove supprimer une entité de la base de données.
try (EntityManager entityManager = emf.createEntityManager();) {
    Optional<Customer> customer = Optional.ofNullable(entityManager.find(Customer.class, newId));

    if (customer.isEmpty())
        log.error("Id %d not in database.".formatted(newId));
    else {
        log.info("Removed Id %d from the database.".formatted(newId));
        entityManager.getTransaction().begin();
        entityManager.remove(customer.get());
        entityManager.getTransaction().commit();
    }
}
12:24:23.228 [IJava-executor-0] INFO  notebook -- Removed Id 2 from the database.

Fonctions supplémentaires de l’EntityManager

  • merge: Attache un objet au contexte de persistence (par son ID).
  • refresh: Annule les modifications en cours de transaction sur une entité.
  • detach: Indique à l’EM de ne plus gérer une entité.
Customer customer = Customer.of("???");
customer.setId(newId);
log.info("BEFORE MERGE "+customer.toString());

try (EntityManager entityManager = emf.createEntityManager();) {
    entityManager.getTransaction().begin();
    customer =  entityManager.merge(customer);
    entityManager.refresh(customer);
    entityManager.getTransaction().commit();
}

log.info("AFTER MERGE+REFRESH "+customer.toString());
12:24:23.484 [IJava-executor-0] INFO  notebook -- BEFORE MERGE Customer(id=3, email=???, name=null)
12:24:23.538 [IJava-executor-0] INFO  notebook -- AFTER MERGE+REFRESH Customer(id=3, email=pierre.durand@ici.fr, name=null)

Conseils pour l’utilisation de JPA avec Lombok

  • Attention avec @EqualsAndHashCode: Vérifier les implications avec les relations Hibernate, voir https://thorben-janssen.com/ultimate-guide-to-implementing-equals-and-hashcode-with-hibernate/
  • Éviter d’utiliser @Data: Contrôler précisément les méthodes générées pour éviter des comportements inattendus.
  • Exclure les attributs “Lazy” de @ToString: Prévenir les problèmes de chargement paresseux inattendus lors de l’affichage.
  • Ajouter @NoArgsConstructor(access= AccessLevel.PROTECTED): Utiliser en conjonction avec @Builder ou @AllArgsConstructor pour garantir des comportements de construction cohérents.