Java Persistence API

JPA

JPA provides developers with an ORM facility for managing relational data. JPA consists of 4 areas:

  • The Java Persistence API

  • The query language

  • The Criteria API

  • ORM metadata

https://docs.oracle.com/javaee/7/tutorial/persistence-intro.htm
— Java EE Tutorial

JPA

  • JPA est une spécification d’ORM

  • Implémentations disponibles :

    • Hibernate

    • EclipseLink

    • OpenJPA

Avertissement

  • Le périmètre du standard JPA est très vaste.

  • Ce cours est uniquement une introduction à une partie de ce standard.

JDBC

  • Java DataBase Connectivity

  • API d’accès aux bases de données SQL

  • Code indépendant du type de la base de données

  • Nécessite l’écriture de code SQL

Rappels JDBC

  1. Enregistrement du driver

  2. Connection à la base de données

  3. Exécution du code SQL

java jdbc

Exemple de code

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost/test", "user", "password");
PreparedStatement stmt = connection.prepareStatement(
    "SELECT * FROM PERSON WHERE ID = ?");
stmt.setString(1, 1234L);
ResultSet rs = stmt.executeQuery();
if (rs.next()) {
    String name = rs.getString("NAME");
    Date birthDate = rs.getDate("BIRTH_DATE");
    Person person = new Person(name, birthDate);
}
if (conn != null) {
    conn.close();
}

Problèmes de JDBC

  • Bas niveau

  • Nécessite beaucoup de code répétitif

  • Gestion des exceptions

  • Libération des ressources

  • Gestion manuelle des verrous

Obtention de connection

Point à point

Gestionnaire de pilotes

Ressource commune

Source de données

driver manager flow

Problèmes du P2P

  • Inadapté pour un nombre élevé de connections

  • Dépassement du nombre de connections côté DB

    • Refus de nouvelles connections après une certaine limite

  • Temps d’ouverture d’une nouvelle connection

Source de données

  • Pour un nombre élevé de connections utilisées pour une courte durée

  • Pool de connections initialisées au démarrage du serveur d’applications

Processus

  1. Récupérer une connection de la source de données

  2. Utiliser la connection pour exécuter du code SQL

  3. Remettre la connection à disposition du pool

    • Appel de la méthode close()

Configurer une source de données

  • Paramètres : URL de la base, nombre initial de connections, nombre maximal, etc.

  • Dépendant du serveur d’applications

  • Mis à disposition dans l’arbre de ressources JNDI

Utilisation de la source de données

  1. Code explicite pour récupérer la ressource JNDI

  2. Injection via @Resource

  3. Configuration pour JPA

public class SomeRepository {

    @Resource(name="java:comp/env.jdbc/mydb")
    private DataSource dataSource;

    ...
}
datasource

Entité

An entity is a lightweight persistence domain object. An entity class represents a table in a relational database, and each entity instance corresponds to a row in that table.
https://docs.oracle.com/javaee/7/tutorial/persistence-intro001.htm#BNBQA
— Oracle Docs

Contraintes sur la classe Entité

  1. Etre annotée avec @javax.persistence.Entity

  2. Respecter le contrat d’un JavaBean

    • Constructeur public sans argument

    • Accesseurs publics

  3. Avoir un attribut/getter d’id

Champs persistés

  • Tous les champs sont persistés

  • Sauf ceux annotés avec @javax.persistence.Transient

Types autorisés pour les champs persistés

  • Types primitifs

  • Chaînes de caractères

  • Types Serializable

  • Enumérations

  • Entités

  • Collections des types ci-dessus

Champ identité

Un attribut spécifique annoté @Id référence la clé primaire de l’entité

Génération de la clé

Annotation @javax.persistence.GeneratedValue sur l’attribut annoté par @Id

Stratégies de génération

Auto-incrémentation

GenerationType.IDENTITY

Séquence
  • GenerationType.SEQUENCE

  • @SequenceGenerator

Table
  • GenerationType.TABLE

  • @TableGenerator

Exemple d’entité JPA

@Entity
public class Book {

    @Id @GeneratedValue(strategy=IDENTITY)
    private long id;
    private String isbn;
    private String title;
    private String description;
    private Date publicationDate;
    private Author author;

    // Getters and setters
}

Emplacement des annotations

  • De manière consistante

    • Soit sur les attributs

    • Soit sur les getters

Mapping table-entité

  • Par défaut :

    Nom de l’entité

    nom de la table

    Nom de l’attribut

    nom de la colonne

Personnalisation du nom

  • @javax.persistence.Table(name = "PERSON")

  • @javax.persistence.Column(name = "FIRST_NAME")

Autres personnalisations

Les annotations précédentes permettent également :

  • Spécification d’un schéma

  • Définition des contraintes (DDL)

Exemple d’entité personnalisée

@Entity
@Table(name="T_BOOK")
public class Book {

    @Id @GeneratedValue(strategy=IDENTITY)
    private long id;
    private String isbn;
    private String title;
    private String description;
    @Column(name="PUBLICATION_DATE")
    private Date publicationDate;
    private Author author;

    // Getters and setters
}

Gestionnaire d’entités

The EntityManager API creates and removes persistent entity instances, finds entities by the entity’s primary key, and allows queries to be run on entities.
https://docs.oracle.com/javaee/7/tutorial/persistence-intro003.htm#JEETT01161
— Oracle Docs

