Skip to content

Persistance des objets

Dans cette partie, nous illustrons la persistance à travers l'application room-manager que vous allez devoir compléter. La première partie de cette séance présente l'application et les différents concepts à apprendre pour la comprendre. La seconde partie présente la persistance (que vous avez déjà vu dans d'autres matières) en Java EE.

Présentation du projet Room-Manager

  • v1.5 : version ligne de commande.
  • Une version de base vous est fournie ; vous devrez la compléter.

Objectifs pédagogique

  • Manipuler les concepts logiciels modernes :
    • injection de dépendances avec Google Guice
    • persistance JavaEE JPA avec Hibernate
  • Utiliser l'outillage JavaEE (Maven, CheckSyle, PMD, JUnit, Cobertura, etc...)
  • Utiliser des bibliothèques tierces open-source (slf4j, etc...)

Démarrer l'application avec le livrable

Le binaire de l'application est également livrée sous forme d'archive .zip: room-manager-1.5-bin.zip`. Pour l'installer, il suffit de décompresser l'archive (voir le contenu ci-après).

Sous Windows : double-clic sur l'icône du fichier .jar (si installation de Java par l'installeur).

En ligne de commande (bash ou Dos) :

$ cd <room-manager directory>
$ java -jar room-manager.jar
Notez qu'il n'y a aucune indication de Class-Path ni de Main-Class dans la ligne de commande !

Interface ligne de commandes :

INFO  21:35:34.870 f.i.rm.App [App.java:119] - Room-Manager version 1.0 started
INFO  21:35:35.115 f.i.rm.App [App.java:122] - starting persistency service
usage: room-manager.jar
 -c <name>   Create new room
 -h          Display help message
 -l          List all rooms
 -q          Quit

Contenu de l'archive du livrable

Toutes les bibliothèques dépendantes sont stockées dans le dossier lib, sous l'archive JAR.

<room-manager directory>
    |
    - room-manager.jar
    |
    - lib
         - antlr-2.7.6.jar
         - aopalliance-1.0.jar
         - asm-3.1.jar
         - cglib-2.2.jar
         - commons-cli-1.2.jar
         - commons-collections-3.1.jar
         - dom4j-1.6.1.jar
         - guice-3.0.jar
         - guice-persist-3.0.jar
         - h2-1.3.160.jar
         - hibernate-commons-annotations-3.2.0.Final.jar
         - hibernate-core-3.6.0.Final.jar
         - hibernate-entitymanager-3.6.0.Final.jar
         - hibernate-jpa-2.0-api-1.0.0.Final.jar
         - javassist-3.12.0.GA.jar
         - javax.inject-1.jar
         - jta-1.1.jar
         - logback-classic-1.0.13.jar
         - logback-core-1.0.13.jar
         - slf4j-api-1.6.1.jar

Voici le contenu du fichier MANIFEST.MF qui informe la JVM des dépendances à l'exécution du programme :

$ unzip -c room-manager.jar META-INF/MANIFEST.MF
Archive:  /Users/fred/workspace_iut/teaching.s4_jee/03_persistence/room-manager/target/room-manager-1.0/room-manager.jar
  inflating: META-INF/MANIFEST.MF    
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: fred
Build-Jdk: 1.7.0_25
Main-Class: fr.iut.rm.App
Class-Path: lib/guice-3.0.jar lib/javax.inject-1.jar lib/aopalliance-1
 .0.jar lib/guice-persist-3.0.jar lib/slf4j-api-1.6.1.jar lib/logback-
 classic-1.0.13.jar lib/logback-core-1.0.13.jar lib/hibernate-jpa-2.0-
 api-1.0.0.Final.jar lib/hibernate-entitymanager-3.6.0.Final.jar lib/h
 ibernate-core-3.6.0.Final.jar lib/antlr-2.7.6.jar lib/commons-collect
 ions-3.1.jar lib/dom4j-1.6.1.jar lib/hibernate-commons-annotations-3.
 2.0.Final.jar lib/jta-1.1.jar lib/cglib-2.2.jar lib/asm-3.1.jar lib/j
 avassist-3.12.0.GA.jar lib/h2-1.3.160.jar lib/commons-cli-1.2.jar

Les informations de l'entrée Class-Path sont stockées par convention à 80 colonnes, chaque bibliothèque (.jar) séparée d'un caractère espace !

Construction des Livrables

Le projet est outillé sous Maven ; on tire ainsi profit des déclarations de dépendances du pom.xml pour automatiquement construire le livrable (nommé assembly).

Les sources sont fournies : room-manager-1.5-src.zip.

Construction du livrable : plugin Maven Assembly

La commande mvn assembly:assembly1 permet de fabriquer entièrement le programme a livrer. Le résultat est un fichier .zip dans le répertoire target qui contient (cf. section précédente) :

  • l'archive Java (.JAR) de notre code
  • l'ensemble des bibliothèques nécessaires au fonctionnement du programme
    • Injection
    • Google Guice
    • JPA
    • Hibernate (implémentation JPA)
    • Module d'interprétation de commande en ligne
    • Gestionnaire de traces applicatives
    • Parseur XML

Comment maven a-t-il connaissance des informations à inclure ?

Le livrable est défini dans le fichier binary.xml du dossier src/main/assembly2

<?xml version="1.0" encoding="UTF-8"?>
<assembly  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
        xsi:schemaLocation="http://maven.apache.org/xsd/assembly-1.1.0.xsd">
    <id>bin</id>
    <formats>
        <format>zip</format>
    </formats>
    <files>
        <file>
            <source>target/${project.artifactId}-${project.version}.jar</source>
            <outputDirectory>/</outputDirectory>
            <destName>${project.artifactId}.jar</destName>
        </file>
    </files>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/lib</outputDirectory>
            <unpack>false</unpack>
            <scope>runtime</scope>
            <useProjectArtifact>false</useProjectArtifact>
        </dependencySet>
    </dependencySets>
</assembly>

Construction du site web du projet : plugin Maven Site

La commande mvn site3 génère le site web du projet qui contient :

  • la liste des dépendances et licences logicielles associées
  • différents rapports d'audit
    • Surfire : tests unitaires
    • Cobertura : couverture des tests unitaires
    • FindBugs : outil d'analyse statique de code
    • PMD : outil de recherche des erreurs communes de programmation
    • Checkstyle, outil d'audit des conventions de codage
    • JavaDocs (applicatif et tests unitaires)

Certains des plugins mis en œuvre sont configurés dans le dossier config du projet.

Lancement de l'application depuis les sources

$ mvn assembly:assembly && cp target/room-manager-1.5-bin.zip /tmp && (cd /tmp && unzip -o room-manager-1.5-bin.zip && cd room-manager-1.5 && java -jar room-manager.jar)

Exercice

Objectif : Re-construire les livrables de l'application et enregistrer une salle (15 minutes).

  1. récupérer le code source dans la bibliothèque
  2. le décompresser dans un dossier room-manager du dépôt utilisé pour les TDs
  3. Commiter et pousser
  4. construire le livrable. L'archive du livrable est dans le dossier 'target'.
  5. générer le site. Il faut que la version du plugin findBugs soit 3.0.1 (faire les modifications nécessaires dans le fichier pom.xml)
  6. faire fonctionner l'application via le livrable
    1. lister les salles
    2. créer une salle
    3. lister à nouveau les salles
    4. quitter l'application
    5. relancer l'application
    6. lister les salles

Architecture technique

Architecture de l'application fournie

Le patron de conception DAO (Data Access Object) est utilisé dans ce type d'application.

A savoir une claire séparation entre les classes métiers et les fonctions d'accès à la base de données (lecture/écriture/modification/supression des données de la BD). Le paquet persistence.domain contient les classes métiers. Le paquet persistence.dao contient les classes réalisant la persistance des données (objets des classes métiers), c'est à dire les accès à la base de données.

Seules les classes du paquet persistence.dao.imp dépendent de la technologique de stockage utilisée (JDBS, JPA, ...). En cas de changement de technologie, seules ces classes sont à réécrire.

Usage du patron de conception DAO

Les fonctionalités de mise à jour de la BD sont déclarées dans des interfaces (RoomDAO en étant un exemple). Ces interfaces définissent les accès à la base de données permettant la persistance des "objets". L'acronyme informatique anglais CRUD (pour create, read, update, delete) (parfois appelé SCRUD avec un "S" pour search) désigne les quatre opérations de base pour la persistance des données, en particulier le stockage d'informations en base de données.

L'interface RoomDAO définit les fonctionnalités de persistance relatives aux objets de la classe Room : sauvegarde, récupération, modification. L'interface RoomDAO est incomplète (l'opération de suppression n'est pas proposée).

Composants tiers

Interpréteur Apache-Commons CLI

Apache-Commons CLI4 est une bibliothèque utilitaire pour l'interprétation des commandes saisies.

...
// build options command line options
options.addOption(
  OptionBuilder
  .withDescription("List all rooms")
  .create(LIST));
options.addOption(
  OptionBuilder
  .withArgName("name")
  .hasArg()
  .withDescription("Create new room")
  .create(CREATE));
options.addOption(
  OptionBuilder
  .withDescription("Display help message")
  .create(HELP));
options.addOption(
  OptionBuilder
  .withDescription("Quit")
  .create(QUIT));
...

Google Guice

Guice permet la mise en œuvre de l'injection de dépendances.

  @Override
  protected final void configure() {
    logger.debug("MainModule configuration started");
    bind(App.class);
    bind(RoomDao.class).to(RoomDaoImpl.class);
    logger.debug("MainModule configuration done");
  }

Remarquez comment sont liées l'application (App) ainsi que l'implémentation de DAO.

Traces applicative

Un framework d'abstraction des traces applicatives est utilisé dans le programme : slf4j. Plusieurs vrais frameworks d'implémentation des traces peuvent ainsi être utilisés (intégré à Java, log4j, etc.). Changer d'implémentation devient alors transparent afin que l'application puisse facilement s'intégrer à d'autres systèmes possédant déjà une solution de traces applicatives (cas des Conteneurs Java EE).

La configuration des traces est réalisée dans le fichier logback.xml.

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%-5level %d{HH:mm:ss.SSS} %logger{10} [%file:%line] - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="${log.root.level}">
    <appender-ref ref="STDOUT"/>
  </root>

  <logger name="fr.iut.rm" level="info"/>
  <logger name="org.hibernate" level="warn"/>
  <logger name="org.hibernate.SQL" level="debug"/>
  <logger name="cli" level="info">
    <appender-ref ref="FILE"/>
  </logger>
</configuration>

Pour que les traces soit enregistrées dans le fichier ~/.log

<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>${user.home}/.log</file>
        <encoder>
            <pattern>%-5level %d{HH:mm:ss.SSS} %logger{10} [%file:%line] - %msg%n</pattern>
        </encoder>
    </appender>

  <root level="${log.root.level}">
    <appender-ref ref="FILE"/>
  </root>

  <logger name="fr.iut.rm" level="info"/>
  <logger name="org.hibernate" level="warn"/>
  <logger name="org.hibernate.SQL" level="debug"/>
  <logger name="cli" level="info">
    <appender-ref ref="FILE"/>
  </logger>
</configuration>

H2

H25 est une base de données SQL implémentée en Java et accessible en JDBC6 (JDBC est un standard Java de connexion aux bases de données).

H2 supporte des modes serveur (stand-alone) ou embarqué (embedded). Le stockage peut aussi bien être en mémoire (in memory) ou sur système de fichiers (filesystem).

Modèle métier

Composé simplement d'une classe Room qui respecte la norme Bean aussi nommé javaBean :

  • un constructeur vide publique
  • des accesseurs

ainsi que des attributs (id et name).

À souligner la présence d'annotations JPA (présenté dans les sections suivantes) :

  • à la déclaration de la classe
  • à la déclaration des attributs de la classe
package fr.iut.rm.persistence.domain;

import javax.persistence.*;

/**
 * A classic room
 */
@Entity
@Table(name = "room")
public class Room {
    /**
     * sequence generated id
     */
    @Id
    @GeneratedValue
    private long id;

    /**
     * Room's name
     */
    @Column(nullable = false, unique = true)
    private String name;

    /**
     * Default constructor (do nothing)
     */
    public Room() {
        // do nothing
    }

...

JPA, Java Persistance API

Notons qu'en français, on dit persistance et en anglais, persistence.

Il est nécessaire de rendre certains objets de l'application accessibles en permanence (même après un redémarrage, une mise à jour, un changement de machine, etc.)

Une solution est la persistance des objets dans des bases de données relationnelles :

  • le stockage se fait donc sous forme de lignes stockées (appelé enregistrements) dans une ou plusieurs tables composées de différentes colonnes
  • une clé primaire permet d'identifier les données (elle est stockée dans une colonne particulière)

Problème : Le monde objet\Classe est différent du monde relationnel

Les classes ne sont pas des tables (les objets ne sont pas des enregistrements) !

  • il n'y a pas de notion d'héritage dans le monde relationnel ;
  • les références aux collections sont inversées ;
  • il n'y a pas la même notion d'égalité (primary key vs. instance)

Solution : JPA et Mapping Relationnel-Objet (ou ORM, Object Relationnal Mapping)

  • Illusion de manipuler une base orientée objet dans une base relationnelle : il s'agit de créer une correpondance des classes à une base de données relationnelles.
  • Différents ORM framework propriétaires le permettent (Hibernate, Ibatis, etc.). Cependant, les dernières versions de JavaEE ont publié un standard : JPA pour Java Pesistence API. Une partie des frameworks propriétaires ont implémenté le standard, dont Hibernate, utilisé dans l'application Room-Manager.

Littéralement « Java Persistence API », il s’agit d’un standard faisant partie intégrante de la plate-forme Java EE, une spécification qui définit un ensemble de règles permettant la gestion de la correspondance entre des objets Java et une base de données, ou autrement formulé la gestion de la persistance. les ORM plateformes réalisent cette correspondance.

Configuration de ORM

L'élément de premier niveau pour faire fonctionner JPA est l'EntityManager7. Ce dernier est configuré au moyen du fichier persistence.xml qui doit être placé dans le dossier META-INF de l'archive (resources/META-INF dans les sources).

<?xml version="1.0" encoding="UTF-8" ?>
<persistence 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" version="1.0">

  <!-- JPA Test Unit -->
  <persistence-unit name="room-manager" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>

    <!-- Need to add all classes due to JPA persistence file scope problem -->
    <class>fr.iut.rm.persistence.domain.Room</class>

    <properties>
      <property name="hibernate.hbm2ddl.auto" 
                value="update"/>
      <property name="hibernate.showSql" 
                value="true"/>

      <!-- H2 -->
      <property name="hibernate.dialect" 
                value="org.hibernate.dialect.H2Dialect"/>
      <property name="hibernate.connection.driver_class" 
                value="org.h2.Driver"/>
      <property name="hibernate.connection.url" 
                value="jdbc:h2:room-manager;DB_CLOSE_DELAY=-1"/>

    </properties>
  </persistence-unit>

</persistence>

Parmi les informations importantes contenues dans les balises, on peut noter :

  • le nom de l'unité de persistance "base de données" (ici la base de données est stockée dans le répertoire courant sous le nom room-manager). D'autres possibilités plus fiables existent :
  • la base de données est en mémoire vive (les données ne sont pas réellement persistantes) :
    <property name="hibernate.connection.url"  
           value="jdbc:h2:mem:room-manager;DB_CLOSE_DELAY=-1"/>
    
    • la base de données est stockée dans le répertoire ~/JEE/BD/ sous le nom room-manager :
      <property name="hibernate.connection.url" 
           value="jdbc:h2:~/JEE/BD/room-manager;DB_CLOSE_DELAY=-1"/>
      
  • le framework d'implémentation, org.hibernate.ejb.HibernatePersistence
  • les classes Beans annotées @Entity
  • hibernate.hbm2ddl.auto, pour créer automatiquement le schéma
    • validate : valide le schéma, ne réalise aucune modification sur le schéma de la base.
    • update : met à jour le schema.
    • create : crée le schéma, détruisant au préalable les anciennes données.
    • create-drop : supprime le schéma à la fin de la session.

Dans un programme, l'EntityManager est instancié pendant la phase de bootstrap (démarrage)

...
EntityManagerFactory emf = Persistence.createEntityManagerFactory("room-manager");
EntityManager em = emf.createEntityManager();
...

Dans le cas de l'application Room-Manager, l'instanciation est réalisée par le biais du PersistService afin que ce dernier puisse être injecté.

Gestion de la persistence avec Guice

Utilisation de JPA

Décrire le Mapping entre les classes et les tables de la base

Afin qu'une classe Java puisse être considérée comme une entité une table, c'est à dire une classe qui est gérée par JPA, elle doit être déclarée dans le persistence.xml et annotée comme tel.

Pour Rappel :

...
@Entity
@Table(name = "room")
public class Room {
    /**
     * sequence generated id
     */
    @Id
    @GeneratedValue
    private long id;

    /**
     * Room's name
     */
    @Column(nullable = false, unique = true)
    private String name;

    /**
     * Default constructor (do nothing)
     */
    public Room() {
        // do nothing
    }
- La classe doit être un bean. Constructeur public sans arguments/getters/setters - L'entité ne doit pas être finale, ses attributs non plus - @javax.persistence.Entity permet au gestionnaire de persistance de ne pas reconnaitre la classe comme un simple POJO et de permettre de la persister - @javax.persistence.Id indique l'identifiant unique de l'objet (cf. clé primaire) - @javax.persistence.GeneratedValue indique que le gestionnaire de persistance se charge de générer cette valeur - Par défaut : - L'entité est stockée dans une table - Chaque attribut est stocké dans une colonne


Exercice (40 minutes)

Objectif : Apprendre à utiliser JPA. Rajouter un champ description de type String, de taille maximale de 10 caractères à l'entité Room.

  1. Modifier la classe Room.
  2. Modifier temporairement le hibernate.hbm2ddl.auto pour les besoins du développement (fichier persistence.xml)
  3. Rajouter les annotations nécessaires (penser à la Validation vu la séance précédente).
  4. Vérifier que la description de la class Room générée par le plugin de Maven contient bien le champs description (fichier Room_ dans le répertoire target). SI nécessaire compile le projet via la commande en ligne de Maven
  5. Tester la création d'un Objet "Room" dont la description a plus de 10 caractères.
  6. Modifier les commandes de création et construction de liste.

Relations entre les entités

Les relations entre les entités peuvent avoir différentes cardinalités; ce qui doit être spécifié au travers d'annotations.

  • 1-1 (@OneToOne) 8
  • 1-n (@OneToMany) 9
  • n-1 (@ManyToOne) 10
  • n-n (@ManyToMany) 11

Relations entre les entités - exemple «OneToOne»

extrait de http://blog.paumard.org/cours/jpa/chap03-entite-relation.html

 //
 // Entité Commune
 //
 @Entity
 public class Commune implements Serializable {

     @Id
     @GeneratedValue(strategy = GenerationType.AUTO)
     private Long id;

     @Column(length=40)
     private String nom ;

     @OneToOne
     private Personne maire ;

     // suite de la classe
}

En base, les choses se passent assez simplement : une colonne maire_id va être créée dans la table Commune. Cette colonne sera une clé étrangère référençant la colonne id de la table Maire. On peut imposer le nom de cette colonne en ajoutant l'annotation @JoinColumn(name="...") sur la relation maire.

 //
 // Entité Maire
 //
 @Entity
 public  class Personne  implements Serializable {

     @Id
     @GeneratedValue(strategy = GenerationType.AUTO)
     private Long id;

     @Column(length=40)
     private String nom ;

     @OneToOne(mappedBy="maire")  // référence la relation dans la classe Commune
     private Commune commune ;

     // suite de la classe
}

Exemple de relation bidirectionnelle entre les entités - «OneToMany» et «ManyToOne»

 //
 // Entité Marin
 // 
 @Entity
 public  class Marin  implements Serializable {

     @Id
     @GeneratedValue(strategy = GenerationType.AUTO)
     private Long id;

     @ManyToOne
     private Bateau monBateau ;

     // reste de la classe
}

 // 
 // Entité Bateau
 // 
 @Entity
 public  class Bateau  implements Serializable {

     @Id
     @GeneratedValue(strategy = GenerationType.AUTO)
     private Long id;

     @OneToMany(mappedBy="monBateau")
     private Collection<Marin> marins ;

     // reste de la classe
}

Utilisation des objets mappés

Dans Room-Manager, l'utilisation des objets mappés est encadrée dans une UnitOfWork 12. Cet élément permet de gérer la connexion et les transactions avec la base de données :

  • sauvegarde en base rapide (1 grosse modification plutôt que plusieurs petites)
  • état de la base toujours valide (tout ou rien)

Il est possible d'interdire des opérations JPA en dehors d'un cadre transactionnel.

Cycle de vie d'une entité

Un objet instancié dans la JVM a plusieurs états vis-à-vis de l'EntityManager dont le rôle est d'assurer la synchronisation avec la base de données.

Diagramme de séquence illustrant une transaction

Gestion des transactions

Dans le monde des base de données relationnelles, les transactions sont essentielles à la conservation de l'intégrité du modèle de données.

Les propriétés ACID sont les quatre principaux attributs d'une transaction de données.

  • Atomicité (transaction complète ou rien du tout)
  • Cohérence (état nécessairement valide après transaction)
  • Isolation (Pas de dépendences entre transactions)
  • Durabilité (Si transaction confirmées : sur à 100% que les données sont enregistrées)

Pour autant, il n'y a pas de trace, de prime abord de la gestion des transactions dans le code source de Room-Manager. Elles sont en réalité gérées au moyen d'annotations, comme illustré dans l'extrait de code ci-après :

  /**
   * @param room room to persist
   */
  @Override
  @Transactional
  public void saveOrUpdate(final Room room) {
    this.em.get().persist(room);
    logger.debug("Room '{}' saved", room.getName());
  }

L'annotation permet d'ouvrir et commiter automatiquement la transaction. Pour autant, le vrai commit n'aura lieu qu'à la fin de l'UnitOfWork. Si une exception est levée, la transaction sera automatiquement annulée (rollback).

Patron de conception DAO

Le programme Room-Manager utilise le pattern (modèle de conception logicielle) de DAO pour Data Access Object 13. Son utilité est d'isoler au sein d'une seule et même couche tous les accès à la persistance (à savoir les accès (CRUD) aux données stockées de manière permanente). Pour isoler, les accès aux données stockées, des interfaces telles que RoomDao sont créées; leur implémentation dépendra de la technique de stockage choisie (BD, fichier, ...).

package fr.iut.rm.persistence.dao;

import fr.iut.rm.persistence.domain.Room;

import java.util.List;

/**
 * Data Access Object of {@link fr.iut.rm.persistence.domain.Room}
 */
public interface RoomDao {

  /**
   * Persists room.
   *
   * @param room object to persist
   */
  void saveOrUpdate(Room room);
    /**
   * @param room object to remove from DB
   */
  void delete(Room room);

  /**
   * @return the full room list
   */
  List<Room> findAll();

  /**
   * @param name the searched name, must be case sensitive and exact match. 
   *             Null name returns null result
   * @return the room or null if nothing found
   */
  Room findByName(final String name);

}

Java Persistence Query Language : JPQL

JPQL permet de créer des requêtes de recherche d'objets (aussi nommé entités) persistantes indépendamment du mécanisme utilisé pour stocker ces entités.

JPQL est donc portable et non limité à un type de base de données particulier.

JPQL est conçu pour combiner la syntaxe et la simplicité sémantique des requêtes SQL aux caractéristiques propres à Java.

L'implémentation des méthodes utilise des requêtes en JPQL (Java Persistence Query Language) 14. Ce langage, est très proche de SQL, sauf qu'il utilise le nom des classes/attributs. Ainsi le programmeur n'a pas à connaître les noms des tables et des colonnes pour écrire une requête.

Afin d'éviter d'écrire en dur les noms des attributs (et de générer des requêtes erronnées), un plugin Maven génère du code Java de description des tables afin d'utiliser des constantes générées et vérifiables à la compilation 15 pour nommer les tables, colomnes , ... .

Donc à chaque modification de la structure d'une Entity, il faut mettre à jour les méta-données : (c'est à dire recompiler via Maven le projet). Par exemple,

  • Room.class.getName() est la table associée à la classe Room.
  • Room_.name.getName() est le nom de la colonne correspondant à l'attribut "name" de la table associée à la classe Room.
    /**
     * @param name  of the room
     * @return the corresponding room or null if nothing found
     */
    @Override
    @Transactional
    public Room findByName(final String name) {
        StringBuilder query = new StringBuilder("from ");
        query.append(Room.class.getName()).append(" as room");
        query.append(" where room.").append(Room_.name.getName());
        query.append(" = :name");

        List<Room> resultList = em.get().createQuery(query.toString()).
                        setParameter("name", name).
                getResultList();

        if (resultList.size() > 0) {
            logger.debug("Room  with name '{}' found", name);
            return (Room) resultList.get(0);
        }
        logger.debug("No room with name '{}' found", name);
        return null;
    }

Enfin, l'utilisation de PreparedStatement (requêtage paramétré) offre de multiples avantages :

  • cachabilité des plans d'exécution par les (gros) moteurs de base de données (Oracle, Postgres, etc.)
  • insensibilité au risque d'attaque par injection SQL16 avec l'échappement automatique des paramètres

Exercice

Objectif : Maîtriser le pattern DAO et les fonctions JPA, avoir des notions de JPQL. Ajouter une nouvelle entité AccessEvent afin de gérer les entrées/sorties dans une salle.

  1. Ajouter dans roomDao la fonctionnalité de destruction d'un enregistrement removeRoom(String name); ainsi les 4 opérations de CRUD seront implémentés.
  2. Ajouter la commande dans l'application pour détruire un salle;
  3. compléter le code de la méthode createRoom de la classe App : tester l'unicité des noms de salle;
  4. Modifier temporairement le hibernate.hbm2ddl.auto pour les besoins du développement;
  5. Développer la classe AccessEvent * ajouter une attribut d'identification auto-généré * ajouter un attribut de nom de personne * ajouter un attribut de type d'évènement (entrée/sortie) * ajouter un attrivut de data/heure d'évenement * la relation vers Room est unidirectionnelle et elle est de type many-to-one
  6. Ajouter la commande dans l'application pour déclarer une entrée dans une salle;
  7. Ajouter la commande dans l'application pour déclarer une sortie d'une salle;
  8. Ajouter la commande de visualisation du journal des entrées/sorties d'une salle.
  9. Réaliser une fonction qui affiche tous les movements d'une personne (désignée par son nom).

Aspects non abordés

JPA est une norme très complète et un TD de 4 heures est largement insuffisant pour la couvrir en totalité. Ainsi, on peut citer quelques points afin de se faire une idée :

- Les caches de premier et second niveaux (et les problématiques associées en cluster) - La gestion de la concurrence - La gestion du versionning des entités - La gestion des Callbacks - L'utilisation des intercepteurs - L'intégration avec les fonctionnalités natives de certaines base de données (trigger, etc.)

Finalisation du projet room manager

Exercice

Objectif : Maîtriser les outils d'aide à la gestion de qualité du logiciel.

  1. Modifiez votre code pour ne plus générer d'erreur dans FindBugs, PMD et checkstyle. Le code fourni a déjà des erreurs détectées par ces outils, il faut aussi les corriger.
  2. Maximisez la couverture de tests.

À retenir (non exhaustif)

  1. Usage, contenu, structure, et réalisation d'un livrable via Maven.
  2. Patron de conception DAO (Data Acces Object).
  3. Usage de JPA pour construire la structure d'une base de données. Par exemple, savoir écrire la classe Building et modifier la classe Room (dont les objets seront enregistés dans la base de données) à l'aide des annotations JPA. Un building a une adresse (chaîne de moins de 500 caractéres alphanumériques), un nombre d'étages et il contient des pièces (Room). La relation entre building et Room est bidirectionnelle. L'annotation permettant de valider l'adresse d'un immeuble est aussi à écrire. Qu'elles sont les modifications à faire dans le fichier persistence.xml ?
  4. Des notions de JPQL. Par exemple savoir écrire la requête paramétrée suivante en JPQL "lister les building ayant au moins X étages" à l'aide de la requête JPQL suivante :
    StringBuilder query = new StringBuilder("from ");
    query.append(Room.class.getName()).append(" as room");
    query.append(" where room.").append(Room_.name.getName());
    query.append(" = :name");
    List<Room> resultList = em.get().createQuery(query.toString()).
                            setParameter("name", name).getResultList();
    
  5. Utilité/usage de JPA, ORM, JPQL, ...
  6. Des connaissances sur les composants utilisés : slf4j, h2, common CLI, Google Guice.
  7. Connaissance du rôle des fichiers binary.xml, persistence.xml et lockback.xml.
  8. Architecture logiciel (métier, dao, contrôle, IHM)
  9. Connaissance du rôle de la classe com.google.inject.persist.UnitOfWork et des annotations @Transactional, @Inject ...

Ressources supplémentaires


  1. http://maven.apache.org/plugins/maven-assembly-plugin/ 

  2. http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html 

  3. http://maven.apache.org/plugins/maven-site-plugin/ 

  4. http://commons.apache.org/proper/commons-cli/ 

  5. http://www.h2database.com/html/main.html 

  6. http://www.oracle.com/technetwork/java/javase/jdbc/index.html 

  7. http://docs.oracle.com/javaee/7/api/javax/persistence/EntityManager.html 

  8. http://docs.oracle.com/javaee/7/api/javax/persistence/OneToOne.html 

  9. http://docs.oracle.com/javaee/7/api/javax/persistence/OneToMany.html 

  10. http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToOne.html 

  11. http://docs.oracle.com/javaee/7/api/javax/persistence/ManyToMany.html 

  12. http://martinfowler.com/eaaCatalog/unitOfWork.html 

  13. http://www.oracle.com/technetwork/java/dataaccessobject-138824.html 

  14. https://docs.oracle.com/html/E24396_01/ejb3_langref.html 

  15. https://docs.jboss.org/hibernate/jpamodelgen/1.0/reference/en-US/html_single/ 

  16. http://fr.wikipedia.org/wiki/Injection_SQL