Utilisation de JDO dans une application web (JPox/Tomcat)

Cet article fait suite au « Tutoriel sur l'utilisation de JDO sur une base de données relationnelle ». Il se propose de décrire l'intégration d'un modèle métier persisté par le driver JDO JPox au sein d'un serveur d'application Tomcat 5.x.
Téléchargez la version PDF

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Ce tutoriel va vous montrer comment intégrer un modèle métier persisté par le driver JDO JPox au sein d'un serveur d'application Tomcat 5.x. Pour une explication complète des concepts JDO reportez-vous à l'article Tutoriel sur l'utilisation de JDO sur une base de données relationnelle. Ce tutoriel suppose que le lecteur est familier avec le serveur d'application Tomcat 5.x et possède des connaissances sur les mécanismes de persistance JDO. La première partie de l'article est consacrée à l'installation des produits. Ensuite il enchaine sur le déploiement d'un exemple et termine sur les quelques points spécifiques à cette intégration.

II. Installation des composants

Dans notre tutoriel nous allons faire persister nos classes Java dans une base de données MySQL en utilisant le driver open source JPox s'exécutant dans le container Web Apache/Tomcat.

II-A. MySQL

II-B. JPox

II-C. Tomcat 5.x

Tomcat est une implémentation open source d'un container web J2EE. Il permet d'écrire des applications web dynamiques en utilisant les technologies Servlet et JSP. Pour télécharger une version, rendez-vous ICI. Il existe pour les environnements de type Microsoft Windows, un programme d'installation automatique. Pour les autres environnements, il faut télécharger une version archive type .tar.gz.
Pour plus d'informations sur la technologie Servlet et Tomcat, reportez-vous à l'article Créer et déployer un premier servlet avec TomCat publié par LFE.

III. Déploiement

Cette partie va détailler comment déployer un exemple d'un modèle géré en persistance par JDO dans le container web Tomcat. Vous aurez besoin pour ceci de télécharger l'archive (WAR) et le fichier de paramétrage.

III-A. Installation de l'application Discothèque

Cette application est la suite du modèle Complexe présenté dans la première partie. Le modèle comporte des notions d'artistes, de personnes, de groupes, d'albums, de pistes. Les services de notre application discothèque vont nous permettre de créer des instances du modèle, de vérifier que l'on a bien tout persisté et d'effectuer des requêtes.

Démarrez le serveur d'application Tomcat et ouvrez la page d'administration http://localhost:8080/manager/html.

Vous devez obtenir un écran comme celui-ci :

Tomcat: Console manager
Tomcat: Console manager

Cliquez sur le bouton « Parcourir » de la section « WAR File to Deploy » et sélectionnez le fichier discotheque.war présent dans le fichier tutoriel_jpox_tomcat.zip.
Cliquez sur le bouton « Deploy »

Déploiement discotheque.war
Déploiement discotheque.war

L'application est déployée. Si vous cliquez sur le lien /discotheque vous devez arriver sur une page vous proposant les trois services de l'application.

Ne cliquez pas tout de suite sur les liens, il reste à configurer la base de données.

III-B. Paramétrage de la source de données

Avant d'exécuter notre application, il faut définir la source de données utilisée par le driver JDO. Cette source de données sera gérée par le web container Tomcat. Si vous ouvrez le fichier discotheque.xml, vous voyez :

discotheque.xml
Sélectionnez
<Context docBase="discotheque" path="/discotheque" reloadable="true" source="com.ibm.wtp.web.server:disco">
  <Logger className="org.apache.catalina.logger.FileLogger"  prefix="localhost_discotheque_log." suffix=".txt" timestamp="true"/>
  <Resource auth="Container" name="jdbc/discoDB" type="javax.sql.DataSource"/>
  <ResourceParams name="jdbc/discoDB">
    <parameter>
      <name>factory</name>
      <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
    </parameter>
    <parameter>
      <name>username</name>
      <value>root</value>
    </parameter>
    <parameter>
      <name>password</name>
      <value/>
    </parameter>
    <parameter>
      <name>driverClassName</name>
      <value>com.mysql.jdbc.Driver</value>
    </parameter>
    <parameter>
      <name>url</name>
      <value>jdbc:mysql://localhost:3306/test?autoReconnect=true</value>
    </parameter>
  </ResourceParams>
