Persistance des objets et Java

La durabilité des objets, ou persistance , est le terme que vous entendez souvent utilisé en conjonction avec le problème du stockage des objets dans des bases de données. La persistance est censée fonctionner avec l'intégrité transactionnelle et, en tant que telle, elle est soumise à des conditions strictes. (Consultez la section Ressources de cet article pour plus d'informations sur le traitement des transactions.) En revanche, les services linguistiques proposés via les bibliothèques et les packages de langage standard sont souvent exempts de contraintes transactionnelles.

Comme nous le verrons dans cet article, les preuves suggèrent que la persistance Java simple proviendra probablement du langage lui-même, tandis que des fonctionnalités de base de données sophistiquées seront proposées par les fournisseurs de bases de données.

Aucun objet n'est une île

Dans le monde réel, vous trouvez rarement un objet qui n'a pas de relations avec d'autres objets. Les objets sont des composants de modèles d'objets . La question de la durabilité des objets transcende la question de la durabilité et de la distribution des modèles d'objets une fois que nous faisons le constat que les objets sont interconnectés en vertu de leurs relations les uns avec les autres.

L'approche relationnelle du stockage des données a tendance à agréger les données par type. Les lignes d'une table représentent l'agrégat physique d'objets du même type sur le disque. Les relations entre les objets sont ensuite représentées par des clés qui sont partagées entre de nombreuses tables. Bien que grâce à l'organisation de la base de données, les bases de données relationnelles permettent parfois aux tables susceptibles d'être utilisées ensemble d'être colocalisées (ou mises en cluster ) dans la même partition logique, telle qu'un segment de base de données, elles ne disposent d'aucun mécanisme pour stocker les relations d'objets dans la base de données. Par conséquent, afin de construire un modèle objet, ces relations sont construites à partir des clés existantes au moment de l'exécution dans un processus appelé jointures de table . Il s'agit de la même propriété bien connue des bases de données relationnelles appeléesindépendance des données . Presque toutes les variantes de bases de données d'objets offrent un mécanisme pour améliorer les performances d'un système qui implique des relations d'objets complexes par rapport aux bases de données relationnelles traditionnelles.

Pour interroger ou pour naviguer?

En stockant des objets sur disque, nous sommes confrontés au choix de co-localiser des objets associés pour mieux accueillir l'accès à la navigation, ou de stocker des objets dans des collections de type table qui regroupent les objets par type pour faciliter l'accès basé sur les prédicats (requêtes), ou les deux. . La co-localisation d'objets dans le stockage persistant est un domaine où les bases de données relationnelles et orientées objet diffèrent largement. Le choix de la langue de requête est un autre domaine à considérer. Le langage SQL (Structured Query Language) et ses extensions ont fourni aux systèmes relationnels un mécanisme d'accès basé sur les prédicats. Object Query Language (OQL) est une variante d'objet de SQL, normalisée par ODMG, mais la prise en charge de ce langage est actuellement limitée. Les méthodes polymorphes offrent une élégance sans précédent dans la construction d'une requête sémantique pour une collection d'objets. Par exemple,imaginez un comportement polymorphe pouracccountappelé isInGoodStanding. Il peut renvoyer la valeur booléenne true pour tous les comptes en règle et false dans le cas contraire. Imaginez maintenant l'élégance d'interroger la collection de comptes, où inGoodStandingest implémentée différemment en fonction des règles métier, pour tous les comptes en règle. Cela peut ressembler à quelque chose comme:

setOfGoodCustomers = setOfAccounts.query(account.inGoodStanding());

Bien que plusieurs des bases de données d'objets existantes soient capables de traiter un tel style de requête en C ++ et Smalltalk, il leur est difficile de le faire pour des collections plus volumineuses (par exemple 500+ gigaoctets) et des expressions de requête plus complexes. Plusieurs sociétés de bases de données relationnelles, telles qu'Oracle et Informix, proposeront bientôt une autre syntaxe basée sur SQL pour atteindre le même résultat.

Persistance et type

Un aficionado du langage orienté objet dirait que la persistance et le type sont des propriétés orthogonales d'un objet; en d'autres termes, les objets persistants et transitoires du même type peuvent être identiques car une propriété ne doit pas influencer l'autre. La vue alternative soutient que la persistance est un comportement pris en charge uniquement par les objets persistants et que certains comportements peuvent s'appliquer uniquement aux objets persistants. La dernière approche nécessite des méthodes qui ordonnent aux objets persistants de se stocker et de se récupérer à partir du stockage persistant, tandis que la première offre à l'application une vue transparente de l'ensemble du modèle d'objet - souvent en étendant le système de mémoire virtuelle.

