IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]


 

53. La persistance des objets

 

chapitre 5 3

 

Niveau : niveau 2 Elémentaire 

 

La quasi-totalité des applications de gestion traitent des données dans des volumes plus ou moins importants. Dès que ce volume devient assez important, les données sont stockées dans une base de données.

Il existe plusieurs types de base de données

  • Hiérarchique : historiquement le type le plus ancien, ces bases de données étaient largement utilisées sur les gros systèmes de type mainframe. Les données sont organisées de façon hiérarchique grâce à des pointeurs. Exemple DL1, IMS, Adabas
  • Relationnelle (RDBMS / SGBDR) : c'est le modèle le plus répandu actuellement. Ce type de base de données repose sur les théories ensemblistes et l'algèbre relationnel. Les données sont organisées en tables possédant des relations entre elles grâce à des clés primaires et étrangères. Les opérations sur la base sont réalisées grâce à des requêtes SQL.  Exemple : MySQL, PosgreSQL, HSQLDB, Derby
  • Objet (ODBMS / SGBDO) : Exemple db4objects
  • XML (XDBMS) : Exemple : Xindice

La seconde catégorie est historiquement la plus répandue mais aussi une des moins compatibles avec la programmation orientée objet.

Ce chapitre contient plusieurs sections :

 

53.1. La correspondance entre les modèles relationnel et objet

La correspondance des données entre le modèle relationnel et le modèle objet doit faire face à plusieurs problèmes :

  • le modèle objet propose plus de fonctionnalités : héritage, polymorphisme, ...
  • les relations entre les entités des deux modèles sont différentes
  • un objet ne possède pas d'identifiant en standard (hormis son adresse mémoire qui varie d'une exécution à l'autre). Dans le modèle relationnel, chaque occurrence devrait posséder un identifiant unique

La persistance des objets en Java présente de surcroît quelques inconvénients supplémentaires :

  • de multiples choix dans les solutions et les outils (standard, commerciaux, open source)
  • de multiples choix dans les API et leurs implémentations
  • de nombreuses évolutions dans les API standard et les frameworks open source

 

53.2. L'évolution des solutions de persistance avec Java

La première approche pour faire une correspondance entre ces deux modèles a été d'utiliser l'API JDBC fournie en standard avec le JDK. Cependant cette approche présente plusieurs inconvénients majeurs :

  • nécessite l'écriture de nombreuses lignes de codes, souvent répétitives
  • le mapping entre les tables et les objets est un travail de bas niveau
  • ...

Tous ces facteurs réduisent la productivité mais aussi les possibilités d'évolutions et de maintenance. De plus, une grande partie de ce travail peut être automatisée.

Face à ce constat, différentes solutions sont apparues :

  • des frameworks open source : le plus populaire est Hibernate qui utilise des POJOs.
  • des frameworks commerciaux dont Toplink  était le leader avant que sa base passe en open source
  • des API Standards : JDO, EJB entity, JPA

 

53.3. Le mapping O/R (objet/relationnel)

Le mapping Objet/Relationnel (mapping O/R) consiste à réaliser la correspondance entre le modèle de données relationnel et le modèle objets de la façon la plus facile possible.

Un outil de mapping O/R doit cependant proposer un certain nombre de fonctionnalités parmi lesquelles :

  • Assurer le mapping des tables avec les classes,  des champs avec les attributs, des relations et des cardinalités
  • Proposer une interface qui permette de facilement mettre en oeuvre des actions de type CRUD
  • Eventuellement permettre l'héritage des mappings
  • Proposer un langage de requêtes indépendant de la base de données cible et assurer une traduction en SQL natif selon la base utilisée
  • Supporter différentes formes d'identifiants générés automatiquement par les bases de données (identity, sequence, ...)
  • Proposer un support des transactions
  • Assurer une gestion des accès concurrents (verrou, dead lock, ...)
  • Fournir des fonctionnalités pour améliorer les performances (cache, lazy loading, ...)

Les solutions de mapping sont donc riches en fonctionnalités ce qui peut rendre leur mise en oeuvre plus ou moins complexe. Cette complexité est cependant différente d'un développement de toute pièce avec JDBC.

