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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
<project
name
=
"iBatis"
default
=
"genfiles"
basedir
=
"."
>
<!-- Library versions and JARs -->
<property
file
=
"../services/build.properties"
/>
<property
file
=
"${global.lib.dir}/lib.properties"
/>
<!-- génération des fichiers de mapping ibatis avec abator-->
<target
name
=
"genfiles"
description
=
"Generate the files"
depends
=
"clean"
>
<taskdef
name
=
"abator.genfiles"
classname
=
"org.apache.ibatis.abator.ant.AbatorAntTask"
classpath
=
"${abator.jar}"
/>
<abator.genfiles
overwrite
=
"true"
configfile
=
"abatorAffaireConfig.xml"
/>
</target>
<target
name
=
"clean"
>
<delete
includeEmptyDirs
=
"true"
>
<fileset
dir
=
"src/fr/ippon/dao/tools/maps/"
includes
=
"**/*"
/>
<fileset
dir
=
"src/fr/ippon/dao/vo/"
includes
=
"**/*"
/>
</delete>
</target>
</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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE abatorConfiguration PUBLIC
"-//Apache Software Foundation//DTD Abator for iBATIS Configuration
1.0//EN"
"http://ibatis.apache.org/dtd/abator-config_1_0.dtd"
>
<abatorConfiguration>
<abatorContext>
<!-- TODO: Add Database Connection Information -->
<jdbcConnection
driverClass
=
"oracle.jdbc.OracleDriver"
connectionURL
=
"jdbc:oracle:thin:@192.196.121.78:1525:IPPONDEV"
userId
=
"*******"
password
=
"*******"
>
<classPathEntry
location
=
"lib/oracle/ojdbc14.jar"
/>
</jdbcConnection>
<javaModelGenerator
targetPackage
=
"fr.ippon.dao.vo"
targetProject
=
"dao/src"
type
=
"fr.ippon.abator.IpponJavaModelGenerator"
>
<property
name
=
"enableSubPackages"
value
=
"true"
/>
</javaModelGenerator>
<sqlMapGenerator
targetPackage
=
"fr.ippon.dao.tools.maps"
targetProject
=
"dao/src"
>
<property
name
=
"enableSubPackages"
value
=
"true"
/>
</sqlMapGenerator>
<table
schema
=
"AFFAIRE"
tableName
=
"AFFAIRE"
/>
</abatorContext>
</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.