Canonisation et indépendance linguistique

Les objets du même type dans une langue doivent être stockés dans un stockage persistant avec la même disposition, quel que soit l'ordre dans lequel leurs interfaces apparaissent. Les processus de transformation d'une mise en page d'objet vers ce format commun sont collectivement connus sous le nom de canonisation de la représentation d'objet. Dans les langages compilés avec un typage statique (pas Java), les objets écrits dans le même langage, mais compilés sous différents systèmes, doivent être représentés de manière identique dans le stockage persistant.

Une extension de la canonisation concerne la représentation d'objets indépendante du langage. Si les objets peuvent être représentés de manière indépendante du langage, il sera possible pour différentes représentations du même objet de partager le même stockage persistant.

Un mécanisme pour accomplir cette tâche consiste à introduire un niveau supplémentaire d'indirection via un langage de définition d'interface (IDL). Les interfaces de base de données d'objets peuvent être créées via l'IDL et les structures de données correspondantes. L'inconvénient des liaisons de style IDL est double: Premièrement, le niveau supplémentaire d'indirection nécessite toujours un niveau supplémentaire de traduction, ce qui a un impact sur les performances globales du système; deuxièmement, il limite l'utilisation des services de base de données qui sont propres à des fournisseurs particuliers et qui peuvent être utiles aux développeurs d'applications.

Un mécanisme similaire consiste à prendre en charge les services d'objets via une extension du SQL. Les vendeurs de bases de données relationnelles et les petits fournisseurs d'objets / relationnels sont les partisans de cette approche; Cependant, il reste à voir dans quelle mesure ces entreprises réussiront à façonner le cadre du stockage d'objets.

Mais la question demeure: la persistance des objets fait-elle partie du comportement de l'objet ou s'agit-il d'un service externe offert aux objets via des interfaces séparées? Qu'en est-il des collections d'objets et des méthodes pour les interroger? Les approches relationnelle, relationnelle étendue et objet / relationnelle ont tendance à préconiser une séparation entre le langage, tandis que les bases de données objets - et le langage Java lui-même - considèrent la persistance comme intrinsèque au langage.

Persistance Java native via la sérialisation

La sérialisation d'objets est le mécanisme spécifique au langage Java pour le stockage et la récupération des objets Java et des primitives dans les flux. Il est intéressant de noter que bien que les bibliothèques tierces commerciales pour la sérialisation d'objets C ++ existent depuis un certain temps, C ++ n'a jamais proposé de mécanisme natif pour la sérialisation d'objets. Voici comment utiliser la sérialisation de Java:

// Ecriture de "foo" dans un flux (par exemple, un fichier)

// Étape 1. Créer un flux de sortie

// c'est-à-dire créer un bucket pour recevoir les octets

FileOutputStream out = new FileOutputStream ("fooFile");

// Étape 2. Créer ObjectOutputStream

// c'est-à-dire créer un tuyau et mettre sa tête dans le seau

ObjectOutputStream os = nouvel ObjectOutputStream (sortie)

// Étape 3. Écrire une chaîne et un objet dans le flux

// c'est-à-dire que le flux s'écoule dans le compartiment

os.writeObject ("toto");

os.writeObject (nouveau Foo ());

// Étape 4. Videz les données vers leur destination

os.flush ();

La Writeobjectméthode sérialise foo et sa fermeture transitive - c'est-à-dire tous les objets qui peuvent être référencés à partir de foo dans le graphique. Dans le flux, une seule copie de l'objet sérialisé existe. Les autres références aux objets sont stockées sous forme de poignées d'objet pour économiser de l'espace et éviter les références circulaires. L'objet sérialisé commence par la classe suivie des champs de chaque classe dans la hiérarchie d'héritage.

// Lecture d'un objet à partir d'un flux

// Étape 1. Créer un flux d'entrée

FileInputStream in = new FileInputStream ("fooFile");

// Étape 2. Créer un flux d'entrée d'objet

ObjectInputStream ins = nouvel ObjectInputStream (in);

// Étape 3. Vous devez savoir ce que vous lisez

String fooString = (Chaîne) ins.readObject ();