Les solutions de mapping O/R permettent de réduire la quantité de code à produire mais impliquent une partie configuration (généralement sous la forme d'un ou plusieurs fichiers XML ou d'annotations pour les solutions reposant sur Java 5).

Depuis quelques années, les principales solutions mettent en oeuvre des POJO (Plain Old Java Object).

 

53.3.1. Le mapping de l'héritage de classes

L'héritage est un des concepts clé de la programmation orientée objet mais il ne possède pas d'équivalent dans le modèle relationnel.

Le modèle objet supporte des relations de type « est un » ou « possède un ».

Le modèle relationnel supporte seulement les relations de types « possède un »

Il existe cependant plusieurs stratégies standard pour permettre de mapper une hiérarchie de classes dans le modèle relationnel. Chacune de ces stratégies présente des inconvénients.

Le choix de la stratégie doit tenir de deux points particuliers :

  • la performance
  • la maintenabilité du schéma et des données de la base de données

Le changement de la stratégie de mapping impose une migration plus ou moins lourde des données.

 

53.3.1.1. Une table par hiérarchie de classes

Dans la stratégie une table par hiérarchie de classes (Table Per Hierarchy : TPH), une seule table est utilisée pour mapper l'ensemble de la hiérarchie de classes.

Cette table contient toutes les propriétés de chaque classe de la hiérarchie. Les propriétés qui n'appartiennent pas à la classe précisée par le discriminant ont pour valeur null. Toutes les colonnes de la table correspondant à une de ces propriétés doivent être nullable. Si la hiérarchie comporte de nombreuses classes ou si le nombre de leurs propriétés est important alors la table est composée de toutes ces propriétés dont la plupart seront null.

Il est important que toutes les propriétés des classes qui sont mappées aient toutes un nom de colonne unique.

Cette table requiert une colonne technique supplémentaire qui sert de discriminant en identifiant la classe concernée pour les données de la ligne. La colonne qui sert de discriminant permet de préciser selon sa valeur à quelle classe correspondent les données de la ligne : elle permet à Hibernate de déterminer le type de la classe qu'il devra instancier lors de la lecture de la ligne dans la base de données.

Il n'est pas possible de définir le type d'une classe fille comme clé étrangère sur l'identifiant de la table puisque toutes les classes sont mappées dans une seule table.

Avantages

Inconvénients

Facile à implémenter

Les performances de cette stratégie sont bonnes grâce à une seule instruction select permettant de retrouver les données (aucune jointure ni sous-select ne sont requis)

Une seule table à gérer

Chaque changement d'une propriété dans une des classes nécessite de modifier la colonne correspondante dans la table (ajout, suppression, modification). Elle n'est donc pas adaptée si le graphe d'objets est modifié fréquemment.

La contrainte NOT NULL ne peut pas être utilisée sur toutes les colonnes qui correspondent à une propriété dans les classes filles

La table n'est pas normalisée : elle ne répond pas à la troisième forme normale


Si des requêtes sont faites sur les types des classes filles, il peut être intéressant de définir un index sur la colonne qui sert de discriminant.

 

53.3.1.2. Une table par sous-classe

La stratégie une table par sous-classe (Table Per Subclass : TPS) propose de convertir la relation "est un" du modèle objet en une relation « possède un » du modèle relationnel en utilisant des clés étrangères. Toutes les sous-classes et la mère si elle est abstraite sont mappées sur leurs propres tables.

Les tables des sous-classes possèdent une clé étrangère qui permet de faire référence à la clé primaire de la table de la classe mère mettant en oeuvre une relation de type 1-1. La clé primaire de la table de la classe mère est utilisée comme clé primaire et clé étrangère dans les tables des classes filles. Hibernate utilise alors des jointures ouvertes dans les requêtes SQL pour obtenir les données.

Cette stratégie de mapping est celle qui est la plus proche du modèle objet. Chaque classe abstraite ou concrète est mappée sur une table dans la base de données. Seule la colonne servant d'identifiant est partagée par toutes les tables : elle permet de faire les jointures requises entre les tables.

Avantages

Inconvénients

Le modèle relationnel est normalisé et l'intégrité des données peut être maintenue

Les changements dans la base de données sont simples lorsqu'une modification a lieu dans une classe

