L'idée principale de JPA est que le développeur n'ait besoin que de travailler avec de simples instances de classes et non pas avec des requêtes SQL dont il doit ensuite parser le résultat. Il y aura bien évidemment en arrière-plan un moteur de persistence (tel que Hibernate ou Toplink) mais dont le fonctionnement sera totalement transparant.

Pour cette démo, il y aura deux tables :

  • Author : l'auteur d'un livre (nom, prénom, date de naissance)
  • Book : le livre en question (titre, auteur)

Cela correspond à deux classes Java au sens propre du terme. Afin qu'elles puissent être utilisées comme des entités, il faut leur définir les annotations nécessaires.

@Entity
@Table(name="Author")
public class Author
{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="idAuthor")
    private int _id;
 
    @Column(name="nameAuthor")
    private String _name;
 
    @Column(name="firstNameAuthor")
    private String _firstName;
 
    @Column(name="birthDateAuthor")
    @Temporal(TemporalType.DATE)
    private Date _birthDate;
 
    //getters & setters...
}

Quelques explications sur les annotations présentes ci-dessus :

  • @Entity : comme dit, cela sert à rendre la classe 'utilisable' par le gestionnaire de persistence.
  • @Table : permet de définir le nom de la table dans la base de données.
  • @Column : permet de définir le nom de la colonne dans la base de données.
  • @Id : définit le champs comme faisant partie de la clé primaire.
  • @GeneratedValue : définit le mode de génération automatique de la clé primaire (ici ce sera un auto_incremant dans MySQL par exemple).
  • @Temporal : définit le type de donnée temporelle (date, time ou timestamp).

Il existe un bon nombre d'annotations différentes et je me borne ici aux plus basiques. Pour ceux qui s'y intéressent, vous en trouverez un peu plus sur cette page !

La classe Book se présente comme ceci :

@Entity
@Table(name="Book")
@NamedQueries({
    @NamedQuery(name="Book.selectAll", query="SELECT b FROM Book b ORDER BY b._title")
})
public class Book
{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="idBook")
    private int _id;
 
    @Column(name="titleBook")
    private String _title;
 
    @ManyToOne
    @JoinColumn(name="idAuthor")
    private Author _author;
 
    //getters & setters...
}

Un petit mot sur les annotations @ManyToOne et @JoinColumn : elles permettent de définir une liaison de type foreign key avec la classe Author à l'intérieur de la base de données.

Autre annotation intéressante : @NamedQuery qui permet de définir des requêtes JPA-SQL. C'est un format de requête standardisé qui permet au gestionnaire de persistence de la convertir ensuite au format de la base de données (Oracle, MySQL, ...). Un avantage non négligeable est que cela permet également de pouvoir vérifier la validité toutes les requêtes d'un coup : s'il y a une erreur (du style faute de frappe pour le nom d'un champs), elle sera directement signalée !

Maintenant que les entités sont prêtes, il faut encore définir un fichier de configuration (une PersistenceUnit) qui va définir diverses propriétés :

  • Moteur de persistence (Hibernate, Toplink, ...)
  • URL de la base de données
  • Nom d'utilisateur et mot de passe
  • Stratégie de génération des tables (création si inexistante, suppression puis création, rien)
  • Les classes entités qui seront gérés par le moteur de persistence

NetBeans permet de générer automatiquement ce fichier (New > Other > Persistence > PersistenceUnit). Il se présente de la forme suivante :

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="BookAuthorPU" transaction-type="RESOURCE_LOCAL">
    <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <class>ch.capi.entities.Author</class>
    <class>ch.capi.entities.Book</class>
    <properties>
      <property name="toplink.jdbc.user" value="sample"/>
      <property name="toplink.jdbc.password" value="sample"/>
      <property name="toplink.jdbc.url" value="jdbc:derby://localhost:1527/BookAuthor"/>
      <property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
      <property name="toplink.ddl-generation" value="drop-and-create-tables"/>
    </properties>
  </persistence-unit>
</persistence>

Maintenant tout est prêt pour se plonger dans le code et jongler avec les données. Le gestionnaire de persistence est représenté par une interface nommée EntityManager qui contient les méthodes suivantes :

  • persist : permet de rendre un objet persistent (donc de l'enregistrer dans la base de données)
  • remove : suppression d'un objet persistent
  • find : récupération d'un objet persistent via sa clé primaire

Pour récupérer une instance de l'interface EntityManager, le bout de code est tout simple :

Persistence.createEntityManagerFactory("BookAuthorPU");
EntityManagerFactory factory = Persistence.createEntityManagerFactory("BookAuthorPU");
EntityManager entityManager = factory.createEntityManager();

Et par la suit il est très simple de créer un objet et de l'enregistrer dans la base de données :

Author a = new Author();
a.setName("Tabin");
a.setFirstName("Cédric");
a.setBirthDate(new Date());
 
Book b = new Book();
b.setTitle("TheFlashBook");
b.setAuthor(a);
 
entityManager.persist(a);
entityManager.persist(b);

Il manque une dernière étape afin que les objets soient "poussés" dans la base de données. JPA gérant un cache par défaut, il faut le forcer à se lier à la base de données en passant par une transaction :

entityManager.getTransaction().begin();
entityManager.flush();
entityManager.getTransaction().commit();

Et voila ! Le moteur de persistence va s'occuper de créer les tables et d'effectuer les requêtes SQL à la base de données. Il est très simple ensuite de récupérer un objet via son identifiant :

Book myBook = entityManager.find(Book.class, 1); //récupère le livre avec l'ID 1

Et idéalement, lorsque l'EntityManager n'est plus utilisé :

entityManager.close();

J'ai mis un peu plus de détails et de fioritures dans le projets NetBeans attaché à ce billet. Pour le faire fonctionner, il faut juste créer une base de données avec JavaDB :

  1. Aller dans l'onglet Services > Clique droit sur JavaDB > Create Database
  2. URL : jdbc:derby://localhost:1527/BookAuthor
  3. Username : sample, Password : sample

Il se peut également qu'il faille ajouter les librairies suivantes :

  • TopLink Essentials
  • Java DB Driver