</Context>

Il vous faut modifier les paramètres de la Datasource (driverClassName, URL, username et password) en adéquation avec votre configuration mysql. Ensuite, copier le fichier discotheque.xml dans le répertoire %TOMCAT_HOME%/conf/Catalina/localhost et recharger l'application avec l'aide de l'application Manager de Tomcat.

III-C. Création des données

L'application est prête à être utilisée. Lancez votre navigateur sur l'URL : http://localhost:8080/discotheque/. Cliquez sur « Création des Données: CreateData ». Ce service va peupler la base de données. Les données insérées sont les mêmes que l'exemple 4.3. Création de données.

Si tout se passe correctement l'application vous répond :

 
Sélectionnez
Création des données..................OK

III-D. Vérification des données

Pour vérifier que les données sont correctement persistées, nous allons compter le nombre d'instances insérées. Lancez votre navigateur sur l'URL http://localhost:8080/discotheque/. Cliquez sur « Vérification des données : CheckData ». Le code de contrôle est le même que l'exemple 4.4.1. Vérification du nombre d'instances.

Si tout se passe correctement l'application vous répond :

 
Sélectionnez
    3 albums de Pink Floyd, 1 de Television et le best of d'Enio Morricone
    
    Nombre d'instances de class org.moussaud.data.Album = 5
    
    Pink Floy et Television
    
    Nombre d'instances de class org.moussaud.data.Group = 2
    
    Enion Moricone, les 5 membres de PF
    
    Nombre d'instances de class org.moussaud.data.Person = 6
    
    Groupe + Person
    
    Nombre d'instances de class org.moussaud.data.Artist = 8
    
    Tracks
    
    Nombre d'instances de class org.moussaud.data.Track = 38
    
    Genre: rock, blues
    
    Nombre d'instances de class org.moussaud.data.Genre = 2

III-E. Requêtes

Nous allons maintenant demander à l'application Discotheque de nous ramener l'ensemble des albums avec leur genre, une fois non trié, une fois trié. Lancez votre navigateur sur l'URL http://localhost:8080/discotheque/. Cliquez sur « Requete sur les données : SortData ». Ce service va effectuer une requête JDO sans rien préciser. Ensuite il réitère la même requête en demandant un tri au niveau du nom. Le code de contrôle est le même que l'exemple 4.4.2. Les Tris.

Si tout se passe correctement l'application vous répond :

 
Sélectionnez
Liste des Albums (non triés)

    * The Wall - rock
    * Dark Side Of The Moon - rock
    * Wish you Were Here - rock
    * Marquee Moon - rock
    * Compilation - Blues

Liste des Albums (triés)

    * Compilation - Blues
    * Dark Side Of The Moon - rock
    * Marquee Moon - rock
    * The Wall - rock
    * Wish you Were Here - rock

Voici donc une application utilisant JDO (Driver JPox) avec le web container J2EE Tomcat. La section suivante va détailler quelques points particuliers.

IV. Creusons !

Dans cette partie nous allons décrire l'exemple - discotheque.war - et nous attarder sur deux points en particulier :

  • l'accès au persistence manager ;
  • la nouvelle fonctionnalité de JDO 2.0, détachement et attachement des objets.

IV-A. Conception de « Discothèque »

L'application Discotheque est articulée pour les trois services sur un modèle classique :

Image non disponible
Conception

Le navigateur envoie la requête sur une Servlet qui s'appuie sur une couche service pour répondre à sa requête (Classe DiscothequeManager). Cette couche utilise les API JDO pour créer et manipuler le modèle et répondre au service. Les données récupérées, la couche présentation (ui) appelle une JSP pour l'affichage.

Le motif de conception est le MVC, Modèle Vue Contrôleur, sans Struts !

IV-B. L'accès au persistence manager

