Vous êtes sur la page 1sur 9

Sami Jaber

Valtech Training
Implmenter une couche de persistance
avec .Net
fvrier 06
Rsum

1
Dvelopper une couche de persistance est assurment une des tches les plus difficiles lors de la
ralisation d'une application de gestion. Alors que les techniques de mapping objet / relationnel sont
restes pendant plus de dix ans l'apanage d'experts, de plus en plus de projets franchissent le pas.
Avec cet engouement, ces mmes projets payent souvent les pots casss d'une mauvaise
conception ou d'une mconnaissance des principes du mapping, dont la persistance est tout sauf
transparente (malgr ce qu'affirment les prsentations marketing).

Quels sont les piges viter lorsqu'on doit faire du mapping O/R ? Y-a-t-il des modles de
conception suivre ? La gnration de code peut-elle apporter une solution alternative moins
coteuse et mieux matrise ? Faut-il rserver cette technique des petits projets ou des gros
projets ?

Cette session s'attachera dcrire les grands principes du mapping objet / relationnel tout en
dcrivant au travers d'une tude de cas pratique les effets pervers d'une mauvaise conception. Les
outils de persistance utiliss seront ceux du march, quil soient propritaires ou Open Source
(nhibernate, Code-Smith, ). Quant lapproche "techniquement agnostique", elle conviendra
aussi bien aux aficionados de .NET que de Java EE.

2
Table des matires

1. LE MAPPING O/R, POUR QUOI FAIRE ?_________________________________________3
2. QUELLE DMARCHE ? ____________________________________________________3
3. LES OUTILS ET STANDARDS_________________________________________________4
3.1 J2EE........................................................................................................................................ 4
3.2 .NET........................................................................................................................................ 4
3.2.1 Nhibernate...................................................................................................................................... 5
4. LES DIFFRENTES APPROCHES DU MAPPING ____________________________________5
4.1 La persistance manuelle (ADO.NET, DataSet, ) ................................................................. 5
4.2 La gnration de code (CodeSmith, NetTiers, etc ) ............................................................ 5
4.2.1 La persistance automatique........................................................................................................... 6
5. OPTIMISER SA COUCHE DE PERSISTANCE_______________________________________7
5.1 Quelles sont les solutions ? .................................................................................................... 8
6. CONCLUSION ___________________________________________________________9


3
1. Le mapping O/R, pour quoi faire ?
Nombre dapplications oriente objet comprennent un accs une quelconque base relationnelle.
Jusqu prsent, pour peu que le dveloppeur ait pris soin dadopter une conception purement
objet, une phase de conversion savre indispensable. Cette phase de conversion encore appele
mapping objet/relationnel consiste adapter le modle relationnel un modle objet, navigationnel
et hirarchique.
Alors quun schma purement relationnel reprsente les liens via des mcanismes de cls
trangres, un modle objet sappuie sur des dpendances fortement types (Collections, Classes,
..). Les caractristiques intrinsques du dveloppement objets tels que lhritage ou le
polymorphisme saccommodent difficilement dune vue relationnelle.

Ce problme a conduit au fil des ans les dveloppeurs effectuer dincessantes pirouettes
techniques pour adapter ces deux modles par lutilisation de composants techniques spcifiques
(Pattern DAO pour Data Access Objects).
Si pour certains, la solution passe par lacquisition dune base de donnes nativement objet, pour
dautres au contraire, le problme est dordre applicatif. Les bases de donnes relationnelles ayant
en effet prouv leur maturit et leur capacit monter en charge.
2. Quelle dmarche ?
Que ce soit dans le monde J2EE ou .NET, il existe de nombreux motifs de conception ou design
pattern permettant dimplmenter et doptimiser manuellement (c'est--dire sans outil du march)
une couche daccs aux donnes. Dans cette optique, une architecture multi-couches savre
souvent bnfique car elle tend relguer les phases de conversion dans cette couche spcifique
appele encore DAL (Data Access Layer) ou couche de persistance. Prendre en charge
manuellement le mapping O/R ncessite de la rigueur de la part du concepteur. Car si cette
dmarche a lavantage de tirer profit des comptences relationnelles existantes (SQL, procdures
stockes, batch), elle devient vite fastidieuse sans un outillage minimum. En .NET, cela consiste
faire appel aux API ADO.NET afin dextraire les donnes via des requtes SQL pour les restituer
sous la forme de graphes dobjets.

Plus le schma relationnel est riche et complexe, plus cette tche sera fastidieuse et rptitive.
Sans compter les nombreux cas spcifiques tels que le chargement la demande (Lazy Loading),
la gestion du cache objet ou la prise en compte de lhritage. Quelle soit automatique ou manuelle,
la persistance requiert la mise en place dun modle dobjets mtier ou modle du domaine. Ce
modle contient gnralement lensemble des classes mtier dune application indpendamment de
leur structure de stockage (fichiers, sgbd, etc). Il constitue le pendant des tables relationnelles
dans le monde objet.

Nombreux sont les outils proposer dans une approche bottom-up des gnrateurs de code
partir de schmas relationnels. A linverse, une fonctionnalit incontournable dans le monde du
mapping est la gnration de schma et de scripts SQL partir dun modle objet (approche top-
down).



4
3. Les outils et standards
Depuis quelques annes, le monde JEE croule sous les standards en tous genres, une situation
diffrente ct .NET. Voyons ce que proposent les deux clans.

3.1 J2EE
La premire spcification visant le mapping O/R dans le monde JEE ft linitiative JDO (Java Data
Object). Spcification adopte massivement dans un premier temps par la communaut Java puis
dlaisse par les acteurs majeurs de lindustrie (IBM, Oracle, BEA) au profit de la norme EJB 3.
Pour ses dtracteurs, JDO serait trop structurant et pas assez riche dun point de vue technique. En
ralit, se cache derrire une bataille technologique et des enjeux financiers considrables.
Contrairement aux EJB, JDO na pas spcialement vocation sinsrer dans un serveur
dapplication, seul loutil de persistance suffit prendre en charge les diffrents services techniques.
Loffre doutils JDO tant dj assez abondante et la plupart des diteurs prcdents ne fournissant
aucun outil de ce type, le march des serveurs dapplication aurait subit le contrecoup dune
concurrence redoutable. Pour IBM Cette spcification se chevauche avec dautres JSR en cours
de dveloppement (ndrl EJB 3). Dans un contexte de rationalisation et de simplification des outils
JEE, il nest pas souhaitable de voir se multiplier plusieurs modles de dveloppement concourrant
aux mmes objectifs . Ce qui explique le refus du gant de voter la dernire spcification JDO 2.0,
lune des plus aptes supplanter les EJB Entits.

En marge de ces travaux, plusieurs acteurs runis autour dun projet OpenSource trs convoit
rpondant au nom dHibernate embotent le pas aux gants de lindustrie. Aux cts de Sun, Gavin
King (lauteur dHibernate), engag aujourdhui aux cts de la socit JBoss, rdige les prmices
de la spcification EJB 3. En quelques mois, Hibernate gagne son pari et peut se targuer de runir
toute la communaut Java autour du projet EJB 3 (Enterprise Java Bean).

Autre signe des temps, Oracle, longtemps rest lcart de ce march du mapping, russit un tour
de force en rachetant pour une poigne de dollars lditeur Toplink, grand spcialiste du domaine.

Et comme pour conjurer un sort dj bien scell, Oracle sassocie au groupe dexpert de la JCP
EJB 3.

3.2 .NET
Ct .NET, la situation est sensiblement diffrente. Si lditeur de Redmond na jamais cach son
apathie pour le mapping objet/relationnel, privilgiant une approche plus spcifique de laccs aux
donnes avec sa solution de stockage Sql Server, il semble que les choses voluent diffremment
depuis quelques mois. En effet, la stratgie de Microsoft se redessine progressivement autour du
futur langage C# V3 et du Framework Linq (Language Integrated Query), un langage supportant
nativement les concepts de mapping avec plus spcifiquement Dlinq pour laspect mapping
objet/relationnel.

Si pour lheure Microsoft ne brille pas par son offre, la plateforme .NET peut tout de mme se
targuer de disposer de plus dune cinquantaine doutils (tous recenss sur le site
sharptoolbox.com). Ils se nomment nhibernate, DTM (Evaluant), Pragmatier ou encore EntityBroker.


5
3.2.1 Nhibernate
NHibernate est le portage de l'excellent Hibernate, outil de persistance relationnel vers la plate-
forme .NET. NHibernate (version 1.0) est compatible fonctionnellement avec Java Hibernate 2.1.
Cest dans le monde .NET, un des outils de mapping les plus utiliss.

4. Les diffrentes approches du mapping
4.1 La persistance manuelle (ADO.NET, DataSet, )
La persistance manuelle consiste grer soit mme le mapping objet/relationnel. .NET propose
dans ce domaine un large ventail doutils. Le plus connu et le plus largement utilis tant le
DataSet.

Le DataSet est un objet qui rside en mmoire et qui correspond une copie locale des donnes
d'une base. Il contient les tables d'une base mais aussi les relations entre ces diffrentes tables et
les contraintes appliques aux donnes. XML tant utilis sous .NET comme le standard de
description et de persistance des donnes, l'objet DataSet est reprsent sous cette forme. Les
donnes d'un DataSet sont formalises en XML et le schma est crit en XSD (XML Schema
Definition Language).

L'objet DataSet tant dconnect de toute source de donnes, il peut tre utilis pour la
transmission de donnes entre diffrents tiers d'une application (rle assur auparavant par les
Recordsets dconnects sous ADO), mais aussi d'autres systmes grce sa reprsentation
XML .
Si le DataSet convient parfaitement dans le cadre dapplications simples , il prsente de
multiples inconvnients. Tout dabord, un DataSet est un objet de donnes . A ce titre, il sintgre
relativement mal dans le cadre darchitectures multi-couches dans lesquelles les dpendances
binaires sont importantes. En effet, un DataSet ncessitant lutilisation de la DLL System.Data, toute
manipulation ct client requiert lintgration de cette DLL. Une approche qui va clairement
lencontre des prceptes fondamentaux du multi-tiers.

