Solutions de persistance Java

Ippon Technologies

La couche de persistance est sûrement la partie la plus sensible et la plus stratégique des applications complexes. C'est à ce composant que sont confiées les données métier de l'entreprise, et c'est ce composant sur lequel se concentrent les contraintes de performance et d'intégrité des données (goulets d'étranglement, transaction, concurrence d'accès… ).

Ce document présente la réalisation de la couche de persistance de la célèbre application de démonstration « PetStore » (issue des BluePrints de Sun) à l'aide du projet Hibernate et de produits respectant les spécifications JDO 2.0 et EJB 3.0. Les produits mis en œuvre dans ce document sont :

- le framework Hibernate 3.1.3 ;

- JPOX 1.1.0, l'implémentation de référence du standard JDO 2.0 ;

- JBoss Application Server 4.0.4, l'un des premiers serveurs applicatifs J2EE compatible avec la norme EJB 3.0.

Ce document n'est pas un nouveau comparatif passant en revue chacune des caractéristiques techniques des trois solutions. L'objectif est de détailler la réalisation, étape par étape, de la couche de persistance de l'application de démonstration. ♪

- Titre : Solutions de persistance Java

- Auteur : Ippon Technologies

- Parution : 28 janvier 2008

- Licence : Copyright ® 2008 Ippon Technologies. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.

Sommaire

- I. PRÉSENTATION DE L'ÉTUDE

- I-A. À PROPOS D'IPPON TECHNOLOGIES

- I-B. OBJECTIF DU DOCUMENT

- II. HISTORIQUE DES MÉCANISMES DE PERSISTANCE

- III. APPROCHE MÉTHODOLOGIQUE

- III-A. CRITÈRES

- III-B. MÉTHODOLOGIE

- III-C. PRÉSENTATION DES SOLUTIONS

- III-C-1. Hibernate

- III-C-2. JDO 2.0

- III-C-3. EJB 3.0

- IV. PRÉSENTATION DU CAS D'ÉTUDE

- IV-A. DESCRIPTION DE L'APPLICATION « PET STORE »

- IV-B. DESCRIPTION DE LA COUCHE DE PERSISTANCE

- IV-C. DESCRIPTION DU CAS D'ÉTUDE

- V. RÉALISATIONS

- V-A. RÉALISATION DE LA COUCHE DE PERSISTANCE À L'AIDE D'HIBERNATE

- V-A-1. Conception

- V-A-2. Implémentation

- V-A-2-a. Fichier de configuration « hibernate.cfg.xml »

- V-A-2-b. Implémentation de la stratégie d'accès aux données

- V-A-2-c. Implémentation de la classe persistante « HibItem »

- V-A-2-d. Implémentation de la classe persistante « HibItemDetail »

- V-A-2-e. Implémentation de la classe utilitaire

- V-A-2-f. Implémentation de la classe de conversion

- V-A-3. Création du schéma de base de données

- V-B. RÉALISATION DE LA COUCHE DE PERSISTANCE À L'AIDE DE JDO 2.0

- V-B-1. Conception

- V-B-2. Implémentation

- V-B-2-a. Fichier de configuration

- V-B-2-b. Implémentation de la stratégie d'accès aux données

- V-B-2-c. Implémentation de la classe persistante « JDOItem »

- V-B-2-d. Implémentation de la classe utilitaire

- V-B-2-e. Implémentation de la classe de conversion

- V-B-3. Instrumentation des classes persistantes

- V-B-4. Création du schéma de base de données

- V-C. RÉALISATION DE LA COUCHE DE PERSISTANCE À L'AIDE D'EJB 3.0

- V-C-1. Conception

- V-C-2. Implémentation

- V-C-2-a. Fichiers de configuration

- V-C-2-b. Implémentation de la stratégie d'accès aux données

- V-C-2-c. Implémentation de la classe persistante « EntityItem »

- V-C-3. Création du schéma de base de données

- VI. CONCLUSION

- VI-A. RÉCAPITULATIF

- VI-B. COMMENT CHOISIR UNE SOLUTION DE PERSISTANCE ?

- VI-C. OÙ EN EST-ON SUR LE TERRAIN ?

- VII. Remerciements

I - PRÉSENTATION DE L'ÉTUDE

I-A - À PROPOS D'IPPON TECHNOLOGIES

Ippon Technologies est une société de services informatiques spécialisée dans les technologies J2EE, Portails et SOA. Société fondée en 2002 sous la double impulsion de Sportifs de Haut Niveau et de Managers issus de grandes sociétés de conseil en technologies. Ippon Technologies s'est développée autour de l'expertise nécessaire à la conception et l'intégration de solutions logicielles à la pointe des nouvelles technologies de l'entreprise, et notamment sur la plateforme J2EE. Notre force passe avant tout par la richesse de notre potentiel humain et notre positionnement original sur le marché de l'externalisation. L'ensemble de nos consultants sont des professionnels aguerris dans leur secteur et ont à leur actif des expériences déterminantes pour le développement des entreprises.

Notre expertise technologique :

I-B - OBJECTIF DU DOCUMENT

La couche de persistance est sûrement la partie la plus sensible et la plus stratégique des applications complexes. C'est à ce composant que sont confiées les données métier de l'entreprise, et c'est ce composant sur lequel se concentrent les contraintes de performance et d'intégrité des données (goulets d'étranglement, transaction, concurrence d'accès… ).

Ce document présente la réalisation de la couche de persistance de la célèbre application de démonstration « PetStore » (issue des BluePrints de Sun) à l'aide du projet Hibernate et de produits respectant les spécifications JDO 2.0 et EJB 3.0. Les produits mis en œuvre dans ce document sont :

- le framework Hibernate 3.1.3 ;

- JPOX 1.1.0, l'implémentation de référence du standard JDO 2.0 ;

- JBoss Application Server 4.0.4, l'un des premiers serveurs applicatifs J2EE compatible avec la norme EJB 3.0.

Ce document n'est pas un nouveau comparatif passant en revue chacune des caractéristiques techniques des trois solutions. L'objectif est de détailler la réalisation, étape par étape, de la couche de persistance de l'application de démonstration.

II - HISTORIQUE DES MÉCANISMES DE PERSISTANCE

Depuis les premières versions de la plateforme Java, les mécanismes de persistance offerts se sont considérablement étoffés.

La version initiale de la plateforme Java, le JDK 1.0, offrait des API limités pour stocker les données. L'état des objets était enregistré et restauré directement à l'aide de l'API d'entrée/sortie (java.io), ou placé dans des tables de propriétés (java.util.Properties).

Le JDK 1.1 apporta le mécanisme de sérialisation des objets (Java Object Serialization) permettant l'encodage et le décodage automatique des objets en flux binaires. Il intégra également l'API JDBC (Java Database Connectivity) permettant aux applications J2EE d'accéder, par le biais d'une interface commune, à des sources de données pour lesquelles il existe des pilotes JDBC. Bien que JDBC permette à une application Java d'exploiter efficacement une base de données relationnelle, un lourd travail de traduction des objets en requêtes SQL (Object to Relational Mapping) était à la charge des développeurs.

Pour pallier ce problème et pour permettre le développement d'applications réparties à base de composants transactionnels, la rédaction des spécifications EJB 1.0 (Enterprise JavaBeans) débuta dès 1998 par Sun Microsystems. Au prix d'un effort de développement conséquent, les composants EJB permettent la persistance des objets métier et la gestion des transactions distribuées.

Du fait du cout de développement important des EJB, des performances limitées des composants EJB Entity CMP, et du succès des plateformes Java en entreprise, l'utilisation du framework de persistance Hibernate, dont les premières versions datent de novembre 2001, se généralisa dans l'industrie. Hibernate est un projet « open source » (licence LGPL) gérant la persistance des objets en base de données relationnelle.

Parallèlement à Hibernate, JDO 1.0 (Java Data Objects) est accepté comme le standard Java de gestion de la persistance en mai 2002 (JSR12). Cette spécification vise à ajouter à des objets Java, la capacité de persistance de manière « orthogonale », c'est-à-dire sans modification des objets.

Le 13 mars 2006, la version finale de la spécification JDO 2.0 (JSR 243) est acceptée.

Le dernière version d'Hibernate, portant le numéro 3.1.3, est disponible depuis le 20 mars 2006. Elle ne respecte pas le standard JDO.

Le 1er mai 2006, le comité JSR 244 a approuvé à l'unanimité (Final Approval Ballot) la spécification Java Platform Enterprise Edition 5 (Java EE 5) incluant la spécification EJB 3.0 (JSR 220).

III - APPROCHE MÉTHODOLOGIQUE

Ce document montrera, à l'aide d'exemples, comment Hibernate et des produits, respectant les standards JDO 2.0 et EJB 3.0, permettent d'implémenter la couche de persistance de l'application de démonstration « PetStore ». Pour chaque exemple, les principales étapes de conception et de développement seront décrites.

III-A - CRITÈRES

Les critères étudiés seront :

- la facilité de mise en œuvre à l'aide de plug-ins Eclipse ou de tâches ANT ;

- la richesse des solutions de mise en relation des objets (collection, map, composition, agrégation, effacement en cascade…) ;

- la souplesse des langages de requêtage.

III-B - MÉTHODOLOGIE

Pour chaque solution mise en œuvre :

- une stratégie de persistance sera conçue ;

- un diagramme de classes UML présentera l'ensemble des classes et des interfaces à implémenter ;

- un projet Eclipse et un schéma de données seront créés ;

- la classe d'accès aux données, les classes persistantes, les fichiers de mapping et les annotations seront décrits ;

- les tables créées automatiquement par la couche de persistance seront détaillées.

III-C - PRÉSENTATION DES SOLUTIONS

III-C-1 - Hibernate

Hibernate est un framework « open source » gérant la persistance des objets Java et .NET en base de données relationnelle (Object-Relational Mapping). Hibernate est basé sur une architecture pragmatique. L'objectif d'Hibernate est de réduire les temps de développement de la couche de persistance de 95% tout en conservant un haut niveau de performance.

Caractéristiques générales :

- Hibernate est un projet « open source » (licence Lesser GPL) ;

- Hibernate, fondé en novembre 2001, rejoint JBoss, Inc en octobre 2003 ;

- Hibernate s'appuie sur des fichiers de configuration XML, décrivant les correspondances entre les attributs des objets et les colonnes des tables en base de données ;