La classe DiscothequeManager est un mélange entre une classe service et une classe DAO. En effet, dans notre exemple simple, il n'est pas nécessaire de séparer les deux couches. Dès qu'une méthode a besoin d'accéder à JDO, elle a besoin d'une instance de la classe PersistenceManager. Cette classe va lui offrir de pouvoir faire persister des objets, les détruire, faire des requêtes. L'application s'exécute dans un environnement serveur, il n'est pas concevable, à chaque requête :

  • de charger les propriétés JDO / Jpox ;
  • de créer une instance de la classe PersistenceManagerFactory ;
  • d'ouvrir une instance de la classe PersistanceManager.

En particulier l'étape #2 est très consommatrice de ressources : lecture de la configuration JDO, initialisation des paramètres internes, connexion à la base de données.

Donc la classe DiscothequeManager utilise une classe PMHelper qui va lui fournir une instance de PersistenceManager qui correspond au contexte d'exécution (Méthode a()).Cette méthode associe une instance de PersistenceManager au Thread courant via l'objet magique ThreadLocal. Si le thread courant n'est pas associé avec une instance, il en instancie une nouvelle et lui associe. Sinon, le thread est déjà associé à un PersistenceManager, il lui retourne celle existante. Ce pattern permet à toute méthode d'obtenir une instance de PersistenceManager sans nécessiter de la passer de méthode en méthode. L'objet ThreadLocal nous assure qu'il n'y aura pas de collisions en cas d'exécutions parallèles: chaque requête est associée à un thread géré par le serveur d'application. Par contre, il est absolument nécessaire de fermer l'association (PMHelper.closePM()) en fin de service.

PMHelper
Sélectionnez
package org.moussaud.service;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

public class PMHelper {

    private static final ThreadLocal pms = new ThreadLocal();

    private static PersistenceManagerFactory pmf = null;

    public static PersistenceManager currentPM() {

        PersistenceManager pm = (PersistenceManager) pms.get();
        if (pm == null) {
            pm = getPMF().getPersistenceManager();
            pms.set(pm);
        }
        return pm;
    }

    public static void closePM() {
        PersistenceManager pm = (PersistenceManager) pms.get();
        pms.set(null);
        if (pm != null)
            pm.close();
    }

    private static PersistenceManagerFactory getPMF() {
        if (pmf == null) {
            pmf = JDOHelper.getPersistenceManagerFactory(Utils
                    .getJDOProperties());
        }
        return pmf;
    }
}

Ce pattern est couramment utilisé dans les frameworks, et en particulier les frameworks de persistance. Exemple : avec Hibernate 2.x, Thread Local Session.

IV-C. Détachement des objets

L'un des problèmes que l'on rencontre quand on interagit avec un framework de persistance ou un ResultSet JDBC, c'est que les objets retournés sont en lien plus ou moins direct avec la source de données. Ceci est très fâcheux quand on doit les transmettre à une page JSP ou à des méthodes métier en dehors de la transaction courante.

Généralement la solution envisagée est de créer une famille de Java Bean, ressemblant généralement au modèle, pour accueillir les données. Ce pattern est connu sous le nom de DTO (Data Transfert Object) ou VO (Value Object). Avec schéma, les applications se retrouvent avec une quantité importante d'objets et de services qui font le transfert entre le modèle DTO et le modèle persisté. La valeur ajoutée n'est pas énorme, mais le temps de développement, de maintenance et d'exécution augmente !

Les frameworks de persistance tels que JDO ou Hibernate ont bien compris ce besoin (ou cette nécessité) de pouvoir manipuler ces objets de manière déconnectée de la base de données. En JDO, ces méthodes existent : PersistenceManager.attach() et PersistenceManager.detach().

Dans notre exemple, nous allons voir comment utiliser détacher des instances pour transmettre la liste des albums et des genres directement à la JSP pour affichage.

IV-C-1. La requête

Si on regarde la requête faite pour trier les albums on a le code suivant :

 
Sélectionnez
public Collection sortData(boolean sorted) {
    try {

        PersistenceManagerFactory pmf = JDOHelper
                .getPersistenceManagerFactory(Utils.getJDOProperties());
        PersistenceManager pm = pmf.getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        pm.getFetchPlan().addGroup("detach_album_genre");
        tx.begin();
        Query qt = pm.newQuery(Album.class);
        if (sorted)
            qt.setOrdering("name ascending");
        Collection c = (Collection) qt.execute();
        c = pm.detachCopyAll(c);
        tx.commit();

        return c;
    } finally {
        PMHelper.closePM();
    }
}