Les performances se dégradent lorsque la hiérarchie de classes grossit car le nombre de jointures entre tables augmente dans les requêtes

 

53.3.1.3. Une table par classe concrète

Dans la stratégie une table par classe concrète (Table Per Concret Class), chaque classe concrète de la hiérarchie possède sa propre table. Les colonnes des classes mères sont dupliquées dans les tables des classes fille. Les classes abstraites ne possèdent pas de table.

Chaque table de la hiérarchie possède des identifiants dédiés : il n'y a pas d'identifiants dupliqués dans ces tables. Ceci permet de garantir l'unicité des identifiants lors de requêtes polymorphiques.

Il n'y a pas de relation entre les tables de la hiérarchie. Il n'est pas nécessaire d'avoir un champ de type discriminant puisque chaque classe possède sa propre table.

Avantages

Inconvénients

C'est la stratégie de mapping la plus simple à implémenter

Les tables ne sont pas normalisées

Les colonnes correspondant à des données de classes mères abstraites sont dupliquées dans les tables des classes filles. La modification d'une colonne (ajout / modification / suppression) de la table de la classe mère doit donc être reportée dans toutes les tables des classes filles

Le support du polymorphisme est délicat car il nécessite plusieurs requêtes ou l'utilisation d'une clause UNION. Pour effectuer une requête sur le type de la classe mère, il sera nécessaire de faire des requêtes sur chacune des tables

Les performances se dégradent si la hiérarchie comporte de nombreuses classes : les requêtes polymorphiques sont coûteuses car elles requièrent l'utilisation d'unions sur les tables de la hiérarchie

Il n'est pas possible d'utiliser la colonne identifiant comme clé étrangère car elle est répartie sur plusieurs tables

Il n'est pas possible de définir des contraintes d'unicité sur des colonnes d'une classe mère car elles sont dupliquées sur toutes les tables

Il n'est pas possible d'utiliser une valeur auto-générée pour les colonnes identifiants car chaque ligne dans toutes les classes de la hiérarchie doivent avoir un identifiant unique


Cette stratégie n'est pas fréquemment utilisée.

 

53.3.2. Le choix d'une solution de mapping O/R

Comme pour tout choix d'une solution, des critères standard doivent entrer en ligne de compte (prix, complexité de prise en main, performance, maturité, pérennité, support, ...)

Dans le cas d'une solution de mapping O/R, il faut aussi prendre en compte des critères plus spécifiques à ce type de technologie.

La solution doit proposer des fonctionnalités de base :

  • gestion de tous les types de relations (1-1, 1-n, n-n)
  • langage de requêtes supportant des fonctions avancées de SQL (jointure, groupage, agrégat, ...)
  • support des transactions
  • support de l'héritage et du polymorphisme
  • support de nombreuses bases de données
  • gestion des accès concurrents
  • support des différents types de clés et des clés composées
  • support des mises à jour en cascade
  • support du mapping de type une classe contenant des données de plusieurs tables ou l'inverse
  • configuration par fichiers ou annotations
  • ...

La prise en compte des performances et des optimisations proposées par la solution est très importante :

  • chargement différé (lazy loading) au niveau d'un champ
  • gestion de caches au niveau des données et des requêtes
  • optimisation des requêtes
  • ...

Il est aussi nécessaire de tenir compte des outils proposés par la solution pour faciliter sa mise en oeuvre. Ces outils peuvent par exemple :

  • permettre l'automatisation de la génération des classes ou des schémas de la base de données
  • faciliter la rédaction et la maintenance des fichiers de configuration
  • ...

Certaines fonctionnalités avancées peuvent être utiles voire requises en fonction des besoins :

  • mise en oeuvre de POJO
  • support du mode déconnecté
  • support des procédures stockées
  • ...

 

53.3.3. Les difficultés lors de la mise en place d'un outil de mapping O/R

De nombreuses difficultés peuvent survenir lors de la mise en oeuvre d'un outil de mapping O/R

  • Difficultés à mapper le modèle relationnel à cause de la complexité du modèle ou de sa mauvaise conception
  • Temps d'apprentissage de l'outil plus ou moins important
  • Difficultés pour mettre en oeuvre les transactions
  • Parfois des problèmes de performance peuvent nécessiter un paramétrage plus fin notamment en se qui concerne la mise en oeuvre de caches ou du chargement tardif (lazy loading)
  • Difficultés pour maintenir les fichiers de configuration généralement au format XML et à les synchroniser avec les évolutions du modèle de données. Des outils existent pour certaines solutions afin de faciliter cette tâche.

 

