JDO

Le logo de JDOJava Data Objects : objets de données Java.

Besoin

Gérer la persistance d'objets Java de manière :

Analyse

JDO vise à ajouter à des POJO une capacité de persistance de manière "orthogonale", c'est-à-dire sans contraintes pour ces objets (suivant en cela l'approche AOP, en ajoutant un aspect "persistance" aux objets).

En particulier il vise à garantir une indépendance :

JDO accède au support de stockage (datastore) via le moyen de son choix. Il peut s'agir de pilotes JDBC ou de connecteurs JCA dans le cas d'applications J2EE.

Cas d'utilisation

JDO APIOn peut distinguer 3 types de classes dans une application utilisant JDO :

  • non-enhancées, n'ayant aucun lien avec JDO
  • persistence capable (PC) : les classes d'objets capables de persister leurs données et/ou de les modifier transactionnellement. Comme indiqué précédemment ces classes n'ont rien de particulier, hormis le fait que leur code a été enrichi après leur compilation.
  • persistence aware : les classes conscientes de la persistance des persistance capable, utilisant l'API JDO pour les manipuler (requêtes de recherches multi-critères, délimitation des transactions, changement d'état JDO, etc.). Ces classes n'ont en fait rien rien de particulier, et persistence aware se réduit plus à un rôle sans conséquence technique. La seule exception est le cas rare (et peu recommandé) où elles souhaiteraint accéder directement (sans passer par des méthodes) aux champs public/package/protected d'une classe persistence capable (elles devront alors être enhancées comme les persistence capable pour assurer la transparence de ces accès).

Persistence capable