De plus, un DataSet est une classe plutt intrusive dans la mesure o le modle du domaine est
entirement mlang avec les mthodes techniques permettant de rechercher/sauvegarder les
entits (factories).

Tout cela fait quune approche ADO.NET manuelle doit tre rserve une application de faible
envergure et dont le modle du domaine (lensemble des classe persistantes) nest pas stratgique
(application essentiellement graphique, peu de mtier, etc )

4.2 La gnration de code (CodeSmith, NetTiers, etc )
Lapproche par gnration de code est une pratique cense rallier le meilleur des deux mondes
(persistance manuelle et automatique). Cette approche consiste gnrer statiquement tout le
code permettant de rendre les services techniques habituels des outils de mapping (persistance
automatique via recherche, sauvegarde, concurrence, etc ).

Loutil le plus en vogue dans la communaut .NET est CodeSmith : CodeSmith propose plusieurs
modles prdfinis permettant de gnrer diffrents types d'applications. L'outil NetTier constitue la
partie n-tiers de ces modles. .NetTiers are CodeSmith templates for object-relational mapping

6
that takes an existing SQLServer database and automatically generates a personnalized Data Tiers
application block to use in your .Net applications.



Linconvnient de lapproche par gnration de code est de constituer en quelque sorte le parent
pauvre du mapping. Certaines dcisions ne peuvent tre prises que dynamiquement (typiquement
vrifier quun objet et ses proprits sont dirty ). Lapproche par gnration part du principe que
lutilisateur matrise lui-mme le cycle de vie dune entit.
4.2.1 La persistance automatique
La persistance automatique est lessence du mapping objet/relationnelle. Cette approche consiste
prendre en charge le cycle de vie dune entit de manire dynamique (grce la gnration de
code et la rfection). Lutilisateur ne se proccupe aucun moment de ltat de son entit.
La persistance automatique fournit les services suivants :
- recherche, sauvegarde, mise jour
- concurrence
- cache de 1
er
niveau et second niveau
- navigation
- chargement la demande
- transactions (ventuellement)