Fonctionnalités du gestionnaire d’entités

  • Gestion du cycle de vie

  • Recherche d’une entité par sa clé primaire

  • Synchronisation avec la base de données

  • Exécution de requêtes avec l’API Criteria

  • Exécution de requêtes JPQL (& SQL)

  • etc.

Cycle de vie des entités

entity lifecycle

Configuration du gestionnaire d’entités

  • Initialisé par le serveur d’apps

  • Fichier persistence.xml

  • Situé dans WEB-INF/classes/META-INF

Exemple de persistence.xml

<persistence>
  <persistence-unit name="PeopleManagement">
    <!-- Non-default data source
    <jta-data-source>jdbc/people</jta-data-source>
    -->
    <class>ch.frankel.Person</class>
    <class>ch.frankel.Teacher</class>
    <class>ch.frankel.Student</class>
  </persistence-unit>
</persistence>

Utilisation du gestionnaire d’entités

@Stateless
public class Service {

    @PersistenceContext
    private EntityManager em;
}

Contrainte sur l’injection

Uniquement pour des objets dont le cycle de vie est gérée par le serveur d’applications

Transaction

A transaction symbolizes a unit of work performed within a DBMS against a database, and treated in a coherent and reliable way independent of other transactions.
https://en.wikipedia.org/wiki/Database_transaction
— Wikipedia

Contrainte

Transaction nécessaire pour les opérations d’écriture

Gestion des transactions par l’API

transaction.begin();
em.persist(new Person());
transaction.commit();

Utilisation de l’objet de transaction

@Stateless
public class Service {

    @Resource
    private UserTransaction userTransaction;
}

Utilisation implicite

@Stateless
public class Service {

    @Transactional
    public void doInTransaction() {
        // Everything here will run in a transaction
    }
}

Règles

  • Commit à la fin de la méthode pour une exécution normale

  • En cas d’exception runtime, rollback

  • @Transactional(rollbackFor=MyException.class)

Note sur Java Transaction API

  • Permet les transactions distribuées

    • 2-Phases Commit

  • Nécessite un pilote JDBC XA

Enterprise Java Bean

An EJB component is a body of code that has fields and methods to implement modules of business logic.
https://javaee.github.io/tutorial/overview008.html#BNACL
— Oracle doc
@Stateless
public class Service {

    // Business logic
}
@Stateless
public class AccountService {

    @Transactional
    public void transfer(Account source, Account target, Money amount) {
        // Business logic
    }
}

Note on Java Transaction API

  • Permet les transactions distribuées

    • 2-Phases Commit

  • Nécessite un pilote JDBC XA

JPQL

The query language allows you to write portable queries that work regardless of the underlying data store. It uses the abstract persistence schemas of entities, including their relationships, for its data model and defines operators and expressions based on this data model.
http://bit.ly/2jbjNft
— Oracle Docs

Rappel JDBC

PreparedStatement stmt = conn.prepareStatement(
    "SELECT * FROM PERSON " +
    "WHERE FIRST_NAME LIKE ? AND LAST_NAME LIKE ?");
stmt.setString(1, firstName);
stmt.setString(2, lastName);
ResultSet rs = stmt.executeQuery();

Exemple JPQL

List persons =
    em.createQuery(
      "SELECT p FROM Person p " +
      "WHERE p.firstName LIKE :firstName "
      "AND p.lastName LIKE :lastName")
      .setParameter("firstName", firstName)
      .setParameter("lastName", lastName)
      .getResultList();

API Criteria

The Criteria API is used to define queries for entities and their persistent state by creating query-defining objects. Criteria queries are written using Java programming language APIs, are typesafe, and are portable
https://docs.oracle.com/javaee/7/tutorial/persistence-criteria.htm#GJITV
— Oracle Docs

Problèmes du JPQL

  • Typage faible

  • Paramètres optionnels :

    • Concaténation de chaînes

criteria api
Person person = em.find(Person.class, 1234L);
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
cq.from(Person.class);
TypedQuery<Person> q = em.createQuery(cq);
List<Person> allPersons = q.getResultList();

Clause WHERE

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
Root<Person> person = cq.from(Person.class);
cq.where(cb.like(person.get("firstName"), firstName),
         cb.like(person.get("lastName"), lastName));
TypedQuery<Person> q = em.createQuery(cq);
List<Person> persons = q.getResultList();

Méta-modèle

For each entity class in a particular package, a metamodel class is created with a trailing underscore and with attributes that correspond to the persistent fields or properties of the entity class.
https://docs.oracle.com/javaee/7/tutorial/persistence-criteria002.htm#sthref2133
— Oracle Docs

Génération du méta-modèle

  • Soit :

    • Manuelle

    • Via l’implémentation spécifique

  • Devrait être synchronisée avec chaque changement de code

    • Via un outil de build (Maven)

Exemple avec TomEE

javac -classpath $TOMEE_HOME/lib/openjpa-3.1.0.jar:\
                 $TOMEE_HOME/lib/javaee-api-8.0-3.jar \
      -Aopenjpa.metamodel=true \
      -Aopenjpa.source=8 ch/hesge/javaee/Book.java

Typesafe WHERE

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> cq = cb.createQuery(Person.class);
Root<Person> person = cq.from(Person.class);
cq.where(cb.like(person.get(Person_.firstName), firstName),
         cb.like(person.get(Person_.lastName), lastName));
TypedQuery<Person> q = em.createQuery(cq);
List<Person> persons = q.getResultList();