- Hibernate exploite le mécanisme de réflexion de Java contrairement à d'autres frameworks instrumentant (modification du bytecode) les classes persistantes ;

- Hibernate peut être utilisé sur des clients lourds comme sur des serveurs d'applications ;

- Hibernate Query Language (HQL) permet d'écrire des requêtes sophistiquées ;

- Le développement de couche de persistance est facilité par l'utilisation de nombreux projets tiers :

- Hibernate Doclet : mécanisme de génération de ficher de configuration à partir de balises placées dans le code,

- Middlegen : générateur de code permettant de produire une couche de persistance à partir d'une base de données existante,

- AndroMDA : générateur de code suivant le modèle « Model Driven Architecture »,

- Spring : conteneur léger open source incluant le support d'Hibernate,

- Hibernate Synchronizer ou Hibernator : plug-ins Eclipse ;

- Hibernate supporte :

- les relations d'association, de composition, d'héritage et polymorphiques,

- les collections et les tableaux associatifs (API Collection & Map).

Vue générale de l'architecture d'Hibernate :

Principaux apports de la version 3 :

- les données peuvent être stockées et relues dans des bases de données comme dans des fichiers XML ;

- HQL supporte maintenant les opérations DELETE et UPDATE ;

- un objet peut être « mappé » sur plusieurs tables ;

- Hibernate gère maintenant les procédures stockées ;

- l'activité d'Hibernate peut être contrôlée via l'objet « Statistics » ou via JMX ;

- les fichiers de « mapping » XML peuvent être remplacés par des annotations placées dans le code des classes persistantes ;

- plus de 20 autres nouvelles fonctionnalités ont été ajoutées…

Limitation :

- Hibernate ne respecte pas les standards JDO (Java Data Object) & SDO (Service Data Objects) mais propose une API JPA.

III-C-2 - JDO 2.0

Java Data Object (JDO), initiée par Sun Microsystems, est une spécification d'accès aux données de façon transparente depuis n'importe quelle source de données transactionnelle. La deuxième version de JDO (JSR 243) a été acceptée le 13 mars 2006 par 15 membres du « Executive Committee for J2SE/J2EE » (incluant Sun Microsystems, BEA Systems, IBM, Oracle) sur 16. Le seizième membre, JBoss, Inc, s'est abstenu. La force de JDO est la possibilité de stocker et de retrouver, facilement, l'état de n'importe quel objet Java sérialisable et de proposer un langage de requêtage puissant.

Caractéristiques générales :

- JDO 1.0 est accepté comme standard de persistance en avril 2002 ;

- comme indiqué plus haut, JDO 2.0 devient un standard le 13 mars 2006 ;

- JDO s'appuie sur un ou plusieurs fichiers de configuration XML, décrivant les correspondances entre les attributs des objets et les colonnes des tables en base de données ;

- contrairement à Hibernate, JDO instrumente les classes Java compilées des objets persistants ;

- JDOQL, le langage de requêtage de JDO, permet d'écrire des requêtes complexes ;

- Comme Hibernate, JDO supporte :

- les relations d'association, de composition, d'héritage et polymorphiques,

- les collections et les tableaux associatifs (API Collection & Map).

Principaux apports de la version 2.0 :

- La plus importante évolution est l'enrichissement du langage JDOQL. :

- requêtage sur les interfaces du modèle,

- ajout des mots clés d'agrégation sum, min, max, average…,

- nouvelles opérations sur les chaînes de caractères (indexOf, toLowerCase, matches…),

- nouvelles opérations mathématiques (abs, sqrt),

- nouvelles opérations sur les tableaux associatifs (get, contains, size) ;

- la génération automatique d'objets persistants (JavaBeans) à partir d'interface Java ;

- la possibilité de détacher puis de rattacher un objet persistant à une session (Cette possibilité est une nouvelle caractéristique de EJB 3.0 et existe dans Hibernate depuis la version 2) ;

- la standardisation des métadonnées et la compatibilité binaire permettant de changer d'implémentation JDO 2.0 sans réinstrumenter les fichiers Java compilés des objets persistants.

Processus de développement :

- création des objets persistants sous forme de JavaBean ou d'interface. La seule contrainte, pour les JavaBeans, est qu'un constructeur par défaut doit être défini ;

- définition du « mapping » sous forme de fichiers XML ou de balises XDoclet. Les descriptions du « mapping » des objets d'un même package peuvent être regroupées dans un même fichier ;

- compilation des sources Java ;

- instrumentation (Enhancement) des fichiers Java compilés des objets persistants. Suivant les implémentations, cette étape est transparente grâce à l'utilisation de plug-ins Eclipse ou de tâches ANT ;

- exécution

Implémentation libre :

- JPOX 1.1.0 : Projet libre, sous licence « Apache 2 open source license », sélectionné par Sun comme implémentation de référence de JDO 2.0.

Implémentations commerciales :

- Kodo JDO : Poduit de SolarMetric, société acquise par BEA en novembre 2005 ;

- XIC : Xcalia Intermediation Core, le noyau d'intermédiation de Xcalia, permet d'accéder à des sources de données diverses via des API variées ;

- Versant Object Database 7.0 : Base de données orientée objet de l'éditeur Versant Corporation.

Les développements JDO 2.0 présentés dans ce document s'appuient sur JPOX.

III-C-3 - EJB 3.0

« Enterprise JavaBeans  » est une architecture de composants distribués hébergés au sein de serveurs d'applications J2EE. Jusqu'à la spécification EJB 2.1, la création d'un EJB nécessitait l'écriture d'une classe Java (contenant la logique métier et la définition de méthodes de « callback » souvent vides), d'une interface Home, d'une interface métier, et d'un nombre variable de fichiers de description XML dépendant du serveur J2EE utilisé.

Tout en restant compatible avec les versions antérieures, la spécification EJB 3.0 propose de simplifier le développement de composants EJB. Le nouveau modèle réduit la création d'un EJB à l'écriture d'une seule classe Java. Les informations, jusque-là présentes dans les fichiers de description, peuvent maintenant être placées soit dans un fichier XML, soit directement dans la classe Java grâce au mécanisme d'annotation de Java 5.0. La persistance des composants EJB Entity 3.0 est déléguée à l'API standard JPA (Java Persistence API) faisant elle-même partie de la spécification EJB 3.0 (JSR 220).

Bien que le développement de composants EJB soit considérablement allégé, EJB 3.0 n'est pas une spécification pour les « débutants » : les développeurs devront maîtriser les 76 annotations définies dans les spécifications ! Caractéristiques générales :

- EJB 1.1 est accepté comme standard en décembre 1999 ;

- EJB 3.0 devient un standard le 1er mai 2006.

Limitation :

- la persistance des EJB Entity 3.0 s'appuie sur Java Persistence API. JPA unifie les API d'accès aux données. Il devient possible de changer de frameworks de persistance (Hibernate, JDO, TopLink) sans modifier les applications exploitant JPA. Cependant, pour tirer le meilleur parti de ces frameworks, il est nécessaire d'utiliser des extensions propriétaires de JPA ;

- JPA ne permet pas d'enregistrer simplement des tableaux associatifs de type de base. Sauvegarder un objet de type « Map<String, String> » nécessite d'écrire un EJB Entity possédant :

- un attribut contenant la clé,

- un attribut contenant la valeur,

- un attribut contenant un identifiant technique ;

- une seule stratégie de persistance peut être appliquée à un graphe d'héritage ;

- la spécification JPA ne définit pas le périmètre des objets et des attributs détachés ;

- un seul niveau d'encapsulation d'objet est permis (« Embedded field ») ;

Principaux apports de la version 3.0 :

- un modèle de composants basé sur de simples objets et interfaces (« Plain Old Java Object » et « Plain Old Java Interface ») ;

- la persistance est déléguée à un composant tiers (implémentation JDO 2.0, Hibernate, TopLink 10g…) ;

- la définition des métadonnées peut être définie dans un fichier XML ou en utilisant le mécanisme d'annotation de Java 5.0 ;

- l'injection de dépendance (dependency injection) permet aux conteneurs J2EE d'initialiser les attributs des composants EJB. Ce mécanisme est déjà connu des utilisateurs du framework Spring, par exemple ;

- les interfaces « home » et métier ont disparu ;

- les déclarations des exceptions techniques (javax.ejb.CreateException, java.rmi.RemoteException…) sont éliminées ;

- le langage de requêtage a grandement été enrichi et devient JPQL ;

- EJB 3.0 supporte les relations d'héritage et les associations de type « OneToOne », « OneToMany », « ManyToOne » et « ManyToMany » ;

- Il est possible de détacher puis de rattacher un objet persistant à une session.

Processus de développement :

- création des classes persistantes (simples classes Java) ;

- définition du « mapping » sous forme d'annotations ou dans un fichier XML ;

- compilation ;

- packaging des EJB dans des fichiers JAR ou EAR ;

- déploiement ;

- exécution.

Implémentations libres :

- JBoss Application Server 4.0.4 : Serveur d'application libre de l'éditeur JBoss, Inc publié sous licence GPL le 13 mai 2006 ;

- EasyBeans : Conteneur EJB 3.0 « open source » de JonAS disponible en version « preview 2 ».

Implémentations commerciales :

- Oracle Application Server 10g (10.1.3) : Serveur d'application d'Oracle dispose d'une implémentation EJB 3.0 ;

- WebLogic 10 Preview Tech : Futur serveur de l'éditeur BEA.

IV - PRÉSENTATION DU CAS D'ÉTUDE

Ce document présente l'étude du développement de la couche de persistance de l'application de démonstration « PetStore » à l'aide du framework Hibernate et de produits implémentant les standards JDO 2.0 et EJB 3.0.

IV-A - DESCRIPTION DE L'APPLICATION « PET STORE »

Pour ceux ne connaissant pas encore PetStore, il s'agit d'une application de commerce en ligne de démonstration issue des BluePrints de Sun Microsystems. Elle permet à un utilisateur de naviguer sur un site Web pour voir quels sont les différents articles disponibles à la vente et d'ajouter dans son panier ceux qu'il désire acheter.

Page d'accueil du site Web PetStore

Les utilisateurs ont la possibilité :

- d'afficher le catalogue contenant des catégories de produits ;

- d'afficher les produits d'une catégorie ;

- d'afficher les produits disponibles ;

- et d'ajouter un produit à son panier.

IV-B - DESCRIPTION DE LA COUCHE DE PERSISTANCE