7
5. Optimiser sa couche de persistance
Le fichier de mapping constitue la pierre angulaire d'un projet de mapping O/R. En fonction de son
paramtrage, les performances pourront varier d'un facteur 100 voire quelque fois 1000 ! Dans la
pratique, peu d'ouvrages ou documentations mettent en lumire les enjeux de ce fichier, combien
important.

Dans tout processus de mapping, il y a certaines tapes respecter. Parmi ces tapes, la mise en
uvre du fichier de configuration est une des plus importantes. En effet, une fois l'ensemble du
modle du domaine configur, le paramtrage du mode "lazy loading" pour les collections et autres
objets associs (par dfaut nhibernate ou hibernate prchargent entirement un graphe d'objets
complet) va avoir un effet direct sur les performances. De cette manire, la navigation ou la
rcupration d'un graphe ne ncessitera pas le chargement de donnes inutiles.

Dans un exemple contenant une table Order possdant une relation 0,n avec les lignes OrderLine.
Si l'utilisateur est intress simplement par le nom du client factur et la date de facturation, il n'est
pas ncessaire de charger les donnes OrderLine de la facture. Qui plus est s'ils ont chacun des
liens avec d'autres objets.
Dans un cadre purement objet, cette solution est assez naturelle mettre en place car tout lien
"tardif" ou "lazy load" est rsolu lorsque l'utilisateur cherche accder la dpendance. Exemple :