Si on compare au code utilisé dans la version autonome du sample, On remarque deux lignes supplémentaires :

  • pm.getFetchPlan().addGroup(« detach_album_genre »). Cette ligne indique au persistence manager que l'on va ajouter au plan de récupération par défaut d'autres instances. Par défaut, quand un objet est récupéré par le persistence manager, le driver s'arrête à ce que j'appelle les types simples ou basiques. Toutes relations de type 1..1 ou 1..n ne sont pas du tout récupérées. Elles le seront à la demande. Dans notre exemple nous allons augmenter la récupération par défaut en y incluant une relation 1..1 vers la classe Genre ;
  • c = pm.detachCopyAll(c). Cette méthode permet de détacher les instances d'une collection du PersistenceManager avec laquelle elles sont liées. (En concordance avec le plan de récupération - le fetch plan.)

IV-C-2. Définition du plan de récupération (Fetch Plan)

Si on regarde de près le fichier package.jdo, on voit pour la classe Ablum, la définition suivante :

 
Sélectionnez
<class name="Album" detachable="true" identity-type="datastore">
  <field name="artist"/>
  <field name="date"/>
  <field name="genre"/>
  <field name="name"/>
  <!-- relation 1..n implementee sous forme de List (arraylist)
  contenant des instances de la classe Track -->
  <field name="tracks">
    <collection element-type="org.moussaud.data.Track"/>
    <join/>
  </field>
  <!-- Definition d'un fetch groupe qui contient les pistes -->
  <fetch-group name="detach_album_genre">
    <field name="genre"/>
  </fetch-group>
  <!-- Definition d'un fetch groupe qui contient l'artiste et le genre -->
  <fetch-group name="all_details">
    <field name="artist"/>
    <field name="genre"/>
    <field name="tracks"/>
  </fetch-group>
</class>

En plus de la désormais classique définition des champs (field) on voit maintenant apparaître la définition des fetch groups. Ces fetch groups définissent explicitement quelles instances de classes doivent être récupérées (chargées) en même temps que l'instance. Cette récupération étendue peut être nécessaire comme notre exemple pour transmettre des objets, mais aussi pour indiquer au driver JDO que l'on va avoir besoin de ces instances et qu'il doit les récupérer en une seule fois (optimisation possible au niveau de l'implémentation). Exemple : si pour un traitement, on a besoin de parcourir l'ensemble des pistes d'un album pour calculer la durée totale de l'Album, autant définir un fech group sur les instances de type Track.

Il est possible de définir autant de fetch groups que l'on souhaite pour une classe donnée. On peut voir ces fetch groups comme des cas d'utilisation. Exemple :

  • Cas d'utilisation 1 : requête pour afficher le nom des albums (fetch group defaut) ;
  • Cas d'utilisation 2 : détail d'un album (fetch group all_details, ajout des attributs artists,genre et tracks).

V. Conclusion

Cet article vous a montré comment l'utilisation d'un composant de persistance tel que JDO pouvait s'intégrer facilement au sein d'un serveur Tomcat, en apportant performance et simplicité dans la persistance vos modèles métier.

Une étape supplémentaire pour une conception complète serait d'y ajouter l'utilisation du framework Spring. Spring permet de prendre en charge :

  • la gestion du persistence manager et de sa factory ;
  • la gestion des transactions de façon déclarative (comme dans EJB) ;
  • de l'assemblage des différentes couches (web, services, métiers, données).

Source complet de l'application est disponible en Téléchargement (WorkSpace Eclipse 3.1 avec WTP)

V-A. Crédits et licence

Ce tutoriel est écrit par Benoit Moussaud, Consultant Sénior en Architecture J2EE chez Xebiaxebia
Pour un contact ou des précisions, n'hésitez pas à m'écrire:
Copyrights Benoit Moussaud 2005

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2005 Benoit Moussaud. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.