La couche de persistance de l'application « PetStore » fait appel à une première couche de service et d'une seconde couche d'accès aux données. Ce document propose l'étude du sous-module d'accès aux informations du catalogue de la boutique en ligne.

La couche de service est implémentée par la classe « CatalogEJB ». Ce composant « EJB Session Stateless » contient les règles métiers.

La couche d'accès aux données met en œuvre un modèle de conception (Design Pattern) « Data Access Object ». Cette architecture permet de découpler le modèle de données du mécanisme de persistance. Il devient possible de remplacer le serveur de base de données par une autre source de données transactionnelle ou de faire évoluer le schéma de la base sans modifier les classes du modèle de données de PetStore.

Diagramme de classe d'un sous-ensemble de la couche de persistance de PetStore

Description des classes :

Nom

Description

CatalogEJB

Composant EJB Session Stateless utilisé comme façade

CatalogDAOFactory,
CatalogDAO,
GenericCatalogDAO,
CloudscapeCatalogDAO

Composants formant un pattern « DAO ». L'application PetStore propose deux stratégies d'accès aux données au travers des classes : « GenericCatalogDAO » et « CloudscapeCatalogDAO »

Category

Classe métier représentant une catégorie de produit. Les catégories de l'application « PetStore » sont « Fish », « Dogs », « Reptiles », « Cats » et « Birds »

Product

Classe métier représentant un type de produit. Les produits de la catégorie « Dogs » sont « Bulldog », « Chihuahua »…

Item

Classe métier représentant un produit concret. Les articles du produit « Chihuahua » sont « Female Adult » et « Male Adult »

Page

Classe technique permettant de retourner un ensemble de plusieurs objets métier

IV-C - DESCRIPTION DU CAS D'ÉTUDE

L'étude va présenter le développement de trois nouvelles stratégies d'accès aux données mettant en œuvre le framework Hibernate et des produits implémentant les standards JDO 2.0 et EJB 3.0. Ces nouvelles stratégies pourront se substituer aux stratégies déjà présentes dans l'application « PetStore ».

Le développement d'une nouvelle stratégie va consister à concevoir et implémenter une classe d'accès aux données, implémentant l'interface « CatalogDAO », une liste de classes persistantes et un ensemble de fichiers de mapping ou d'annotations.

Diagramme de classe des nouvelles stratégies de persistance

Les trois nouvelles classes, implémentant l'interface « CatalogDAO » posséderont les méthodes d'accès suivantes :

Retourne un objet de type « Category » ayant l'identifiant « categoryID » et dont la langue des libellés est « l ».

Paramètres :

Retourne un ensemble d'objets de type « Category ».

Paramètres :

Retourne un objet de type « Product » ayant l'identifiant « productID » et dont la langue des libellés est « l ».

Paramètres :

Retourne un ensemble d'objets de type « Product ».

Paramètres :

Retourne un objet de type « Item » ayant l'identifiant « itemID » et dont la langue des libellés est « l ».

Paramètres :

Retourne un ensemble d'objets de type « Item ».

Paramètres :

Retourne un ensemble d'objets de type « Item » possédant l'une des caractéristiques suivantes :

Paramètres :

Prototypages & Descriptions

Category getCategory(String categoryID, Locale l) throws CatalogDAOSysException

- categoryID : identifiant de la catégorie à retourner ;

- l : langue des libellés.

Page getCategories(int start, int count, Locale l) throws CatalogDAOSysException

- start : identifiant de la première catégorie à retourner ;

- count : nombre de catégories à retourner ;

- l : langue des libellés.

Product getProduct(String productID, Locale l) throws CatalogDAOSysException

- productID : identifiant du produit à retourner ;

- l : langue des libellés.

Page getProducts(String categoryID, int start, int count, Locale l)
throws CatalogDAOSysException

- categoryID : identifiant de la catégorie à laquelle doivent appartenir les produits ;

- start : identifiant du premier produit à retourner ;

- count : nombre de produits à retourner ;

- l : langue des libellés.

Item getItem(String itemID, Locale l) throws CatalogDAOSysException

- itemID : identifiant de l'élément à retourner ;

- l : langue des libellés.

Page getItems(String productID, int start, int count, Locale l)
throws CatalogDAOSysException

- productID : identifiant du produit auquel doivent appartenir les éléments ;

- start : identifiant du premier produit à retourner ;

- count : nombre de produits à retourner ;

- l : langue des libellés.

la description de l'élément contient l'un des mots du paramètre « query » ; le nom du produit de l'élément contient l'un des mots du paramètre « query ».

Page searchItems(String query, int start, int count, Locale l)
throws CatalogDAOSysException

- query : liste de mots ;

- start : identifiant du premier produit à retourner ;

- count : nombre de produits à retourner ;

- l : langue des libellés.

V - RÉALISATIONS

Ce paragraphe présente, étape par étape, la réalisation de la couche de persistance de l'application « PetStore » à l'aide du framework Hibernate 3.1.3, de l'implémentation JPOX 1.1.0 de JDO 2.0 et de JBoss AS 4.0.4. Les trois développements seront effectués sur la configuration suivante :

- Plateforme Java : Java 5.0 ;

- Serveur de base de données : MySQL 5.0 ;

- Environnement de développement : Eclipse 3.1.

V-A - RÉALISATION DE LA COUCHE DE PERSISTANCE À L'AIDE D'HIBERNATE

La première implémentation, de la couche de persistance de l'application « PetStore », va mettre en œuvre Hibernate 3.1.3.

V-A-1 - Conception

La conception de la couche de persistance est simple, comme le montre le diagramme de classes suivant. La classe « HibernateCatalogDAO », implémentant l'interface « CatalogueDAO », aura la charge d'accéder aux services de persistance d'Hibernate et de retourner les données sauvegardées en base de données, dans des objets du modèle de « PetStore ».

Diagramme général de classes

Pour découpler les classes du modèle de « PetStore » des classes persistantes Hibernate, nous n'avons pas utilisé de relations d'héritages entre elles. Cela nous oblige à écrire une classe de conversion traduisant les objets du package « fr.ippon.petstore.hibernate.model » en objets du package « com.sun.j2ee.blueprints.catalog.model ».

La complexité des services, offerts par la classe « HibernateCatalogDAO », est réduite. Son écriture permet cependant de mettre en œuvre le « mapping » de tableaux associatifs et une requête HQL complexe.

V-A-2 - Implémentation

Un nouveau projet Eclipse, nommé « PetStore Hibernate », est créé.

Un nouveau schéma MySQL, nommé « hibernate », est créé.

Comme le laissait supposer la phase de conception, l'implémentation de la couche de persistance contient un nombre réduit de classes.

Le projet comprend :

- le package « dao  » contient la classe d'accès aux données ;

- le package « model  » regroupe les classes persistantes et leurs fichiers de description Hibernate ;

- le package « util » contient :

- la classe de conversion des classes persistantes en classes du modèle de données de « PetStore »,

- une classe utilitaire retournant des instances de la classe « Session  » produites par un singleton de type « SessionFactory »,

- le fichier de configuration Hibernate.

De plus, un package « test » contient :

- une classe permettant d'initialiser la base de données ;

- une seconde classe exécutant une série de tests unitaires.

V-A-2-a - Fichier de configuration « hibernate.cfg.xml »

Ce fichier contient les paramètres généraux de configuration d'Hibernate. On y trouve par exemple :

- les paramètres de connexion à la source de données ;

- la liste des fichiers de « mapping » des classes persistantes ;

- le paramètre d'activation des traces SQL ;

- la configuration du cache d'objets.

Fichier de configuration « hibernate.cfg.xml »

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory >

<!-- local connection properties -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.username">java</property>
<property name="hibernate.connection.password">java</property>

<!-- dialect for MySQL -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>
<property name="current_session_context_class">thread</property>

<mapping resource="fr/ippon/petstore/hibernate/model/HibCategory.hbm.xml"/>
<mapping resource="fr/ippon/petstore/hibernate/model/HibCategoryDetail.hbm.xml"/>
<mapping resource="fr/ippon/petstore/hibernate/model/HibItem.hbm.xml"/>
<mapping resource="fr/ippon/petstore/hibernate/model/HibItemDetail.hbm.xml"/>
<mapping resource="fr/ippon/petstore/hibernate/model/HibProduct.hbm.xml"/>
<mapping resource="fr/ippon/petstore/hibernate/model/HibProductDetail.hbm.xml"/>

</session-factory>
</hibernate-configuration>

V-A-2-b - Implémentation de la stratégie d'accès aux données

« HibernateCatalogDAO » est la classe d'accès aux données. Elle possède :

- 3 méthodes d'accès direct aux objets de type « Category », « Product » et « Item » ;

- 3 méthodes d'accès à des listes d'objets ;

- 1 méthode de recherche multicritère d'objets de type « Item ».

Les 3 méthodes d'accès direct aux objets sont construites sur le même modèle. L'implémentation de la méthode « getCategory » est la suivante :

Méthode « getCategory »

package fr.ippon.petstore.hibernate.dao;

[...]

public class HibernateCatalogDAO implements CatalogDAO {

[...]

public Category getCategory(String categoryID, Locale locale)
throws CatalogDAOSysException
{
Session session = HibernateUtil.currentSession();
Transaction tx = null;
Category category = null;
int id = Integer.parseInt(categoryID);

try
{
tx = session.beginTransaction();

HibCategory hibCategory = (HibCategory)session.load(HibCategory.class, id);
category = Converter.convertHibCategory(hibCategory, locale);

tx.commit();
}
catch (HibernateException e)
{
if (tx != null && tx.isActive())
tx.rollback();
throw new CatalogDAOSysException(e.getMessage());
}
finally
{
HibernateUtil.closeSession();
}
return category;
}
[...]
}

Description de la méthode :

- la méthode « currentSession() » de la classe « HibernateUtil » retourne la session Hibernate du thread courant ;

- un objet « Transaction » est créé ;

- l'objet persistant « HibCategory », dont l'identifiant correspond au paramètre « categoryID », est retrouvé en appelant simplement la méthode « load » de l'objet « session ». Cet appel lance l'exécution d'une série de requêtes SQL pour retrouver l'état de l'objet « category », crée et initialise une nouvelle instance de la classe « HibCategory » puis, place l'objet reconstruit dans le système de cache ;

- l'objet « HibCategory » est converti en objet du modèle de données « Category » ;

- la transaction est « commitée » bien qu'aucune donnée n'ait été mise à jour.

