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 :
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 »
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 :
<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 :
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 :
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 :
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 :
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.
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 :
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 :
<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 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 Xebia
Pour un contact ou des précisions, n'hésitez pas à m'écrire:
Copyrights Benoit Moussaud 2005