Toto toto = (toto) s.readObject ();

Sérialisation et sécurité des objets

Par défaut, la sérialisation écrit et lit les champs non statiques et non transitoires du flux. Cette caractéristique peut être utilisée comme mécanisme de sécurité en déclarant des champs qui ne peuvent pas être sérialisés comme transitoires privés. Si une classe peut ne pas être sérialisée du tout, writeObjectet des readObjectméthodes doivent être implémentées pour throw NoAccessException.

Persistance avec intégrité transactionnelle: présentation de JDBC

Inspiré du SQL CLI (Client Level Interface) de X / Open et des abstractions ODBC de Microsoft, la connectivité de base de données Java (JDBC) vise à fournir un mécanisme de connectivité de base de données indépendant du système de gestion de base de données (SGBD) sous-jacent. doivent prendre en charge au moins l'API d'entrée de gamme ANSI SQL-2, ce qui donne aux fournisseurs d'outils et aux applications tiers une flexibilité suffisante pour accéder aux bases de données.

JDBC est conçu pour être cohérent avec le reste du système Java. Les fournisseurs sont encouragés à écrire une API qui est plus fortement typée que ODBC, ce qui permet une plus grande vérification de type statique au moment de la compilation.

Voici une description des interfaces JDBC les plus importantes:

  • java.sql.Driver.Manager gère le chargement des pilotes et prend en charge les nouvelles connexions à la base de données.

  • java.sql.Connection représente une connexion à une base de données particulière.

  • java.sql.Statement agit comme un conteneur pour exécuter une instruction SQL sur une connexion donnée.

  • java.sql.ResultSet contrôle l'accès à l'ensemble de résultats.

Vous pouvez implémenter un pilote JDBC de plusieurs manières. Le plus simple serait de construire le pilote comme un pont vers ODBC. Cette approche est la mieux adaptée aux outils et applications qui ne nécessitent pas de hautes performances. Une conception plus extensible introduirait un niveau supplémentaire d'indirection vers le serveur SGBD en fournissant un pilote de réseau JDBC qui accède au serveur SGBD via un protocole publié. Le pilote le plus efficace, cependant, accèderait directement à l'API propriétaire du SGBD.

Bases de données d'objets et persistance Java

Un certain nombre de projets en cours dans l'industrie offrent une persistance Java au niveau de l'objet. Cependant, au moment d'écrire ces lignes, PSE (moteur de stockage persistant) et PSE Pro d'Object Design sont les seuls packages de base de données entièrement basés sur Java et orientés objet disponibles (du moins, à ma connaissance). Consultez la section Ressources pour plus d'informations sur PSE et PSE Pro.

Le développement Java a conduit à un départ du paradigme de développement traditionnel pour les éditeurs de logiciels, notamment dans la chronologie du processus de développement. Par exemple, PSE et PSE Pro sont développés dans un environnement hétérogène. Et comme il n'y a pas d'étape de liaison dans le processus de développement, les développeurs ont pu créer divers composants fonctionnels indépendants les uns des autres, ce qui se traduit par un code orienté objet meilleur et plus fiable.

PSE Pro a la capacité de récupérer une base de données corrompue à partir d'une transaction annulée causée par une défaillance du système. Les classes responsables de cette fonctionnalité ajoutée ne sont pas présentes dans la version PSE. Aucune autre différence n'existe entre les deux produits. Ces produits sont ce que nous appelons des «dribblewares» - des versions de logiciels qui améliorent leurs fonctionnalités en intégrant de nouveaux composants. Dans un avenir pas si lointain, le concept d'achat de gros logiciels monolithiques deviendrait une chose du passé. Le nouvel environnement commercial dans le cyberespace, associé à l'informatique Java, permet aux utilisateurs d'acheter uniquement les parties du modèle objet (graphique d'objets) dont ils ont besoin, ce qui se traduit par des produits finaux plus compacts.

PSE fonctionne en post-traitement et en annotant les fichiers de classe après leur création par le développeur. Du point de vue de PSE, les classes d'un graphe d'objets sont soit persistantes, soit persistantes. Les classes à capacité persistante peuvent persister elles-mêmes tandis que les classes à capacité persistante peuvent fonctionner sur des objets persistants. Cette distinction est nécessaire car la persistance peut ne pas être un comportement souhaité pour certaines classes. Le post-processeur de fichier de classe apporte les modifications suivantes aux classes: