Tutoriel sur la solution de persistance Java iBatis

Ippon Technologies

- Titre : Tutoriel sur la solution de persistance Java iBatis

- Auteur : Ippon Technologies

- Parution : 4 février 2008

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. 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. Introduction

- I-A. Présentation d'iBatis

- I-B. Abator

- II. Fonctionnement d'iBatis

- III. Exemple d'utilisation

- III-A. Bibliothèques nécessaires

- III-B. Utilisation d'Abator : génération de code pour iBATIS

- III-C. Appel de la requête

- IV. Conclusion

- V. Sources

- VI. Liens

- VII. Remerciements

I - Introduction

Ce document est une présentation technique générale de « iBatis », outil de persistance, au même titre qu'Hibernate.

I-A - Présentation d'iBatis

iBATIS est un projet opensource de la communauté Apache. Il s'agit d'un framework de persistance qui permet de faire un mapping entre des requêtes SQL et des JavaBeans.

iBATIS utilise un fichier de description XML pour mapper les objets à des procédures stockées SQL. Ceci permet ainsi de dissocier les requêtes SQL des DAO.

À la différence d'Hibernate, l'utilisateur d'iBATIS reste maître du SQL généré : il est ainsi possible de tuner exactement les requêtes et de vérifier exactement les requêtes exécutées sur la base.

D'autre part, la prise en main de ce framework est rapide.

I-B - Abator

Abator est un outil de génération de code et de fichiers de mapping pour iBatis. Abator simplifie encore plus l'utilisation d'iBATIS.

Disponible en standalone et sous forme de plug-in Eclipse, Abator prend en entrée un fichier XML qui décrit la base (connexion, tables et autres…) et génère les Beans correspondant aux tables et les fichiers de configuration propres à iBATIS.

II - Fonctionnement d'iBatis

Ce chapitre présente les étapes par lesquelles iBATIS passe pour stocker un objet en base.

- Etape 1. Un objet métier ou de type natif (int, string…) est fourni en paramètre à iBATIS.

- Etape 2. iBATIS cherche la définition du composant métier dans la balise alias de son fichier de configuration.

- Etape 3. iBATIS mappe alors dynamiquement les données de l'objet dans la requête SQL et l'exécute.

- Etape 4. Dans le cas d'un update, le nombre de lignes affectées est renvoyé. Dans le cas d'un select, l'objet paramétré comme objet de résultat est renvoyé.

L'objet métier passé en paramètre (ou objet à renvoyer en résultat) doit comporter les accesseurs correspondant aux données demandées dans la requête SQL, sinon iBATIS renvoie une erreur.

La requête est définie de manière identique dans le fichier de configuration XML, que le résultat soit un objet unique ou une liste.

III - Exemple d'utilisation

III-A - Bibliothèques nécessaires

Les bibliothèques suivantes doivent être présentes dans le classpath de l'application :

- ibatis-common-2.jar ;

- ibatis-dao-2.jar ;

- ibatis-sqlmap-2.jar ;

- le driver de la base de données : dans le cas d'Oracle, ojdbc14.jar.

La bibliothèque Abator, ippon-abator0.6.0.jar est seulement utile pour générer les fichiers de configuration et les Beans, mais non pour le contexte d'exécution.

III-B - Utilisation d'Abator : génération de code pour iBATIS

L'outil Abator de génération de code sera lancé à partir d'une tâche Ant « AbatorAntTask », dont voici un exemple de fichier build.xml :

1. <project name="iBatis" default="genfiles" basedir=".">

2. <!-- Library versions and JARs -->

3. <property file="../services/build.properties"/>

4. <property file="${global.lib.dir}/lib.properties"/>

6. <!-- génération des fichiers de mapping ibatis avec abator-->

7. <target name="genfiles" description="Generate the files" depends="clean">

8. <taskdef name="abator.genfiles"

9. classname="org.apache.ibatis.abator.ant.AbatorAntTask"

10. classpath="${abator.jar}" />

11. <abator.genfiles overwrite="true" configfile="abatorAffaireConfig.xml" />

12. </target>

13. <target name="clean">

14. <delete includeEmptyDirs="true">

15. <fileset dir="src/fr/ippon/dao/tools/maps/" includes="**/*"/>

16. <fileset dir="src/fr/ippon/dao/vo/" includes="**/*"/>

17. </delete>

18. </target>

19. </project>

L'exécution de la tâche genfiles va chercher les informations dans le fichier de configuration d'Abator abatorAffaireConfig.xml qui spécifie :