53.4. L'architecture et la persistance de données

Dans une architecture en couches, il est important de prévoir une couche dédiée aux accès aux données.

Il est assez fréquent dans cette couche de parler de la notion de CRUD qui représente un ensemble des 4 opérations de bases réalisables sur des données.

Il est aussi de bon usage de mettre en oeuvre le design pattern DAO (Data Access Object) proposé par Sun.

 

53.4.1. La couche de persistance

La partie du code responsable de l'accès aux données dans une application multiniveau doit être encapsulée dans une couche dédiée aux interactions avec la base de données de l'architecture généralement appelée couche de persistance. Celle-ci permet notamment :

  • d'ajouter un niveau d'abstraction entre la base de données et l'utilisation qui en est faite.
  • de simplifier la couche métier qui utilise les traitements de cette couche
  • de masquer les traitements réalisés pour mapper les objets dans la base de données et vice versa
  • de faciliter le remplacement de la base de données utilisée

La couche métier qui va utiliser la couche de persistance reste indépendante du code dédié à l'accès à la base de données. Ainsi la couche métier ne contient aucune requête SQL, ni code de connexion ou d'accès à la base de données. La couche métier utilise les classes de la couche persistance qui encapsulent ces traitements. Ainsi la couche métier manipule uniquement des objets pour les accès à la base de données.

Le choix des API ou des outils dépend du contexte : certaines solutions ne sont utilisables qu'avec la plate-forme Enterprise Edition (exemple : les EJB) ou sont utilisables indifféremment avec les plates-formes Standard et Enterprise Edition.

L'utilisation d'une API standard permet de garantir la pérennité et de choisir l'implémentation à mettre en oeuvre.

Les solutions open source et commerciales ont les avantages et inconvénients inhérents à leur typologie respective.

 

53.4.2. Les opérations de type CRUD

L'acronyme CRUD (Create, Read, Update and Delete) désigne les quatre opérations  réalisables sur des données (création, lecture, mise à jour et suppression).

Exemple : une interface qui propose des opérations de type CRUD pour un objet de type Entite
public interface EntiteCrud {
  public Entite obtenir(Integer id);
  public void creer(Entite entite);
  public void modifier(Entite entite);
  public Collection obtenirTous();
  public void supprimer(Entite entite);
}

 

53.4.3. Le modèle de conception DAO (Data Access Object)

DAO est l'acronyme de Data Access Object. C'est un modèle de conception qui propose de découpler l'accès à une source de données.

L'accès aux données dépend fortement de la source de données. Par exemple, l'utilisation d'une base de données est spécifique pour chaque fournisseur. Même si SQL et JDBC assurent une partie de l'indépendance vis-à-vis de la base de données utilisée, certaines contraintes imposent une mise en oeuvre spécifique de certaines fonctionnalités.

Par exemple, la gestion des champs de type identifiants est proposée selon diverses formes par les bases de données : champ auto-incrémenté, identity, séquence, ...

Le motif de conception DAO proposé dans le blue print de Sun propose de séparer les traitements d'accès physique à une source de données de leur utilisation dans les objets métiers. Cette séparation permet de modifier une source de données sans avoir à modifier les traitements qui l'utilisent.

Le DAO peut aussi proposer un mécanisme pour rendre l'accès aux bases de données indépendant de la base de données utilisée et même rendre celle-ci paramétrable.

Les classes métier utilisent le DAO par son interface et sont donc indépendantes de son implémentation. Si cette implémentation change (par exemple un changement de base de données), seul l'implémentation du DAO est modifiée mais les classes qui l'utilisent via son interface ne sont pas impactées.

Le DAO définit donc une interface qui va exposer les fonctionnalités utilisables. Ces fonctionnalités doivent être indépendantes de l'implémentation sous-jacente. Par exemple, aucune méthode ne doit avoir de requêtes SQL en paramètre. Pour les même raisons, le DAO doit proposer sa propre hiérarchie d'exceptions.