Les 3 méthodes de recherche de liste d'objets sont également construites sur le même modèle. L'implémentation de la méthode « getItems » est la suivante :

Méthode « getItems »

package fr.ippon.petstore.hibernate.dao;
[...]
public class HibernateCatalogDAO implements CatalogDAO
{
[...]
public Page getItems(String productID, int start, int count, Locale locale)
throws CatalogDAOSysException
{
Session session = HibernateUtil.currentSession();
Transaction tx = null;
Page page = null;

try
{
tx = session.beginTransaction();

List list = session.createQuery("from HibItem as i where i.product.id = ?")
.setString(1, productID)
.setFirstResult(start)
.setMaxResults(count + 1)
.list();

page = new Page(Converter.convertHibItemList(list, locale),
start,
(list.size() > count));

tx.commit();
}
catch (HibernateException e)
{
if (tx != null && tx.isActive())
tx.rollback();
throw new CatalogDAOSysException(e.getMessage());
}
finally
{
HibernateUtil.closeSession();
}

return page;
}
[...]
}

Description de la méthode :

- recherche de la session Hibernate courante et création d'un objet de type « Transaction » comme précédemment ;

- recherche des objets persistants « HibItem », dont l'identifiant du produit associé correspond au paramètre « productID ». Le premier élément de la liste sera le « start »ième du « result set » et la liste aura une taille de « count+1 » objets au plus ;

- la liste des objets retournés est convertie en une liste d'objets de type « Item » ;

- la transaction est « commitée ».

Remarque : Le dernier paramètre du constructeur de la classe « Page » est un booléen qui indique que d'autres pages pourront être retournées par la méthode « getItems ». Pour le renseigner, une liste de « count+1 » objets est recherchée. Si Hibernate retourne plus de « count » éléments, cela signifie que d'autres pages sont disponibles.

La dernière méthode, à écrire, met en œuvre une requête HQL complexe. « searchItems() » recherche des objets de type « Item » possédant l'une des caractéristiques suivantes :

- la description de l'élément contient l'un des mots passés en paramètre ;

- le nom du produit, de l'élément, contient l'un des mots passés en paramètre.

Prototypage de la méthode :

Page searchItems(String query, int start, int count, Locale l)
throws CatalogDAOSysException

Paramètres :

- query : liste de mots à rechercher ;

- start : identifiant du premier produit à retourner ;

- count : nombre de produits à retourner ;

- l : langue des libellés.

Les mots à rechercher sont passés en paramètre dans une chaîne de caractères et sont séparés par des espaces.

Chacun d'eux sera converti en minuscule puis placé dans une variable « token ».

Les classes accédées, lors de l'exécution de la requête, sont les suivantes :

Diagramme des classes et des attributs accédés lors de la recherche

Écriture de la requête HQL :

- la requête doit retourner des objets de type « HibItem » :

"from HibItem as i where ... "

- les objets « HibItem », dont la description contient l'un des mots passés en paramètre, doivent être retournés :

"lower(i.details[' " +  localeStr + " '].description) like '% " +  token + " %' "

- les objets « HibItem », dont le nom du produit contient l'un des mots passés en paramètre, doivent être retournés :

" lower(i.product.details[' " +  localeStr + " '].name) like '% " +  token + " %' "

Le code source générant la requête HQL est le suivant :

String queryStr = "from HibItem";

if ((query != null) && (query.trim().length() > 0))
{
String localeStr = locale.toString();
queryStr += " as i where";

StringTokenizer st = new StringTokenizer(query);
if (st.hasMoreTokens())
{
String token = st.nextToken().toLowerCase();
queryStr += " lower(i.product.details['" + localeStr + "'].name) like '%" + token + "%'";
queryStr += " or";
queryStr += " lower(i.details['" + localeStr + "'].description) like '%" + token + "%'";

while (st.hasMoreTokens())
{
token = st.nextToken().toLowerCase();

queryStr += " or";
queryStr += " lower(i.product.details['" + localeStr + "'].name) like '%" + token + "%'";
queryStr += " or";
queryStr += " lower(i.details['" + localeStr + "'].description) like '%" + token + "%'";
}
}
}

Remarque : Dans un souci de lisibilité, l'extrait de code précédent construit la requête HQL en concaténant un grand nombre de chaînes de caractères à la variable « queryStr ». Dans une implémentation réelle, un objet de type « StringBuffer » aurait été utilisé.

La requête produite, pour les paramètres « Query » = « Cats Male White » et « l » = Local.UK , est la suivante :

from HibItem as i where lower(i.product.details['en_GB'].name) like '%Cats%'
or lower(i.details['en_GB'].description) like '%Cats%'
or lower(i.product.details['en_GB'].name) like '%Male%'
or lower(i.details['en_GB'].description) like '%Male%'
or lower(i.product.details['en_GB'].name) like '%White%'
or lower(i.details['en_GB'].description) like '%White%'

Cette requête complexe met en œuvre les caractérises de HQL suivantes :

- navigation au travers d'une hiérarchie d'objets persistants ;

- accès aux données d'une table de hachage ;

- opérations sur les chaînes de caractères (lower, like).

Une fois la requête générée, son exécution s'effectue comme nous l'avons vu dans le paragraphe précédent.

V-A-2-c - Implémentation de la classe persistante « HibItem »

Les classes « HibCategory », « HibProduct » et « HibItem » sont construites sur le même modèle. Nous allons examiner l'implémentation de la classe HibItem dans ce paragraphe.

La classe « HibItem » est un simple composant JavaBean possédant un constructeur par défaut et des accesseurs pour chacun de ses attributs.

HibItem.java

package fr.ippon.petstore.hibernate.model;
[...]
public class HibItem
{
private int id;
private HibProduct product;
private Map<String, HibItemDetail> details;

public HibItem()
{
this.id = 0;
this.details = new HashMap<String, HibItemDetail>();
}

public int getId() { return id; }
public void setId(int id) { this.id = id; }

public HibProduct getProduct() { return product; }
public void setProduct(HibProduct product) { this.product = product; }

public Map<String, HibItemDetail> getDetails() { return details; }
public void setDetails(Map<String, HibItemDetail> details) { this.details=details; }

public HibItemDetail getDetail(Locale l) {
if (l == null)
l = Locale.getDefault();

return this.details.get(l.toString());
}

public void setDetail(Locale l, HibItemDetail detail) {
if (l == null)
l = Locale.getDefault();
this.details.put(l.toString(), detail);
}
}

Le fichier de description Hibernate associé est le suivant :

Fichier de description « HibItem.hbm.xml »

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="fr.ippon.petstore.hibernate.model.HibItem" table="ITEM">
<id name="id" type="integer" column="ITEM_ID">
<generator class="increment"/>
</id>

<many-to-one name="product" column="PRODUCT_ID"
class="fr.ippon.petstore.hibernate.model.HibProduct"/>

<map name="details" lazy="true" table="ITEM_ITEMDETAIL" cascade="all, delete-orphan">
<key column="FK_ITEM_ID" not-null="true"/>
<index column="LOCALE" type="string"/>
<one-to-many class="fr.ippon.petstore.hibernate.model.HibItemDetail"/>
</map>
</class>

</hibernate-mapping>

Description du fichier « HibItem.hbm.xml » :

- l'état des objets « HibItem » sera conservé dans la table nommée ITEM ;

- la classe « HibItem » possède une clé technique « id » et deux attributs « product » et « details » ;

- la clé technique sera générée automatiquement par Hibernate en utilisant le générateur natif ;

- l'attribut « product » référence un objet persistant « HibProduct ». Sa clé sera stockée dans la colonne nommée PRODUCT_ID ;

- l'attribut « details » est une table de hachage dont la clé est une chaîne de caractères et la valeur une référence à un objet de type « HibItemDetail ». Cet attribut sera conservé dans une table de jointure nommée ITEM_ITEMDETAIL.

V-A-2-d - Implémentation de la classe persistante « HibItemDetail »

Les classes « HibCategoryDetail », « HibProductDetail » et « HibItemDetail » sont construites sur le même modèle. Nous allons examiner l'implémentation de la classe « HibItemDetail » dans ce paragraphe. Elle contient les informations localisées de la classe « HibItem ».

Comme « HibItem », la classe « HibItemDetail » est un simple composant JavaBean possédant un constructeur par défaut et des accesseurs pour chacun de ses attributs. Son fichier de description Hibernate est le suivant :

Fichier de description « HibItemDetail.hbm.xml »

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="fr.ippon.petstore.hibernate.model.HibItemDetail" table="ITEMDETAIL">
<id name="id" type="integer" column="ITEMDETAIL_ID">
<generator class="increment"/>
</id>

<property name="description" type="string"/>
<property name="attribute1" type="string"/>
<property name="attribute2" type="string"/>
<property name="attribute3" type="string"/>
<property name="attribute4" type="string"/>
<property name="attribute5" type="string"/>
<property name="listPrice" type="double"/>
<property name="unitCost" type="double"/>
<property name="imageLocation" type="string"/>
</class>

</hibernate-mapping>

V-A-2-e - Implémentation de la classe utilitaire

La classe « HibernateUtil » permet de :

- charger le fichier de configuration d'Hibernate et d'initialiser le singleton sessionFactory ;

- retourner l'objet de type « Session » associé au thread courant ;

- libérer l'objet de type « Session ».

Son code source est le suivant :

HibernateUtil.java

package fr.ippon.petstore.hibernate.util;
[...]
public class HibernateUtil
{
private static final SessionFactory sessionFactory;

static
{
try
{
// Création d'une fabrique
URL url = HibernateUtil.class.getResource("hibernate.cfg.xml");
sessionFactory = new Configuration().configure(url).buildSessionFactory();
}
catch (HibernateException ex)
{
throw new RuntimeException("Problème de configuration : " + ex.getMessage(), ex);
}
}

public static final ThreadLocal session = new ThreadLocal();

public static Session currentSession() throws HibernateException
{
Session s = (Session)session.get();

// Création d'une nouvelle session Hibernate pour le Thread courant
if (s == null)
{
s = sessionFactory.openSession();
session.set(s);
}

return s;
}

public static void closeSession() throws HibernateException
{
Session s = (Session)session.get();
session.set(null);
if (s != null)
s.close();
}
}

V-A-2-f - Implémentation de la classe de conversion