- les paramètres de connexion à la base et le driver utilisé ;

- la table qui sera mappée ;

- le répertoire où seront générés les Beans dao/vo ;

- le répertoire où seront générés les fichiers de mapping iBatis.

Il est possible de lister plusieurs fichiers de configuration Abator à la liste des genfiles et ainsi générer l'ensemble des Beans correspondant à une base entière. Voici un exemple de fichier de configuration :

abatorAffaireConfig.xml

1. <?xml version="1.0" encoding="UTF-8"?>

2. <!DOCTYPE abatorConfiguration PUBLIC "-//Apache Software Foundation//DTD Abator for iBATIS Configuration

3. 1.0//EN" "http://ibatis.apache.org/dtd/abator-config_1_0.dtd">

4. <abatorConfiguration>

5. <abatorContext>

6. <!-- TODO: Add Database Connection Information -->

7. <jdbcConnection driverClass="oracle.jdbc.OracleDriver"

8. connectionURL="jdbc:oracle:thin:@192.196.121.78:1525:IPPONDEV"

9. userId="*******"

10. password="*******">

11. <classPathEntry location="lib/oracle/ojdbc14.jar" />

12. </jdbcConnection>

13. <javaModelGenerator targetPackage="fr.ippon.dao.vo" targetProject="dao/src"

14. type="fr.ippon.abator.IpponJavaModelGenerator">

15. <property name="enableSubPackages" value="true" />

16. </javaModelGenerator>

17. <sqlMapGenerator targetPackage="fr.ippon.dao.tools.maps" targetProject="dao/src">

18. <property name="enableSubPackages" value="true" />

19. </sqlMapGenerator>

20. <table schema="AFFAIRE" tableName="AFFAIRE"/>

21. </abatorContext>

22. </abatorConfiguration>

L'exécution de la tâche Ant pour la table AFFAIRE de la base AFFAIRE génère dans :

- dao/vo/affaire, l'ensemble des Beans, dans notre cas : Affaire.java. Cette classe décrit à l'identique la structure de la table Affaire.

- dao/tools/maps/affaire, l'ensemble des fichiers de configuration iBATIS, dans notre cas : AFFAIRE_AFFAIRE_SqlMap.xml. Ce fichier est généré une fois, il sera ensuite personnalisé suivant l'ensemble des requêtes dont le DAO a besoin. Donc il faut faire attention à ne pas le re-générer par la suite, sauf en cas de modification de structure de table.

Voici un exemple simplifié de ce fichier de configuration, avec une requête SQL recherchant une Affaire suivant l'id passé en paramètre.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-
2.dtd">
<sqlMap namespace="AFFAIRE_AFFAIRE">
<resultMap id="abatorgenerated_AffaireResult" class="fr.xxx.b2b.dao.vo.affaire.Affaire">
<!--
WARNING - This element is automatically generated by Abator for iBATIS, do not modify.
This element was generated on Fri Jun 13 11:09:42 CET 2006.
-->
<result column="AFF_ID" property="affId" jdbcType="DECIMAL"/>
<result column="AFF_STATUT" property="affStatut" jdbcType="DECIMAL"/>
<result column="SCL_ID" property="sclId" jdbcType="DECIMAL"/>
<result column="PFA_ID" property="pfaId" jdbcType="DECIMAL"/>
<result column="PRS_ID" property="prsId" jdbcType="DECIMAL"/>
<result column="POP_ID" property="popId" jdbcType="DECIMAL"/>
<result column="WKF_ID" property="wkfId" jdbcType="DECIMAL"/>
<result column="ACM_ID" property="acmId" jdbcType="DECIMAL"/>
<result column="UTI_ID" property="utiId" jdbcType="DECIMAL"/>
<result column="ETA_ID" property="etaId" jdbcType="DECIMAL"/>
<result column="COM_ID" property="comId" jdbcType="DECIMAL"/>
<result column="AFF_K_ENTITE" property="affKEntite" jdbcType="DECIMAL"/>
<result column="AFF_K_GROUPE" property="affKGroupe" jdbcType="VARCHAR"/>
<result column="AFF_K_CP" property="affKCp" jdbcType="DECIMAL"/>
<result column="AFF_B_MVT" property="affBMvt" jdbcType="CHAR"/>
<result column="AFF_D_CREATION" property="affDCreation" jdbcType="DATETIME"/>
<result column="AFF_D_ENVOI" property="affDEnvoi" jdbcType="DATETIME"/>
<result column="AFF_D_ENTREE" property="affDEntree" jdbcType="DATETIME"/>
<result column="AFF_D_EFFET" property="affDEffet" jdbcType="DATETIME"/>
<result column="AFF_D_SORTIE" property="affDSortie" jdbcType="DATETIME"/>
<result column="AFF_D_ARCHIVAGE" property="affDArchivage" jdbcType="DATETIME"/>
<result column="AFF_K_REF" property="affKRef" jdbcType="VARCHAR"/>
<result column="AFF_T_DISCO" property="affTDisco" jdbcType="VARCHAR"/>
<result column="AFF_K_PREV_FRN" property="affKPrevFrn" jdbcType="VARCHAR"/>
<result column="AFF_K_REF_FRN" property="affKRefFrn" jdbcType="VARCHAR"/>
<result column="AFF_T_CLIENT_RS" property="affTClientRs" jdbcType="VARCHAR"/>
<result column="AFF_T_CLIENT_SIRET" property="affTClientSiret" jdbcType="VARCHAR"/>
<result column="AFF_T_COMMUNE" property="affTCommune" jdbcType="VARCHAR"/>
<result column="AFF_K_BPM_AFF_ID" property="affKBpmAffId" jdbcType="VARCHAR"/>
<result column="AFF_K_BPM_WKF_ID" property="affKBpmWkfId" jdbcType="VARCHAR"/>
<result column="PDM_ID" property="pdmId" jdbcType="VARCHAR"/>
</resultMap>