Une implémentation concrète de cette interface doit être proposée. Cette implémentation peut être plus ou moins complexe en fonction de critères de simplicité ou de flexibilité.

Fréquemment les DAO ne mettent pas en oeuvre certaines fonctionnalités comme la mise en oeuvre d'un cache ou la gestion des accès concurrents.

 

53.5. Les différentes solutions

Différentes solutions peuvent être utilisées pour la persistance des objets en Java :

  • Sérialisation
  • JDBC
  • Génération automatisée de code source
  • SQL/J
  • Enrichissement du code source (enhancement)
  • Génération de bytecode
  • framework de mapping O/R (Object Relational Mapping)
  • Base de données objet (ODBMS)

La plupart de ces solutions offre de surcroît un choix plus ou moins important d'implémentations.

 

53.6. Les API standards

Les différentes évolutions de Java ont apporté plusieurs solutions pour assurer la persistance des données vers une base de données.

 

53.6.1. JDBC

JDBC est l'acronyme de Java DataBase Connectivity. C'est l'API standard pour permettre un accès à une base de données. Son but est de permettre de coder des accès à une base de données en laissant le code le plus indépendant de la base de données utilisée.

C'est une spécification qui définit des interfaces pour se connecter et interagir avec la base de données (exécution de requêtes ou de procédures stockées, parcours des résultats des requêtes de sélection, ...)

L'implémentation de ces spécifications est fournie par des tiers, et en particulier les fournisseurs de base de données, sous la forme de Driver.

La mise en oeuvre de JDBC est détaillée dans le chapitre JDBC

 

53.6.2. JDO 1.0

JDO est l'acronyme de Java Data Object : le but de cette API est de rendre transparente la persistance d'un objet. Elle repose sur l'enrichissement du bytecode à la compilation.

Cette API a été spécifiée sous la JSR-012