Le rôle de la classe « Converter » est de créer les objets du modèle à partir des objets persistants Hibernate. L'objectif est de découpler ces deux ensembles d'objets afin de limiter les modifications à apporter au projet en cas d'évolution de l'une de ces deux parties. Le code source de la méthode de conversion d'un objet de type « HibItem », par exemple, est le suivant :

package fr.ippon.petstore.hibernate.util;
[...]
public class Converter
{
[...]
public static Item convertHibItem(HibItem i, Locale locale)
{
HibProduct product = i.getProduct();
HibItemDetail details = i.getDetail(locale);
return new Item(String.valueOf(product.getCategory().getId()),
String.valueOf(product.getId()),
product.getDetail(locale).getName(),
String.valueOf(i.getId()),
details.getImageLocation(),
details.getDescription(),
details.getAttribute1(),
details.getAttribute2(),
details.getAttribute3(),
details.getAttribute4(),
details.getAttribute5(),
details.getListPrice(),
details.getUnitCost());
}
[...]
}

Le code source de la méthode de conversion d'une liste d'objets de type « HibItem » est le suivant :

package fr.ippon.petstore.hibernate.util;
[...]
public class Converter
{
[...]
public static List convertHibItemList(List<HibItem> list, Locale locale)
{
ArrayList<Item> result = new ArrayList<Item>(list.size());

for (HibItem item : list)
result.add(convertHibItem(item, locale));

return result;
}
}

V-A-3 - Création du schéma de base de données

Le fichier de configuration d'Hibernate définit la propriété « hibernate.hbm2ddl.auto » à «  update ». Cela indique à Hibernate de mettre à jour le schéma de la base de données à chaque exécution. Lors de la première utilisation de la couche de persistance Hibernate, le schéma suivant est créé :

Schéma de la base de données produit par Hibernate

L'absence de table associée à la classe « HibCategoryDetail » et la diversité des noms sont dues aux différents types de mapping utilisés. Nous allons les voir plus en détail :

- Persistance des classes « HibCategory » et « HibCategoryDetail »
L'attribut « details » de la classe « HibCategory » a été déclaré comme « serializable » dans le fichier de description « HibCategory.hbm.xml ». Lors de l'enregistrement d'un objet de ce type, Hibernate transforme la table de hachage « details » en flux binaire et le place dans une colonne. Ce mécanisme permet de sauvegarder n'importe quelle arborescence d'objets « serializable » en base de données sans devoir écrire de fichier de description de mapping.

Extrait du fichier « HibCategory.hbm.xml »

<class name="fr.ippon.petstore.hibernate.model.HibCategory" table="CATEGORY">
<id name="id" type="integer" column="CATEGOTY_ID">
<generator class="increment"/>
</id>

<property name="details" type="serializable">
<column name="DETAILS" sql-type="BLOB"/>
</property>
</class>

- Persistance des classes « HibProduct » et « HibProductDetail »
Les noms des tables et des clés primaires n'ont pas été renseignés (sauf pour la colonne PRODUCT_ID car son nom rentrait en conflit avec le nom de la clé primaire). Hibernate crée les tables « hibproduct » et « hibproductdetail » dont les noms correspondent aux noms non qualifiés des classes.

- Persistance des classes « HibItem » et « HibItemDetail »
Les noms des tables et des clés primaires ont été précisés dans les fichiers de description, Hibernate crée donc les tables « item » et « itemdetail ».

V-B - RÉALISATION DE LA COUCHE DE PERSISTANCE À L'AIDE DE JDO 2.0

La seconde implémentation de la couche de persistance de l'application « PetStore » sera basée sur le projet open source JPOX 1.1.0.

V-B-1 - Conception

La conception de la couche de persistance JDO va reprendre le modèle utilisé par la couche de persistance Hibernate. Cela est possible grâce, aux caractéristiques communes de mapping d'Hibernate et de JDO, et à la richesse des langages de requêtage HQL et JDOQL. L'intérêt de réutiliser le même modèle de conception est de faciliter la comparaison des deux implémentations.

La classe « JDOCatalogDAO », implémentant l'interface « CatalogueDAO », aura la charge d'accéder aux services de persistance de JPOX et de retourner les données sauvegardées en base de données dans des objets du modèle de « PetStore ».

Diagramme de classes général

Ce diagramme diffère un peu de celui de la couche de persistance d'Hibernate : les classes « JDOCategoryDetail », « JDOProductDetail » et « JDOItemDetail » ne possèdent pas d'attribut « id ». Dans la première implémentation, cet attribut contenait une clé unique identifiant un objet persistant. Contrairement à Hibernate, JDO n'impose pas de définir un tel attribut. Cette donnée étant purement technique, nous pouvons laisser le soin à JDO de la définir et la gérer à notre place. Pour cela, nous placerons l'information « identity-type=« datastore » » dans la définition du mapping de ces trois classes.

Remarque : La seule contrainte, imposée par JDO sur les classes persistantes, est d'avoir un constructeur par défaut. Il peut être privé. Cependant, JPOX, Kodo JDO et LiDo le rajoutent implicitement s'il n'a pas été défini (Kodo JDO émet un « warning » lors de l'instrumentation si cette situation se présente).

V-B-2 - Implémentation

- Un nouveau projet Eclipse, nommé « PetStore JDO » est créé.

- Le plug-in « Eclipse JPOX Plugin » est installé.

- Un nouveau schéma MySQL, nommé « jdo », est créé.

L'implémentation de la couche de persistance JDO diffère peu de celle de la couche de persistance Hibernate. Seuls, les fichiers de description du mapping et les appels JDO changent.

Les descriptions du mapping des classes persistantes ont été regroupées dans le fichier « package.jdo ». La structure de ce fichier est décrite dans la spécification. Elle est indépendante de l'implémentation JDO choisie. Il aurait été possible :

- de créer un fichier « .jdo » par classe persistante (JDOCatalog.jdo, JDOProduct.jdo…) ;

- de placer le fichier « package.jdo » dans : « fr.ippon.petstore.jdo », « fr.ippon.petstore », « fr.ippon »…, ou directement dans le « CLASSPATH ».

Le fichier de configuration « jpox.properties » est propre au produit JPOX.

Le projet comprend trois packages :

- le package « dao » contient la classe d'accès aux données ;

- le package « model » regroupe les classes persistantes et le fichier de description JDO ;

- Le package « util » contient :

- la classe de conversion des classes persistantes en classes du modèle de données de PetStore,

- une classe utilitaire retournant un singleton de type « PersistenceManagerFactory »,

- le fichier de configuration JPOX ;

Le package « test » contient :

- une classe permettant d'initialiser la base de données ;

- une seconde classe exécutant une série de tests unitaires.

V-B-2-a - Fichier de configuration

Le fichier « jpo.properties » joue, pour JDO, le même rôle que le fichier « hibernate.cfg.xml », pour Hibernate. Il contient des paramètres JDO et des paramètres non standard propres à l'implémentation JPOX. Le fichier de configuration du projet est le suivant :

Fichier de configuration « jpox.properties »

#
# jpox.properties
#

javax.jdo.PersistenceManagerFactoryClass=org.jpox.PersistenceManagerFactoryImpl
javax.jdo.option.ConnectionDriverName=com.mysql.jdbc.Driver
javax.jdo.option.ConnectionURL=jdbc:mysql://localhost:3306/jdo
javax.jdo.option.ConnectionUserName=java
javax.jdo.option.ConnectionPassword=java

org.jpox.autoCreateSchema=true
org.jpox.autoCreateTables=true
org.jpox.autoCreateConstraints=true
org.jpox.validateTables=true
org.jpox.validateConstraints=true

V-B-2-b - Implémentation de la stratégie d'accès aux données

« JDOCatalogDAO » est la classe d'accès aux données. Comme la classe « HibernateCatalogDAO », elle implémente l'interface « CatalogDAO » et possède les méthodes suivantes :

- 3 méthodes d'accès direct aux objets de type « Category », « Product » et « Item » ;

- 3 méthodes d'accès à des listes d'objets ;

- 1 méthode de recherche multicritère d'objets de type « Item ».

Les 3 méthodes d'accès direct aux objets sont construites sur le même modèle. L'implémentation de la méthode « getCategory » est la suivante :

package fr.ippon.petstore.jdo.dao;
[...]
public class JDOCatalogDAO implements CatalogDAO
{
[...]
public Category getCategory(String categoryID, Locale locale)
throws CatalogDAOSysException
{
Category category = null;
PersistenceManager pm =
JDOUtil.getPersistenceManagerFactory().getPersistenceManager();
Transaction tx = pm.currentTransaction();
int id = Integer.parseInt(categoryID);

try {
tx.begin();

JDOCategory jdoCategory = (JDOCategory)pm.getObjectById(JDOCategory.class, id);
category = Converter.convertJDOCategory(jdoCategory, locale);

tx.commit();
} catch (Exception e) {
if (tx != null && tx.isActive())
tx.rollback();

throw new CatalogDAOSysException(e.getMessage());
}

return category;
}
[...]
}

Description de la méthode :

- la méthode « getPersistenceManager() » de la classe « PersistenceManagerFactory » crée un objet de type « PersistenceManager » ;

- un objet « Transaction » est créé et la transaction est démarrée ;

- l'objet persistant « JDOCategory », dont l'identifiant correspond au paramètre « categoryID », est retrouvé en appelant la méthode « getObjectById() » du « persistence manager » ;

- l'objet « JDOCategory » est converti en objet « Category » du modèle de données ;

- la transaction est « commitée » bien qu'aucune donnée n'ait été mise à jour.

Remarque : Nous retrouvons exactement la même enchaînement logique dans la méthode « getCategory » de l'objet « HibernateCatalogDAO ».

Les 3 méthodes de recherche de liste d'objets sont construites sur le même modèle. L'implémentation de la méthode « getItems » est la suivante :

package fr.ippon.petstore.jdo.dao;
[...]
public class JDOCatalogDAO implements CatalogDAO
{
[...]
public Page getItems(String productID, int start, int count, Locale locale)
throws CatalogDAOSysException
{
Page page = null;
PersistenceManager pm =
JDOUtil.getPersistenceManagerFactory().getPersistenceManager();
Transaction tx = pm.currentTransaction();

try {
tx.begin();

Query query = pm.newQuery(JDOItem.class, "product.id = :id");
query.setRange(start, start+count+1);
Collection col = (Collection)query.execute(productID);

page = new Page(Converter.convertJDOItemList(col, locale),
start,
(col.size() > count));

tx.commit();
} catch (Exception e) {
if (tx != null && tx.isActive())
tx.rollback();

throw new CatalogDAOSysException(e.getMessage());
}

return page;
}
[...]
}