class LazyLoading {
public void WorkWithOrderAndOrderLines(string orderID) {
// Provoque le chargement d'un objet Order sans les OrderLine
// SQL : Select * from Order where OrderID=?
Order order = getSession().get(orderID, typeof(Order));

// Provoque le chargement des OrderLine de l'order courant
// SQL : Select * from OrderLine where OrderFK=?
Collection orderLines = order.getOrderLines();
}
}

Imaginons maintenant reproduire le mme exemple pour N Orders avec une boucle. Le cas
fonctionnel est trivial, nous souhaitons rcuprer les factures avec un total suprieur 1000 euros.
A priori, il suffit d'implmenter une mthode CalculTotal() prenant en paramtre une collection de
type OrderLine puis d'effectuer pour chaque ligne la multiplication Quantit*Prix, et ce pour chaque
facture. Le tout est illustr dans le code source suivant.

class NPlusUnSelect {
public void BestTotalOrder(string orderID) {
// Provoque le chargement de l'ensemble des objet Orders de la table
// Select * from Order 1 fois
System.Collections.IList orders = getSession().find("from order");
// Provoque le chargement des OrderLine de chaque order : N fois
// Select * from OrderLine where OrderFK=?
foreach (Orders ol in orders) {
Collection orderLines = ol.getOrderLines();
if (CalculTotal(orderLines) > 1000)
Console.WriteLine("Good Order!!");
}
}
}

8
Rien ne prsage dans un tel code du syndrome "N+1 Select" : le mal absolu en terme de mapping !
En pratique, pour une table contenant 1000 factures avec en moyenne 10 lignes Produit, il faudra
compter pas moins de 1001 requtes SQL pour excuter cette simple tche. Une requte pour
rcuprer l'ensemble des factures puis une autre pour chaque collection. Un vrai dsastre en
terme de performance surtout si on imagine multiplier ce facteur pour chaque utilisateur connect.
S'il peut paratre vident ici, le problme du N+1 Select est souvent masqu dans la complexit
globale d'une application rendant dlicate sa dtection. Seule une analyse minutieuse des traces
SQL (toujours activer le mode showSQL true) permettra d'y remdier.