<select id="getAllAffaireById" resultMap="abatorgenerated_AffaireResult" parameterClass="java.lang.Long">
SELECT aff.AFF_STATUT , aff.AFF_ID , aff.SCL_ID , aff.PFA_ID , aff.PRS_ID,
aff.POP_ID , aff.WKF_ID , aff.ACM_ID , aff.UTI_ID , aff.ETA_ID ,
aff.COM_ID , aff.AFF_K_ENTITE , aff.AFF_K_GROUPE , aff.AFF_K_CP,
aff.AFF_B_MVT, aff.AFF_D_CREATION, aff.AFF_D_ENVOI , aff.AFF_D_ENTREE,
aff.AFF_D_EFFET , aff.AFF_D_SORTIE , aff.AFF_D_ARCHIVAGE ,
aff.AFF_K_REF , aff.AFF_T_DISCO , aff.AFF_K_PREV_FRN, aff.AFF_K_REF_FRN,
aff.AFF_T_CLIENT_RS, aff.AFF_T_CLIENT_SIRET , aff.AFF_T_COMMUNE ,
aff.AFF_K_BPM_AFF_ID , aff.AFF_K_BPM_WKF_ID , aff.PDM_ID from affaire aff
WHERE aff.aff_id = #affId#
</select>
</sqlMap>

getAllAffaireById correspond à l'identifiant unique de la requête afin de pouvoir appeler celle-ci dans le DAO. C'est dans ce fichier que sera listé l'ensemble des requêtes liées à la table Affaire. (insert, update, select, delete…)

Ces requêtes peuvent être dynamiques, par exemple, en ne sélectionnant une condition sur un champ que si la valeur passée est différente de null. Exemple de balise principale :

- dynamic prepend permet de rendre dynamique le langage la requête ;

- isNotNull suite de la requête affichée uniquement si le paramètre existe ;

- isPresent suite de la requête affichée uniquement si le paramètre existe ;

- isNotPresent suite de la requête affichée uniquement si le paramètre n'est pas renseigné ;

Des exemples de scripts dynamiques sont fournis dans le fichier XML lors de la génération Abator.

III-C - Appel de la requête

Pour utiliser les fichiers iBATIS générés et établir une connexion, il faut :

- les fichiers sqlMapConfigAffaire.xml et database.properties ;

- SQLMapUtils.java;

- un DAO.

sqlMapConfigAffaire.xml est le fichier de configuration iBATIS qui permet de faire le lien avec le fichier AFFAIRE_AFFAIRE_SqlMap.xml généré dans l'étape précédente. En voici le contenu :

<sqlMapConfig>
<properties resource="fr/ippon/dao/tools/database.properties"/>
<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="${driver}" />
<property name="JDBC.ConnectionURL" value="${url}" />
<property name="JDBC.Username" value="affaire" />
<property name="JDBC.Password" value="affaire" />
</dataSource>
</transactionManager>
<sqlMap resource="fr/ippon/dao/tools/maps/affaire/AFFAIRE_AFFAIRE_SqlMap.xml" />
</sqlMapConfig>