Description de la méthode :

- un objet « PersistenceManager » est créé par l'objet « PersistenceManagerFactory » ;

- une transaction est créée et démarrée ;

- une requête JDO est créée. Le filtre, passé à la requête, indique que les objets de type « JDOItem », dont l'identifiant du produit associé correspond au paramètre « productID », doivent être retournés. Le filtre aurait pu également s'écrire « this.product.id=? » où « this » aurait représenté une instance « JDOItem » stockée en base de données.

- on indique que le premier élément de la liste sera le « start »ième et que la liste aura une taille de « count+1 » au plus.

- la requête est exécutée et la liste d'objets retournée est convertie en une liste d'objets de type « Item ».

- la transaction est « commitée ».

La dernière méthode met en œuvre une requête HQL complexe. « searchItems() » recherche des objets de type « Item » possédant l'une des caractéristiques suivantes :

- la description de l'élément contient l'un des mots passés en paramètre ;

- le nom du produit, de l'élément, contient l'un des mots passés en paramètre.

Prototypage de la méthode :

Page searchItems(String query, int start, int count, Locale l)
throws CatalogDAOSysException

Paramètres :

- query : liste de mots ;

- start : identifiant du premier produit à retourner ;

- count : nombre de produits à retourner ;

- l : langue des libellés.

Les mots sont passés en paramètre dans une chaîne de caractères séparés par des espaces.

Chacun d'eux sera converti en minuscule, puis placé dans une variable « token ».

Les classes accédées, lors de l'exécution de la requête, sont les suivantes :

Diagramme des classes et des attributs accédés lors de la recherche

Écriture du filtre de la requête :

- Pour chaque mot, passé en paramètre, les objets « JDOItem », dont la description contient le mot courant, doivent être retournés :

"this.details.get(locale).description.toLowerCase().indexOf(' " +  token + " ') != -1 "

- « locale » est un paramètre de la requête JDO. Il contiendra la valeur du paramètre l passé à la méthode. Nous verrons la déclaration dans le paragraphe suivant.

- Pour chaque mot, passé en paramètre, les objets « JDOItem », dont le nom du produit contient le mot courant, doivent être retournés :

"this.product.details.get(locale).name.toLowerCase().indexOf(' " +  token + " ') != -1 "

Le bloc de code Java générant la requête HQL est le suivant :

String filter = null;

// Construction d'un filtre
StringTokenizer st = new StringTokenizer(query);
if (st.hasMoreTokens())
{
String token = st.nextToken().toLowerCase();

filter = "(this.product.details.get(locale).name.toLowerCase().indexOf('" + token + "') != -1)" +
"||(this.details.get(locale).description.toLowerCase().indexOf('" + token + "') != -1)";

while (st.hasMoreTokens())
{
token = st.nextToken().toLowerCase();

filter += "||(this.product.details.get(locale).name.toLowerCase().indexOf('" + token + "') != -1)" +
"||(this.details.get(locale).description.toLowerCase().indexOf('" + token + "') != -1)";
}
}

La requête produite, pour le paramètre « Query » = « Cats Male White » est la suivante :

(this.product.details.get(locale).name.toLowerCase().indexOf('Cats') != -1) ||
(this.details.get(locale).description.toLowerCase().indexOf('Cats') != -1) ||
(this.product.details.get(locale).name.toLowerCase().indexOf('Male') != -1) ||
(this.details.get(locale).description.toLowerCase().indexOf('Male') != -1) ||
(this.product.details.get(locale).name.toLowerCase().indexOf('White') != -1) ||
(this.details.get(locale).description.toLowerCase().indexOf('White') != -1)

Une fois générée, la requête est exécutée à l'aide des 4 lignes suivantes :

Query complexQuery = pm.newQuery(JDOItem.class, filter);
complexQuery.declareParameters("String locale");
complexQuery.setRange(start, start+count+1);
col = (Collection)complexQuery.execute(locale.toString());

Description :

- une nouvelle requête JDO est créée. Elle porte seulement sur les objets de type « JDOItem » et utilisera le filtre qui vient d'être produit ;

- le paramètre de requête « locale » est déclaré ;

- le premier élément de la liste sera le « start »ième du « result set ». Le dernier, s'il existe, sera le « start+count+1 »ième ;

- la requête est exécutée.

Cette requête complexe met en œuvre les caractéristiques de JDOQL suivantes :

- navigation au travers d'une hiérarchie d'objets ;

- accès aux données d'une table de hachage ;

- opérations sur les chaînes de caractères (toLowerCase, indexOf).

V-B-2-c - Implémentation de la classe persistante « JDOItem »

Les classes « JDOCategory », « JDOProduct » et « JDOItem » sont construites sur le même modèle. Nous allons examiner l'implémentation de la classe JDOItem dans ce paragraphe.

La classe « JDOItem » est un simple composant JavaBean possédant un constructeur par défaut et des accesseurs pour chacun de ses attributs. Voici l'extrait du fichier « package.jdo » définissant le mapping de la classe « JDOItem » :

<class name="JDOItem" table="ITEM" identity-type="application">
<field name="id" persistence-modifier="persistent"
primary-key="true" value-strategy="native">
<column name="ITEM_ID"/>
</field>
<field name="product" persistence-modifier="persistent"/>
<field name="details" table="ITEM_ITEMDETAIL">
<map key-type="String" value-type="JDOItemDetail"/>
<join><column name="ITEM_ID"/></join>
<key><column name="LOCALE" length="10"/></key>
<value><column name="ITEMDETAIL_ID"/></value>
</field>
</class>

Description du mapping :

- l'état des objets « JDOItem » sera conservé dans la table nommée ITEM ;

- la classe « JDOItem » possède une clé technique « id » et deux attributs métier « product » et « details » ;

- la clé technique sera générée automatiquement par JPOX en utilisant le générateur natif ;

- l'attribut « product » référence un objet persistant « HibProduct » ;

- l'attribut « details » est une table de hachage dont la clé est une chaîne de caractères et la valeur une référence vers un objet de type « JDOItemDetail ». Cet attribut sera conservé dans une table de jointure nommée ITEM_ITEMDETAIL.

La classe de détail associée à « JDOItem » est un simple JavaBean. Elle ne sera pas présentée.

V-B-2-d - Implémentation de la classe utilitaire

La classe « JDOUtil » permet de charger le fichier de configuration JPOX et d'initialiser le singleton « PersistenceManagerFactory ». Son code source est le suivant :