Un objet JDO persistence-capable est un objet Java capable d'être :

  • transactionnel : Les modifications depuis le dernier début de transaction peuvent être annulées (l'état précédent est rétabli)
    • persistant de manière transparente (sans que le développeur ait à modifier son code source).
    • transitoire (transient) : en mémoire

Cette capacité lui est donnée après modification de son code (généralement son code compilé, ou bytecode) par outil. Cette instrumentation du code après compilation est appelée enhancement (enrichissement de code). Cet enhancement se base sur un descriptif des classes à rendre persistence-capable (définissant les classes et champs à rendre persistants, les champs caractérisant l'identité d'un objet, etc.), nommé métadonnées (metadata).

L'ensemble des états décrits ci-dessus (transactionnel-persistant, transactionnel-transitoire) est généralement qualifié d'ensembles des états "gérés" (managed), puisque c'est dans ces cas que la gestion JDO apporte une valeur ajoutée. Ceci dit un objet enhancé peut également passer en état transitoire-non transactionnel, c'est à dire ayant un comportement identique à des objets Java classiques ou JDO ne s'interpose plus.

Outre sa classe et les champs qui composent son état, un objet Persistence Capable est caractérisé par son identité qui peut être :

  • durable : quand l'objet est persistant
    • gérée par JDO (datastore identity), qui génère et récupère implicitement une identité pour chaque instance.
    • applicative (application identity) : le développeur spécifie alors dans ses métadonnées JDO une combinaison de champs de l'objet (qui seront typiquement associés à leurs colonnes équivalentes dans un SGBDR). Un cas particulier et courant de ce type d'identité est celui d'une identité composée d'un seul champ simple. On parle alors d'identité simple (simple identity).
  • non durable : pour les transactions en mémoire

Persistence aware

Les classes manipulant les objets persistence capable effectuent des :

  • lectures :
    • recherche par identifiant unique
    • recherche multi-critères (requête JDO QL), itération sur les résultats, tris, etc.
    • lecture partielles (groupe de champs chargés par défaut, et non pas tous les champs d'un objet)
    • lecture non transactionnelles : plus performantes mais pouvant être incohérentes (un attribut lu n'est pas garanti cohérent avec un autre lu précédement par exemple).
  • écritures : modification de l'état
    • automatiquement répercuté en base lors de la validation de la transaction
      • optimiste :la transaction est gérée par JDO, aucun verrou n'est maintenu, et la vérification de non-concurrent se fait en fin de transaction. Particulièrement adapté aux applications définissant des transactions dépendantes d'actions clients potentiellement longues ("think time" du client incompatible avec le maintient de verrous).
      • pessimistes : la transaction est gérée par le SGBD, qui maintient des verrous durant celle-ci afin de prévenir des écritures concurrentes.
    • répercuté uniquement dans le cache, en cas d'écriture non transactionnelle.

Conception

JDO SPIPersistence capable

Pour être géré (managed) par JDO, un objet doit implémenter un contrat de Persistence Capable via une instrumentation (enhancement) du bytecode de sa classe. Cette post-compilation ajoute au code des méthodes et champs additionnels, et du code statique permettant d'interragir avec l'infrastructure JDO (de tel ou tel fournisseur, l'enhancement étant standard et n'imposant donc pas de modification des classes métier si l'on change de fournisseur JDO).

Comme il s'agit de l'implémentation d'une interface, il est également possible - mais jamais fait en pratique - d'utiliser d'autres techniques que l'enhancement, telle que :

La correspondance (mapping) entre l'état mémoire de l'objet et son état persistant est rédigé dans un descripteur standard, externe au code (fichier XML), pouvant comporter des extensions propriétaires.

L'accès aux données peut être :

Implémentation

Etats JDO
Transactionnel Non transactionnel
Persistant Lectures
Transitoire Comportement identique à celui d'un objet non JDO
Optimisations Ecriture dans le cache <strong>NonTransactionalWrite</strong>=true
Lecture non synchronisée <strong>NonTransactionalRead</strong>=true
Recherche dans le cache <strong>ignoreCache</strong>=false

Type Description
Optimisations Conservation du cache après transaction <strong>retainValues</strong>=true
Non-restauration après annulation <strong>restoreValue</strong>=false
Ecriture dans le cache <strong>NonTransactionalWrite</strong>=true
Lecture non synchronisée <strong>NonTransactionalRead</strong>=true
Recherche dans le cache <strong>ignoreCache</strong>=false
Transactions JDO

Egalement le JDO Query Language (JDOQL) permet d'obtenir des instances ou collections d'instances JDO d'un type donné, éventuellement filtrées par divers critères exprimés dans une syntaxe proche de celle du langage Java.

Filtres JDOQL
Forme Commentaire
Egalité Avec contante champ == valeur, champ != valeur, champ == null, champ != null
Entre champs champ1 == champ2
Comparaison Caractères chaîne > valeur
chaîne < valeur
chaîne1 < chaîne2
chaîne1 > chaîne2
Pour les types non numérique, l'interprétation est dépendante de l'implémentation / du support de persistance
chaîne.startsWith (valeur), chaîne.endsWith (valeur)
Expression expression1 && expression2
expression1 || expression2
Collections Existence d'un élément caractérisé collection.contains (var) && expression (var)
Vide collection.isEmpty()

Des contraintes peuvent également être imposées sur les ensembles résultats retournés, qui peuvent être :

API

L'API de JDO est définie dans le package javax.jdo.

On peut distinguer trois types de classes dans une application JDO :

Une implémentation JDO doit au moins supporter une des identités SGBD.

SPI

En dehors de l'API visible par les développeurs utilisant JDO, comment cela fonctionne-t-il "à l'intérieur" ?

A l'exécution, toute instance JDO "enhancée" référence un gestionnaire de son état (StateManager) qui communique avec le PersistenceManager pour gérer la persistance.

Le StateManager, chargé de gérer le cycle de vie d'une (ou plusieurs) instances, considère plusieurs états possibles dans la vie du d'un PersistenceCapable :

FAQ

FUD

Du FUD a parfois été présenté sur JDO.

Notes

Exemples

Un exemple de classe persistante est n'importe quelle classe POJO.

Un exemple de code JDO est :

import javax.jdo.*;

 // Code code suppose que pm réference un PersistenceManager et qu'il y a un contexte transactionnel en cours
    Employee jerome = new Employee ("Jérôme", "Beau");
 Address jeromeAddress = new Address("71 rue Desnouettes", "75015", "Paris");

      jerome.setAddress (jeromeAddress);

 Transaction tx = pm.currentTransaction();

      tx.begin();
 {
 ProjectId lidoProjectId = new ProjectId ("LiDO", 3.1); // Identifiant métier du projet

      Project lidoProject = pm.getObjectById (lidoProjectId, true); // Récupère le projet en base


 jerome.getProjects().add (lidoProject);
 lidoProject.getWorkers().add (jerome);
 pm.makePersistent (jerome);
      // Rend aussi jeromeAddress persistant, de proche en proche (clôture transitive)

}
 tx.commit(); // Ecrit les jerome et jeromeAddress en base
    tx.begin();
 {
 // Récupère les projets dont le nom commence par "L", dont les collaborateurs et travaillent à Paris ou qui contiennent le collaborateur jerome

  Query query = pm.newQuery (Projet.class, "workers.contains(aPeople) && aPeople.address.city == someCity && name.startsWith(\"L\") || aPeople == somePeople");

      myQuery.declareVariables  ("Employee aPeople");
 myQuery.declareParameters ("String someCity, Employee somePeople");
 myQuery.setOrdering ("name ascending, manager.name ascending"); // Trie les résultat par nom de projet puis éventuellement par nom du manager du projet

      Collection foundProjects = (Collection) myQuery.execute (jerome, "Paris"); // Exécute la requête avec les arguments suivants
 Iterator foundProjectsIterator = foundProjects.iterator();
 while (iter.hasNext()) {
 Project aFoundProject = (Project) foundProjectsIterator.next();

     // Utilise projet
 }

      myQuery.close (result);
 }
 tx.commit();

      pm.close();

Un exemple de descripteur JDO standard est :

<!--?xml version="1.0" encoding="UTF-8"?-->
    <!DOCTYPE jdo SYSTEM "jdo.dtd">
      <jdo>
        <package name="net.javarome.jdo.samples">
          <class name="Person" identity-type="application" objectidclass="PersonKey">
            <field name="nom" primary-key="true">
          </field></class>
          <class name="Employe" identity-type="application" persistence-capable-superclass="Person">
            <field name="salaire" default-fetch-group="true">
            <field name="dept">
            <field name="chef">
          </field></field></field></class>
      <class name="Project" identity-type="application">

      <field name="nom" primary-key="true">

      <field name="workers">

      <collection element-type="Employe">

      </collection></field>

      </field></class>


      </package>


      </jdo>
Implémentations JDO
Fournisseur Exadel Object Frontier OSS Object Industries Versant Solarmetric Xcalia (LIBeLIS) ObjectDB Software (Apache) Jakarta SignSoft (Apache)
Produit Exadel JDO Frontier Suite for JDO JPOX JRelay JDO Genie

Judo / enJin

Fast Objects Kodo JDO LiDO ObjectDB OJB intelliBO TJDO
Version 3 1 2 j2 2 3 3 2
Domaine Technologie Release 0 1 0 5 1 0
Standard JDO Version 1.0 1.0 2.0-- 2.0--
Cache Partagé / niveau 2 Entre PM Oui Oui Active Java Cache Oui lido.cache.mode=shared CacheConfiguration. setUseNTXCache (true)
Entre PMF JMS ou TCP

lido.cache.mode=shared<br> lido.cache.group= myMultiplePmfGroup
(si même JVM)

Agrafage d'objets DataCache.pin(objectId) pin(object) ou lido.cache.classStrategy (myClassName)= pinned
Par classe Extension cache-strategy=yes|all d'une classe Extension can-cache =true d'une classe (défaut) Oui CacheConfiguration. setUseNTXCache (false)
Distribué Version entreprise Oui JMS Via cache listeners plugins, JDOGenie Servers Non kodo.DataCache via DataRepository API JMS, TCP
JCache Non Oui via DataRepository API
Pluggable Oui Oui
Tangosol Oui kodo.DataCache: tangosol(TangosolCacheName=kodo, TangosolCacheType=distributed) via DataRepository API
Exclusif lido.cache.exclusive=true
Evénements kodo.event.RemoteCommitProvider: jms ou tcp lido.cache.CommitListeners= com.acme.MyCommitListener
Requêtes JDO QL Extensions String toLowerCase() stringContains() caseInsStarts() caseInsEnds() caseInsContains() toLowerCase() toUpperCase() Non charAt() length() startsWith(substr,pos) substring(begin,end) toLowerCase() toUpperCase()
Map containsKey(key) <code>contains(value) isEmpty()</code> containsKey(key)containsValue(value) containsKey(key)containsValue(value) Non
Curseurs Mono-transaction randomAccess= true com.solarmetric.kodo. ResultListProperties cursor= basic
Cross-transactions cursor= cross-pm
Taille paramétrable fetch-size=nLIDOHINT
Cache des résultat kodo.QueryCache: CacheSize=n Oui
Chargement Grappe fetch-group=fields Eager Fetching
Personnalisable Fetch groups
load= dfg| true JDOQLQuery. addFetchGroupAttribute ("myAttribute");
SQL Libre Oui Oui Non sqlEmbed

query = pm.newQuery ("sql")<br> query.setFilter ("where ...")<br> query.setFilter ("select ...")

Non SQLQuery query = QueryFactory. newSQLQuery() Oui
Procédures stockées query.setFilter ("call ...")
Transactions Distribuées (XA) Oui Oui Non Oui Oui Oui
Optimistes Versionnage Oui Oui Oui Oui N° version, Ttimestamp Non Non Extension jdbc-version-ind" value="version-number" champ JDO sélectionné (lido.optimistic.class.MyClass =selectedsur champ int ou Date) Oui Extension optimistic-lock(timestamp ou version)
Champs modifiés Oui Tous, champs modifiés, champ / classe Non
Ecriture non transactionnelle Non Non Oui Non Non Oui Oui Oui
Lecture non transactionnelle Oui Oui Oui Non Non Oui Oui Oui
Environnement Intégration serveurs applicatifs Weblogic, WebSphere, JBoss, Orbix E2A, Oracle 9i, HP AS, Orion, JRun Java Connectors

Via MBeans (WLS 7.0, JBoss 2.4, 3.0, WAS 5.0)

Non DataSource (Jboss, WebLogic, WenSphere, SunOne, JRun, Borland)
Java Connectors Java Connectors
Attacher/détacher (JDO 2.0) Oui Oui
Stockage SGBDR CloudScape Non Oui Non Non Non Non 4.0.6 Oui Non Non 4.0.6, 5.0.9
DB2 7.1 Oui 07.02.0000 Oui Non Oui 7.2 Oui Non Oui 7.2.3
FireBird Non Non Non 1.0.2 Non Non Non Oui Non Non 1.0.0.796
HSQL DB Non Non Non Non Non Non 1.6 Oui Non Non Non
Ingres Non Non Non Non Non Non Non Oui Non Non Non Non
Informix Non Oui Non 9 Non Non Oui Oui Non 7.31 Non
InstantDB Non Non Non Non Non Non 3.26 Oui Non 3.14 Non
Interbase Non Non Non 6 Non Non Non Oui Non Non Oui
JDataStore Non Non Non Oui
MS Access Non Oui Non Non Non Non Oui Oui Non Non Non
MS FoxPro Non Non Oui
MS SQL Server 8.0 Oui 7.0 7.0 Non Oui 8.0 Oui Non Oui 7.0 SP4, 2000 SP2
McKoi Non Non Non Non Non Non Non Oui Non Non Non
MySQL Non Oui Non 3.23.49, 4.0.12 Non Non 3.23 Oui Non Oui 3.23.55-max
Oracle 8.1, 9.2 Oui 8.1.7 8.1.7 Non Oui 8.1-9.1 Oui Non 8.1.7 8.1.7.4.0
Pervasive SQL Non Non Non Non Non Non Non Non Non Oui Non
PointBase Non Oui Non 4.5 Non Non 4.2 Oui Non Non Non
PostgreSQL Non Non 7.1.3 7.3.1 Non Non 7.2.1 Oui Non Oui 7.2.1, 7.3.1, 7.3.2-1
Progress Non Non Non Non Non Non Non Non Non Oui Non
SAP DB Non Non Non 7.3 Non Non Non Non Oui 7.4.3 Build 010-120-035-462
Sybase Non Oui Non 11.9.2, 12.5 Non Non 12.5 Oui Non Oui Non
Unisys Non Non Non Non Non Non Non Oui Non Non Non Non
SGBDO Versant Non Non Non Non Non Oui Non Oui Oui Non Non Oui Non
Autre Propriétaire Non Non Non Non FDB ObjectDB
Autre Via dictionnaire fourni Non
XML Non Non Non Non Non Oui Non Oui
Instance Identité Durable Datastore Oui Oui Oui Oui Oui Oui Oui Oui
Applicative Oui Oui Oui Génération automatique de classe Non Non Oui Génération automatique de classe et de valeurs Non
Personnalisé Oui OidProvider
SGBD Champs identité, séquences, tables
Non durable Oui Non Non Non Non Oui Non
Changement Non Non Non Non Non Oui Non
Transactionnelles Transitoire Oui Oui Oui Oui
Persistante Oui Oui Oui Oui
Non transactionnelles Transitoire Oui Oui Oui
Persistante Oui Oui Oui Oui
Détachement/attachement Oui
Collections Nulles Oui Oui Non Non Oui Oui Non Oui
Tableaux Array[] Non Oui Oui Oui Oui Oui Oui Oui Oui Non
int[][], byte[][] Oui
int[][][], byte[][][] Oui
Dictionnaires Map Non Non Oui Oui Oui Oui Oui Oui Oui Oui
HashSet Oui Oui Oui Oui Oui Oui Oui Oui Non Non
HashMap Non Oui Oui Oui Non Oui Oui Oui Oui Non
TreeMap Non Oui Oui Non Non Non Oui Oui Oui Non
Hashtable Non Oui Oui Oui Oui Non Oui Oui Oui Non
Listes List Oui Oui Oui Oui Oui Oui Oui Oui Oui Non
ArrayList Oui Oui Oui Oui Oui Oui Oui Oui Oui Non
LinkedList Oui Oui Oui Oui Oui Oui Oui Oui Oui Non
Vector Oui Oui Oui Oui Oui Non Oui Oui Oui Non
Ensembles Set Oui Oui Oui Oui Oui Non Oui Oui Oui Oui
TreeSet Oui Oui Oui Non Oui Non Oui Non Oui Non
Développement Outils Mapping graphique Oui Non LiDO Studio JDO Explorer
Navigateur de modèle Non Navilis
Génération de schéma Oui Oui Oui DefineSchema, LPM schemagen
Retro- ingénierie de schéma Oui Pour XML ClassGenerator
Evolution de schéma Oui DefineSchema
Intégration IDE JBuilder Non Non 6 Oui Non Oui
TCC Non Non Non Non Lido 1.4 Oui
Sun ONE Studio Non Oui Oui Non Non
Eclipse / WSAD Non Oui Non Oui 2.x (LiDO Studio) Non
API Métadonnées Oui
Javadoc XDocLet JDODocLet
Taglib Non Oui
Suppressions en cascade Non Extension dependent du champ collection Extensions dependent, value-dependent, key-dependent et element-dependent Automatique pour collections en reverse , sinon en partie via l'extension sql-delete-behavior (sinon InstanceCallback) Extension cascade-delete du champ
Mapping Classe Plusieurs tables Non Oui Extension table du champ collection. Clés primaires différentes supportées Oui
Héritage Vertical Oui Non Oui <inheritance-strategy type="per-class"> Extension inheritance d'une classe
Horizontal Non Non Oui <inheritance-strategy type="per-concrete-class"> (défaut) Non
Autre Possible
Plat / Filtré Non Oui Oui Oui <inheritance-strategy type="per-hierarchy"> Oui
Relations 1-1 Oui Oui
Inversée Oui
1-n Table de liaison Oui Oui Oui Oui Oui (défaut)
Inversée (non ordonnée) Extension inverse du champ collection Extension inverse du champ collection <associate field="f" to-column="c">
n-m Table de liaison Oui Oui Oui Oui Oui
Inversée (non ordonnée) Oui Extension inverse du champ collection Oui Non
Champs Convertisseurs Oui JdbcConverter custom-mapping=ClassMapping, extension column-length CustomMapper, CustomizableMapper
DataStore Multiples Non Extension datastore d'une classe Non Non

Limitations

Références