Les paramètres ${driver} et ${url} de connexion à la base de données sont centralisés dans le fichier database.properties. Ceci est utile car il peut y avoir plusieurs fichiers sqlMapConfigXXX.xml : autant que de bases auxquelles on veut se connecter.

SQLMapUtils.java est une classe statique permettant de récupérer à partir de sqlMapConfigXXX.xml un objet de type SqlMapClient. Cet objet facilite la récupération et l'exécution d'une requête suivant son identifiant (getAllAffaireById dans notre cas).

Le DAO est la classe d'accès aux données qui, grâce à iBATIS devient très simple, et ne va comporter aucune requête SQL en dur dans le code source Java, l'objet SqlMapClient s'en chargeant.

public class AffaireDAO {
/**
* @param idAffaire
* @return vo Affaire
* @throws SQLException, IOException
*/
public Affaire getAffaireById (Long idAffaire) throws Exception {
SqlMapClient sqlMap = SqlMapUtils.getSqlMapClientAffaire();
Affaire vAffaire = null;
vAffaire = (Affaire) sqlMap.queryForObject("getAllAffaireById", idAffaire);
return vAffaire;
}
}

queryForObject permet de récupérer un unique objet IBatis.

Pour une liste d'objets, il suffira d'utiliser la méthode queryForList.

IV - Conclusion

Le fait de pouvoir externaliser les requêtes SQL du code source Java, de pouvoir aisément mapper les objets aux tables, et du fait de la simplicité de sa mise en place, IBatis est un framework opensource qui peut être un choix relativement judicieux pour une entreprise par rapport à Hibernate, surtout si cette dernière possède une base qui ne comporte pas énormément de relations entre les tables.

De plus, le temps de développement est largement accéléré à l'aide de l'outil Abator.

V - Sources

A ce document est couplé à un exemple d'application Web utilisant le framework iBatis. Les sources sont mises à disposition dans le zip dans lequel dist/ibatis.war est déjà compilé et déployable.

La couche présentation de cet exemple est volontairement simple (à base de jsp/servlet). Afin de mettre en exergue uniquement la couche d'accès aux données iBatis.

Seul log4j est mis en place afin de pouvoir logger l'ensemble des requêtes SQL qui sont exécutées sur le serveur.

Cette application permet de lister, afficher, et mettre à jour (CRUD) une liste d'affaires. Elle est couplée avec une base MySQL et un serveur d'application Tomcat.

L'installation de l'application a été facilitée grâce à l'utilisation de scripts Ant permettant de créer la base de données, compiler et déployer le war sur le serveur d'application.

Pour l'installation, voir le fichier README.txt dans le zip décrivant la procédure.

Ce projet a pour but de présenter le framework iBatis
Il utilise l'outil de génération de code Abator pour générer
les pojos et fichiers de configuration iBatis

INSTALL :
- nécessite une jdk 1.4.2
- nécessite un serveur d'application tomcat 5.0.28
- nécessite une base mysql 4.1.9

Modifier les valeurs des connexions dans :
- build.properties
- config/abatorAffaireConfig.xml
- drc/fr.ippon.dao.tools/database.properties

La première étape consiste à créer les tables à l'aide de la tâche
=> ant setup-db (après avoir créé la base Affaire)

Les tests peuvent s'effectuer grâce aux sources dans test/
mais aussi en déployant le projet à l'aide de la tâche
=> ant deploywar

puis aller sur http://localhost:8080/iBatis

Impression écran de l'application exemple :

Dans l'exemple, P6Spy a été mis en place par défaut afin de pouvoir logger précisément les requêtes exécutées sur le serveur. Sinon, il est aussi possible d'activer les logs SQL à partir de log4j en mettant le niveau de log par défaut à DEBUG. (log4j.logger.java.sql = DEBUG)

VI - Liens

- iBatis : http://ibatis.apache.org/

- P6spy : http://www.p6spy.com

- Guide iBatis : http://ibatisnet.sourceforge.net/DevGuide.html

VII - Remerciements

Cet article a été publié avec l'aimable autorisation de la société Ippon. Voici le lien vers le PDF d'origine : ibatis.pdf.

Nous tenons à remercier phanloga pour sa relecture orthographique attentive de cet article et Mickael Baron pour la mise au gabarit.