Il existe plusieurs implémentations dont :

  • Apache JDO (http://db.apache.org/jdo/) 
  • JPOX
  • Xcalia LiDO
  • Kodo (http://www.solarmetric.com/) - BEA 
  • Speedo (http://speedo.objectweb.org/) 

La mise en oeuvre de JDO est détaillée dans le chapitre JDO

 

53.6.3. JD0 2.0

La version 2 de JDO a été diffusée en mars 2006.

Il existe plusieurs implémentations dont :

  • Apache JDO (http://db.apache.org/jdo/) 
  • JPOX (http://www.jpox.org) : c'est l'implémentation de référence (RI) pour JDO 2.0
  • Kodo (http://www.solarmetric.com/) - BEA 

 

53.6.4. EJB 2.0

Les EJB (Enterprise Java Bean) proposent des beans de type Entités pour assurer la persistance des objets.

Les EJB de type Entité peuvent être de deux types :

  • CMP (Container Managed Persistance) : la persistance est assurée par le conteneur d'EJB en fonction du paramétrage fourni
  • BMP (Bean Managed Persistance) :

Les EJB bénéficient des services proposés par le conteneur cependant cela les rend dépendants de ce conteneur pour l'exécution : ils sont difficilement utilisables en dehors du conteneur (par exemple pour les tester).

Il existe de nombreuses implémentations puisque chaque serveur d'applications certifié J2EE doit implémenter les EJB ce qui inclut entre autres JBoss de RedHat, JonAS, Geronimo d'Apache, GlassFish de Sun/Oracle, Websphere d'IBM, Weblogic de BEA, ...

Les Entity Beans CMP 2.x ont été déclarés pruned dans Java EE 6.

 

53.6.5. Java Persistence API et EJB 3.0

JPA (Java Persistence API) est issu des travaux de la JSR-220 concernant la version 3.0 des EJB : elle remplace d'ailleurs les EJB Entités version 2. C'est une synthèse standardisée des meilleurs outils du sujet (Hibernate, Toplink/EclipseLink, ...)

L'API repose sur

  • l'utilisation d'entités persistantes sous la forme de POJOs
  • un gestionnaire de persistance (EntityManager) qui assure la gestion des entités persistantes
  • l'utilisation d'annotations
  • la configuration via des fichiers xml

JPA peut être utilisé avec Java EE dans un serveur d'applications mais aussi avec Java SE (avec quelques fonctionnalités proposées par le conteneur en moins).

JPA est une spécification : il est nécessaire d'utiliser une implémentation pour la mettre en oeuvre. L'implémentation de référence est la partie open source d'Oracle Toplink : Toplink essential. La version 3.2 d'Hibernate implémente aussi JPA.

JPA ne peut être utilisé qu'avec des bases de données relationnelles.

La version 3.0 des EJB utilise JPA pour la persistance des données.

La mise en oeuvre de JPA est détaillée dans le chapitre JPA

 

53.7. Les frameworks open source

Pour palier certaines faiblesses des API standards, la communauté open source a développé de nombreux frameworks concernant la persistance de données dont le plus utilisé est Hibernate. Cette section va rapidement présenter quelques-uns d'entre-eux.

 

53.7.1. iBatis

Le site officiel du projet est à l'url : http://ibatis.apache.org/

 

53.7.2. MyBatis

Le site officiel du projet est à l'url : http://mybatis.org/

 

53.7.3. Hibernate

Hibernate est le framework open source de mapping O/R le plus populaire. Cette popularité est liée à la richesse des fonctionnalités proposées et à ses performances.

Hibernate propose son propre langage d'interrogation HQL et a largement inspiré les concepteurs de l'API JPA. Hibernate est un projet open source de mapping O/R qui fait référence en la matière car il possède plusieurs avantages :

  • manipulation de données d'une base de données relationnelles à partir d'objets Java
  • facile à mettre en oeuvre, efficace et fiable
  • open source

Le site officiel du projet est à l'url : http://hibernate.org/

L'utilisation d'Hibernate est détaillée dans le chapitre  Hibernate

 

53.7.4. EclipseLink

Le site officiel du projet est à l'url : http://eclipse.org/eclipselink/

EclipseLink est un projet open source de la fondation Eclipse qui propose un framework extensible permettant de se connecter à une source de données : base de données (JPA), Moxy pour XML (JAXB), Java Connector Architecture (JCA), Service Data Object (SDO).

EclipseLink est l'implémentation de référence de JPA 2.0.

 

53.7.5. Castor

Castor permet de mapper des données relationnelles ou XML avec des objets Java.

Castor propose une solution riche mais qui n'implémente aucun standard.

Le site officiel du projet est à l'url : http://castor.codehaus.org/

 

53.7.6. Apache Torque

Torque est un framework initialement développé pour le projet Jakarta Turbine : il est développé depuis sous la forme d'un projet autonome.

Torque se compose d'un générateur qui va générer automatiquement les classes requises pour accéder à la base de données et d'un environnement d'exécution qui va permettre la mise en oeuvre des classes générées.

Torque utilise un fichier XML contenant une description de la base de données pour générer des classes permettant des opérations sur la base de données grâce à des outils dédiés. Ces classes reposent sur un environnement d'exécution (runtime) fourni par Torque.

Le site officiel du projet est à l'url http://db.apache.org/torque/

 

53.7.7. TopLink

TopLink a été racheté par Oracle et intégré dans ses solutions J2EE.

Le site officiel du produit est à l'url : http://www.oracle.com/technetwork/middleware/toplink/overview/index.phpl

La partie qui compose la base de TopLink est en open source.

 

53.7.8. Apache OJB

Le site officiel du projet est à l'url http://db.apache.org/ojb/

 

53.7.9. Apache Cayenne

Le site officiel du projet est à l'url http://cayenne.apache.org/

Cayenne est distribué avec un outil de modélisation nommé CayenneModeler.

 

53.8. L'utilisation de procédures stockées

L'utilisation de procédures stockées peut apporter des améliorations notamment en termes de performance de certains traitements.

Cependant, les procédures stockées possèdent plusieurs inconvénients :

  • peu portable
  • peu flexible
  • maintenance généralement difficile liée au manque d'outillage
  • ...

 


Développons en Java v 2.20   Copyright (C) 1999-2021 Jean-Michel DOUDOUX.   
[ Précédent ] [ Sommaire ] [ Suivant ] [ Télécharger ]      [ Accueil ] [ Commentez ]