package fr.ippon.petstore.jdo.util;
[...]
public class JDOUtil
{
public static final PersistenceManagerFactory persistenceManagerFactory;

static
{
InputStream is = null;

try
{
is = JDOUtil.class.getResourceAsStream("jpox.properties");

Properties jdoProperties = new Properties();
jdoProperties.load(is);

// Création de la PersistenceManagerFactory à partir de jpox.properties
persistenceManagerFactory = JDOHelper.getPersistenceManagerFactory(jdoProperties);
}
catch (Throwable ex)
{
System.err.println("Initial PersistenceManagerFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
finally
{
if (is != null)
try
{
is.close();
}
catch (IOException ex)
{
System.err.println("Close JPOX properties file failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
}

public static final ThreadLocal session = new ThreadLocal();

public static PersistenceManagerFactory getPersistenceManagerFactory()
{
return persistenceManagerFactory;
}
}

V-B-2-e - Implémentation de la classe de conversion

Le rôle de la classe « Converter » est de créer des objets du modèle à partir des objets persistants JDO. Son implémentation est la même que celle de la classe « fr.ippon.petstore.hibernate.util.Converter ».

V-B-3 - Instrumentation des classes persistantes

Une fois compilées, les classes persistantes doivent être instrumentées. Cette opération peut être effectuée en ligne de commande, via une tâche ANT ou, plus simplement, à l'aide du plug-in de JPOX.

Menu de l'activation de l'instrumentation automatique

Lorsque l'option « Enable AutoEnhencement » est activée, toutes les modifications du projet déclencheront l'exécution de la ligne de suivante :

java org.jpox.enhancer.JPOXEnhancer ${src}\fr\ippon\petstore\jdo\model\package.jdo

Si l'application de test est lancée sans avoir instrumenté les classes persistantes, JPOX lance l'exception suivante :

javax.jdo.JDOUserException: Found Meta-Data for class fr.ippon.petstore.jdo.model
.JDOItem but this class is not enhanced!! Please enhance the class before running JPOX.

V-B-4 - Création du schéma de base de données

Les trois propriétés suivantes, définies dans le fichier de configuration « jpox.properties », indique à JPOX de mettre à jour le schéma de la base de données à chaque exécution.

Extrait du fichier de configuration « jpox.properties »

org.jpox.autoCreateSchema=true
org.jpox.autoCreateTables=true
org.jpox.autoCreateConstraints=true

Lors de la première utilisation de la couche de persistance, le schéma suivant est créé :

Schéma de la base de données produit par JPOX

Remarque : Bien que nous ayons omis de créer un attribut « id » dans les trois classes de détail, JPOX en ajoute un, durant la phase d'instrumentation, et crée une colonne BIGINT dans chacune des tables associées.

La disparité du schéma tient au fait que trois types de mapping différents ont été utilisés pour stocker les tables de hachage des objets « JDOCategory », « JDOProduct » et « JDOItem ». Nous allons les voir plus en détail :

- Persistance des classes « JDOCategory » et « JDOCategoryDetail » :

- aucun nom de table n'a été fourni dans la description du mapping de « JDOCatalog ». JPOX a créé une table « jdocategory » ;

- « serialized=« true » » indique à JPOX de stocker la table de hachage sérialisée dans une seule colonne. Comme pour Hibernate, ce mécanisme permet de sauvegarder n'importe quel objet « serializable » en base de données de façon transparente ;

- « embedded-only=« true » » indique à JPOX que les instances de la classe « JDOCategoryDetail » ne nécessitent pas de table propre pour être enregistrées en base de données.

Description du mapping de « JDOCategory » et « JDOCategoryDetail »

<class name="JDOCategory" identity-type="application">
<field name="id" persistence-modifier="persistent"
primary-key="true" value-strategy="increment"/>
<field name="details" persistence-modifier="persistent" serialized="true">
<map key-type="String" value-type="JDOCategoryDetail"/>
</field>
</class>

<class name="JDOCategoryDetail" embedded-only="true">
<field name="name" persistence-modifier="persistent"/>
<field name="description" persistence-modifier="persistent"/>
</class>

- Persistance des classes « JDOProduct » et « JDOProductDetail ».
Un minimum d'information est placé dans le fichier de description. JPOX produit trois tables « jdoproduct », « jdoproduct_details » et « jdoproductdetail ».

Description du mapping de « JDOProduct » et « JDOProductDetail »

<class name="JDOProduct" identity-type="application">
<field name="id" persistence-modifier="persistent"
primary-key="true" value-strategy="increment"/>
<field name="category" persistence-modifier="persistent"/>
<field name="details">
<map key-type="String" value-type="JDOProductDetail"/>
<join/>
</field>
</class>

<class name="JDOProductDetail" identity-type="datastore">
<field name="name" persistence-modifier="persistent"/>
<field name="description" persistence-modifier="persistent"/>
</class>

- Persistance des classes « JDOItem » et « JDOItemDetail ».
Les noms des tables et des clés primaires sont donnés. JPOX crée les tables ITEM et ITEMDETAIL, et la table de jointure ITEM_ITEMDETAIL.

Description du mapping de « JDOProduct » et « JDOProductDetail »

<class name="JDOItem" table="ITEM" identity-type="application">
<field name="id" persistence-modifier="persistent"
primary-key="true" value-strategy="increment">
<column name="ITEM_ID"/>
</field>
<field name="product" persistence-modifier="persistent"/>
<field name="details" table="ITEM_ITEMDETAIL">
<map key-type="String" value-type="JDOItemDetail"/>
<join><column name="ITEM_ID"/></join>
<key><column name="LOCALE" length="10"/></key>
<value><column name="ITEMDETAIL_ID"/></value>
</field>
</class>

<class name="JDOItemDetail" table="ITEMDETAIL" identity-type="datastore">
<field name="description" persistence-modifier="persistent"/>
<field name="attribute1" persistence-modifier="persistent"/>
<field name="attribute2" persistence-modifier="persistent"/>
<field name="attribute3" persistence-modifier="persistent"/>
<field name="attribute4" persistence-modifier="persistent"/>
<field name="attribute5" persistence-modifier="persistent"/>
<field name="listPrice" persistence-modifier="persistent"/>
<field name="unitCost" persistence-modifier="persistent"/>
<field name="imageLocation" persistence-modifier="persistent"/>
</class>

V-C - RÉALISATION DE LA COUCHE DE PERSISTANCE À L'AIDE D'EJB 3.0

La troisième implémentation sera réalisée à l'aide de composants EJB Entity 3.0, déployés sur un serveur JBoss 4.0.4.

V-C-1 - Conception

La conception de la troisième couche de persistance est semblable aux deux précédentes. Seule l'implémentation des classes de détails, de « EntityCategory », « EntityProduct » et « EntityItem », diffère : contrairement aux deux autres couches de persistance, les relations entre ces classes et leurs classes de détail associées sont de type « one to many ».

Diagramme général de classes

V-C-2 - Implémentation

- un nouveau projet Eclipse, nommé « PetStore EJB », est créé ;

- un nouveau schéma MySQL, nommé « ejb », est créé.

La classe d'accès aux données « EJBCatalogDAO » est un EJB Session stateless. Les trois classes persistantes et leurs classes associées sont des EJB Entity. Ces sept EJB sont implémentés par de simples composants JavaBean annotés.

Le projet comprend :

- le package « dao » contient l'EJB Session stateless d'accès aux données et son interface « Remote » ;

- le package « model » regroupe les EJB Entity ;

- le package « util » contient la classe de conversion des EJB Entity en classes du modèle de données de « PetStore ».

Le package « test » contient :

- une classe permettant d'initialiser la base de données ;

- une seconde classe exécutant une série de tests unitaires.

Le répertoire « resources » contient :

- « application.xml », le fichier de déploiement de l'EAR ;

- « manifest.mf », le manifeste de l'archive « petstore-ejb.jar » ;

- « persistence.xml » contient le nom JNDI de la source de données utilisée et les paramètres de configuration Hibernate ;

- « petstore-ds.xml » contient la définition de la source de données.

V-C-2-a - Fichiers de configuration

Pour déployer des EJB 3.0, il suffit d'archiver dans un fichier JAR :

- les classes compilées de JavaBeans annotés ;

- un fichier de configuration « persistence.xml ».

Ce fichier XML définit :

- les paramètres d'accès à une ou plusieurs bases de données ;

- le comportement par défaut du gestionnaire de persistance.

Plus précisément, il définit une ou plusieurs unités de persistance. Chaque unité de persistance contient les paramètres de connexion à une source de données, le nom de l'implémentation du fournisseur du gestionnaire de persistance et ses paramètres.

Le fichier « persistence.xml » du projet définit un seul contexte de persistance mettant en œuvre le fournisseur Hibernate. Les trois propriétés, propres à Hibernate, ont déjà été présentées dans le chapitre de la présentation de la couche de persistance Hibernate. Le fichier de configuration du projet est le suivant :

Fichier de configuration « persistence.xml »

<?xml version="1.0" encoding="UTF-8"?>
<persistence>
<persistence-unit name="Petstore EJB">
<jta-data-source>java:/PetstoreDS</jta-data-source>
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql">true</property>
</properties>
</persistence-unit>
</persistence>

V-C-2-b - Implémentation de la stratégie d'accès aux données

« EJBCatalogDAO » est la classe d'accès aux données. Elle possède :

- 3 méthodes d'accès direct aux objets de type « Category », « Product » et « Item » ;

- 3 méthodes d'accès à des listes d'objets ;

- 1 méthode de recherche multicritère d'objets de type « Item ».

Les 3 méthodes d'accès direct aux objets sont construites sur le même modèle. L'implémentation de la méthode « getCategory » est la suivante :

Méthode « getCategory »

package fr.ippon.petstore.ejb.dao;
[...]
@Stateless
public class EJBCatalogDAO implements EJBCatalogDAORemote {

[...]

@PersistenceContext
private EntityManager em;

[...]

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Category getCategory(String categoryID, Locale locale)
throws CatalogDAOSysException
{
EntityCategory entityCategory = (EntityCategory) em.find(EntityCategory.class, categoryID);

return Converter.convertEntityCategory(entityCategory, locale);
}

[...]
}

Description des annotations :

- @Stateless : indique au conteneur EJB que ce JavaBean doit être déployé comme un composant EJB Session Stateless.

- @PersistenceContext : indentifie l'attribut « em » dans lequel le conteneur injectera une référence d'un gestionnaire de persistance.

- @TransactionAttribute(TransactionAttributeType.REQUIRED) : indique au conteneur EJB que la méthode doit être exécutée au sein d'une transaction. Si la méthode « getCategory » est appelée alors qu'une transaction est déjà active, elle sera utilisée. Sinon, une nouvelle transaction sera créée.

Description de la méthode :

- l'objet persistant « EntityCategory », dont l'identifiant correspond au paramètre « categoryID », est retrouvé en appelant la méthode « find » de l'objet « em », le « persistence manager ». Cet objet a été injecté dans dans l'instance de « EJBCatalogDAO » par le conteneur J2EE grâce à l'annotation « @PersistenceContext » ;

- l'objet « EntityCategory » est converti en objet du modèle de données « Category » ;

- si aucune exception n'est lancée par la méthode « getCategory », le serveur validera la transaction. Dans le cas contraire, la transaction sera « rollbackée » : toutes les modifications apportées aux ressources seront annulées.

Les trois méthodes de recherche de liste d'objets sont construites sur le même modèle. L'implémentation de la méthode « getItems » est la suivante :

Méthode « getItems »

package fr.ippon.petstore.ejb.dao;

[...]

@Stateless
public class EJBCatalogDAO implements EJBCatalogDAORemote {

[...]

@PersistenceContext
private EntityManager em;

[...]

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Page getItems(String productID, int start, int count, Locale locale)
throws CatalogDAOSysException
{
List<EntityItem> list = (List<EntityItem>)
em.createQuery("select i from EntityItem i where i.product.id = :productID")
.setParameter("productID", productID)
.setFirstResult(start)
.setMaxResults(count+1)
.getResultList();

return new Page(Converter.convertEntityItemList(list, locale),
start,
(list.size() > count));
}
[...]
}

Description de la méthode :

- si l'appelant est associé à une transaction, le conteneur exécute la méthode « getItems » dans le même contexte transactionnel. Sinon, une nouvelle transaction est créée ;

- recherche des objets persistants « EntityItem », dont l'identifiant du produit associé correspond au paramètre « productID ». Le premier élément de la liste sera le « start »ième du « result set » et la liste aura une taille de « count+1 » objets au plus ;

- la liste des objets retournés est convertie en une liste d'objets de type « Item » ;

- la transaction est « commitée » si aucune exception n'est lancée par la méthode « getItems ».

La dernière méthode, à écrire, met en œuvre une requête JPQL complexe. « searchItems() » recherche des objets de type « Item » possédant l'une des caractéristiques suivantes :

- la description de l'élément contient l'un des mots passés en paramètre ;

- le nom du produit, de l'élément, contient l'un des mots passés en paramètre.

Prototypage de la méthode :

Page searchItems(String query, int start, int count, Locale l)
throws CatalogDAOSysException

Paramètres :

- query : liste de mots à rechercher ;

- start : identifiant du premier produit à retourner ;

- count : nombre de produits à retourner ;

- l : langue des libellés.

Les mots à rechercher sont passés en paramètre dans une chaîne de caractères et sont séparés par des espaces.

Chacun d'eux sera converti en minuscule, puis placé dans une variable « token ».

Écriture de la requête JPQL :

- la requête doit retourner des objets de type « EntityItem » :

" select distinct i from EntityItem i, in (i.details) id, in (i.product .details) pd where ... "

- les objets « EntityItem », dont la description contient l'un des mots passés en paramètre, doivent être retournés :

" lower(id.description) like '% " +  token + " %' and id.locale=' " +  localeStr + " ' "

- les objets « EntityItem », dont le nom du produit contient l'un des mots passés en paramètre, doivent être retournés :

" lower(pd.description) like '% " +  token + " %' and pd.locale=' " +  localeStr + " ' "

Le code source Java générant la requête JPQL est le suivant :

String queryStr = "select distinct i from EntityItem i";
if ((query != null) && (query.trim().length() > 0))
{
String localeStr = locale.toString();

queryStr += ", in (i.details) id, in (i.product.details) pd where ";

StringTokenizer st = new StringTokenizer(query);
if (st.hasMoreTokens())
{
String token = st.nextToken().toLowerCase();

queryStr +=
"(lower(id.description) like '%" + token + "%' and id.locale='" + localeStr + "') or " +
"(lower(pd.name) like '%" + token + "%' and pd.locale='" + localeStr + "')";

while (st.hasMoreTokens())
{
token = st.nextToken().toLowerCase();
queryStr += " or ";
queryStr +=
"(lower(id.description) like '%" + token + "%' and id.locale='" + localeStr + "') or " +
"(lower(pd.name) like '%" + token + "%' and pd.locale='" + localeStr + "')";
}
}
}

La requête produite, pour les paramètres « Query » = « Cats Male White » et « l » = Local.UK, est la suivante :

select distinct i from EntityItem i , in (i.details) id, in (i.product.details) pd where
(lower(id.description) like '%Cats%' and id.locale='en_GB') or
(lower(pd.name) like '%Cats%' and pd.locale='en_GB') or
(lower(id.description) like '%Male%' and id.locale='en_GB') or
(lower(pd.name) like '%Male%' and pd.locale='en_GB') or
(lower(id.description) like '%White%' and id.locale='en_GB') or
(lower(pd.name) like '%White%' and pd.locale='en_GB')

Une fois générée, la requête est exécutée à l'aide de la séquence d'instructions déjà vue précédemment :

List<EntityItem> list = (List<EntityItem>)
em.createQuery(queryStr)
.setFirstResult(start)
.setMaxResults(count+1)
.getResultList();

V-C-2-c - Implémentation de la classe persistante « EntityItem »

Les classes « EntityCategory », « EntityProduct » et « EntityItem » sont construites sur le même modèle. Nous allons examiner l'implémentation de la classe « Entitytem » dans ce paragraphe.

La classe « EntityItem » est un simple composant JavaBean annoté, possédant un constructeur par défaut et des accesseurs pour chacun de ses attributs.

EntityItem.java

package fr.ippon.petstore.ejb.model;
[...]
@Entity
@Table(name = "ITEM")
public class EntityItem
{
@PersistenceContext
private EntityManager em;

private int id;
private EntityProduct product;
private Map<String, EntityItemDetail> details;

public EntityItem()
{
this.id = 0;
this.details = new HashMap<String, EntityItemDetail>();
}

@Id
@GeneratedValue
@Column(name="ID")
public int getId() { return id; }
public void setId(int id) { this.id = id; }

@OneToOne
public EntityProduct getProduct() { return product; }
public void setProduct(EntityProduct product) { this.product = product; }

@OneToMany(cascade=CascadeType.ALL, mappedBy="item")
@MapKey(name="locale")
public Map<String, EntityItemDetail> getDetails() { return details; }
public void setDetails(Map<String, EntityItemDetail> details) { this.details = details; }

public EntityItemDetail getDetail(Locale l)
{
if (l == null)
l = Locale.getDefault();

return this.details.get(l.toString());
}

public void setDetail(Locale l, EntityItemDetail detail)
{
if (l == null)
l = Locale.getDefault();

String sLocale = l.toString();
detail.setLocale(sLocale);
getDetails().put(sLocale, detail);
}
}

Description des annotations :

- @Entity : indique au conteneur EJB que ce JavaBean doit être déployé comme un composant EJB Entity ;

- @Table : indique le nom de la table dans laquelle seront sauvegardées les valeurs des attributs des instances de la classe « EntityItem » ;

- @PersistenceContext : identifie l'attribut « em » dans lequel le conteneur injectera une référence d'un gestionnaire de persistance ;

- @Id : identifie l'attribut qui sera mappé sur la clé primaire ;

- @GeneratedValue : délègue au framework la génération et l'affectation d'une clé primaire lors du premier enregistrement d'un objet en base. La génération de la clé se fait généralement par le biais d'une séquence en base, d'un attribut entier autoincrémenté, ou encore par la production d'un GUID ;

- @Column : donne le nom de la colonne dans laquelle sera stockée la valeur de l'attribut annoté ;

- @OneToOne : identifie l'attribut « product » comme étant une référence vers un EJB Entity « EntityProduct » et permet au conteneur de gérer cette association ;

- @OneToMany : exprime une partie de l'association entre « EntityItem » et « EntityItemDetail » qui est de type 1-N.

La classe de détail, associée à « EntityItem », est un simple composant JavaBean annoté. Elle ne sera pas présentée.

De même, le rôle de la classe « Converter » est le même que celui des classes de conversion des deux projets précédents. Elle ne sera pas présentée non plus.

V-C-3 - Création du schéma de base de données

Le fichier de configuration « persistence.xml » définit la propriété « hibernate.hbm2ddl.auto » à la valeur « update ». A chaque utilisation du gestionnaire de persistance, le schéma de la base de données est mis à jour. Le schéma suivant est créé :

Schéma de la base de données produit par le gestionnaire de persistance Hibernate

VI - CONCLUSION

VI-A - RÉCAPITULATIF

Ce document a présenté l'implémentation de la couche de persistance d'une application simple à l'aide du projet Hibernate et de produits implémentant les dernières spécifications des standards JDO EJB 3.0. Son premier objectif était de montrer la réalisation concrète de la couche de persistance de l'application de démonstration « PetStore ». Son second objectif était de permettre une première réflexion sur ces trois technologies.

Nombre de caractéristiques n'ont pas été abordées dans ce document, mais le but n'était pas de rivaliser avec les 408 pages de « Hibernate in action » de Manning ou les 380 pages de « Java Data Objects » d'Addison-Wesley.

VI-B - COMMENT CHOISIR UNE SOLUTION DE PERSISTANCE ?

Comme l'ont montré les projets présentés, les trois solutions de persistance offrent les mêmes fonctionnalités de base (mapping d'objets et de liste, langage de requêtage puissant, déconnexion et reconnexion des objets au mécanisme de persistance…). Cependant, ces solutions ne peuvent pas se substituer les unes aux autres. En effet, les contraintes de votre projet éliminent naturellement certaines solutions :

- Développez-vous un projet Open Source ?
Hibernate ou JPOX.

- Votre projet exploite-t-il des transactions ? Sont-elles distribuées ?
EJB 3.0 ou, dans un second temps, Hibernate ou JDO via un EJB Session.

- Développez-vous un client lourd ?
Hibernate, JDO (JPOX, Kodo JDO), TopLink.

- Votre modèle de données est-il complexe ?
Hibernate ou JDO.

- Java 5 est non disponible sur les serveurs de production ?
Hibernate.

- Avez-vous des problèmes de performance sur de gros volumes ?
Kodo JDO, Hibernate+SwarmCache ou Hibernate+JBoss TreeCache.

Autres points à étudier pour sélectionner une solution de persistance :

- Est-il possible d'exécuter des requêtes en mémoire ?

- Est-il possible d'utiliser des fonctions SQL ou Java dans le langage de requêtage ?

- Est-il possible de définir le périmètre du graphe des objets détachés ?

- Est-il possible de charger un graphe d'objets détachés dans une JVM ne disposant pas des classes du framework de persistance ?

- Est-il possible d'utiliser un verrouillage pessimiste (SELECT FOR UPDATE) au niveau des requêtes ?

- Les « statement JDBC » sont-ils réutilisés par le framework de persistance ?

- Possibilité de définir le type de « resultset » (forward-only, scroll-insensitive, ou scroll-sensitive) pour une requête ?

- Est-il possible de savoir si un objet se trouve en cache ?

- Les mises à jour en masse sont-elles possibles ? Si oui, les objets cachés sont-ils resynchronisés avec le contenu de la base de données ?

Après avoir éliminé les solutions incompatibles avec les contraintes de votre projet, la démarche consiste à construire une application de test pour chaque solution retenue. Les applications de test doivent permettre de :

- déceler les problèmes de performance (temps de réponse, gestion des volumes cibles) ;

- vérifier la qualité du mapping (nombre de requêtes SQL générées) ;

- valider les drivers JDBC sélectionnés (XA ou non) ;

- valider le déploiement de solutions de persistance sur un cluster de serveurs.

VI-C - OÙ EN EST-ON SUR LE TERRAIN ?

- Java 1.5
L'utilisation de Java 1.5 est une condition sine qua non pour pouvoir travailler avec JDO 2.0, EJB 3.0, et Hibernate Annotations. Or, deux ans après sa sortie, « la neuvième mise à jour est utilisable en production malgré la présence de bogues critiques provoquant des crashs » dixit Jack Shirazi sur www.javaperformancetuning.com en octobre 2006. De ce fait, les projets informatiques utilisant ces technologies sont encore rares.

- Xcalia Intermediation Core (ex LiDO JDO)
XIC, ancienne référence sur le marché français, n'implémente qu'un sous-ensemble des spécifications JDO 2.0. De plus, les performances sont en retrait par rapport aux autres solutions de persistance.

- Java Persistence API
JPA est une API standard de persistance sur laquelle s'appuient les EJB 3.0. JPA fait partie de la spécification JSR 220. Pourquoi Sun Microsystems présente-t-elle une nouvelle API de persistance ? Pour simplifier le développement de la persistance des objets Java sur Java EE et Java SE, et rassembler les meilleures caractéristiques des différents frameworks de persistance.
Un autre intérêt de cette API est d'unifier les méthodes d'accès à la couche de persistance, ce qui permet de ne plus dépendre d'un éditeur particulier.

- simplifier : JPA permet de persister de simple POJO annoté ou accompagné d'un fichier de description XML ;

- rassembler les meilleurs idées : JPA n'est pas basé sur un framework particulier, mais incorpore et améliore les meilleures caractéristiques des frameworks de persistance les plus populaires. Les différents éditeurs proposent déjà des interfaces JPA pour leurs produits (Hibernate Annotations + Hibernate EntityManager pour Hibernate, OpenJPA pour Kodo JDO, TopLink Essentials pour TopLink…).

VII - Remerciements

Cet article a été mis au gabarit de developpez.com. Voici le lien vers le PDF d'origine : article.pdf.

Nous tenons à remercier Philippe DUVAL pour sa relecture orthographique attentive de cet article et Régis POUILLER pour la mise au gabarit.