5.1 Quelles sont les solutions ?
La plupart des outils se sont attaqus trs tt ce problme en proposant diverses optimisations.
Hibernate fournit ainsi l'ordre Batch-Size permettant de prcharger les n collections suivantes
lorsque vous accdez la premire. Dans l'exemple prcdent, un Batch-Size positionn 10
aurait permit de diviser le facteur n+1 par 10 en regroupant dans la mme requte SQL, 10
Collections appartenant 10 entits Order diffrentes. Bien entendu, si votre application ne
s'intresse qu' un Order, cette optimisation atteint vite son cas pire en ramenant plus
d'enregistrement qu'il n'en faudrait.

Curieusement, la rponse idale en terme de performance au problme du N+1 est une solution
avec une philosophie "relationnelle". Calculer le total le plus lev ou effectuer des calculs sur des
tuples peut se rsoudre en SQL ou HQL (le langage de requte objet d'hibernate) avec une seule
requte :

HQL : select order from Order order join order.orderLines ol group by order having sum(ol.amount)
> 1000

Cette requte ne renvoie uniquement que les enregistrements pertinents sans prcharger en
mmoire un graphe d'objet entier. L'inconvnient est son criture peu "objet" et son approche peu
gnrique, pour ne pas dire contre-nature.

Malgr tout, entre les deux alternatives prcdentes, il existe d'autres solutions plus lgantes
mme si lgrement moins performantes. La premire concerne l'ordre outer-join qui, spcifi dans
le fichier de mapping, permet d'indiquer l'outil que l'association doit tre rsolue grce une
jointure. Dans le cas d'une collection, hibernate va chercher recrer systmatiquement partir
d'une jointure l'association Order->OrderLines pour l'ensemble des entits Order de la table (via la
requte suivante :

select order o, orderline ol where o.orderid=ol.orderfk).

Une approche extrmement consommatrice en ressources mais qui a le mrite de rduire l'impact
du Syndrome N+1.

L'autre solution, la plus optimale, consiste tirer partie de l'API CreateCriteria pour prcharger les
collections en tirant partie d'un procd appel "Eager Fetching". Hibernate permet en effet de
rcuprer un graphe d'objet tout en spcifiant les associations charger. Dans le cas des
OrderLines, il suffit de spcifier explicitement qu'on souhaite rcuprer toutes les collections de
l'objet racine Order de la manire suivante :

CreateCriteria : List orders = session.createCriteria(Order.class).setFetchMonde("orderLines",
FetchMode.EAGER).list()


9
Hibernate ralise ainsi une jointure pour renvoyer la liste des Order avec une seule requte. Il reste
cependant extraire de manire distincte le jeu de rsultats car les jointures ont tendance
renvoyer des tuples dupliqus.

class SelectOptimized {
public void BestTotalOrder(string orderID) {
// Provoque le chargement de l'ensemble des objet Orders de la table avec une requte JOIN
HashMap orders =
New
HashMap(session.createCriteria(Order.class).setFetchMonde("orderLines",
FetchMode.EAGER).list());

foreach (Orders ol in orders) {
Collection orderLines = ol.getOrderLines();
if (CalculTotal(orderLines) > 1000) Console.WriteLine("Good Order!!");
}
}
}

Cet exemple illustre parfaitement le danger pour un utilisateur de naviguer dans un modle objet au
gr des besoins du moment pour rcuprer un jeu de donnes. Il convient de toujours prcharger
les donnes avant de les manipuler.

Le problme du N+1 Select constitue 90% des problmes de performances rencontrs sur le
terrain.
6. Conclusion
Quelle soit manuelle ou automatique, la persistance est une des composantes cls dune
application. Un mauvais choix ou une mauvaise conception auront des consquences irrversibles
sur les performances. Plus que jamais il est ncessaire de comprendre que persistance
transparente ne signifie pas persistance non matrise.

Vous aimerez peut-être aussi