Object Relational Mapping - Java Persistence API
Java Persistence API, les aspects techniques (1/2)
Composition
J.P.A est composé de cinq parties :
- L'unité de persistance ;
- Le modèle des objets du domaine ;
- Les méta-données ;
- API ;
- Requêtes.
L'unité de persistance
Il s'agit de l'environnement dans lequel la couche de persistance va être utilisée. Il est ainsi nécessaire de spécifier les éléments suivants :
- le nom de l'unité, on veillera à donner un nom unique ;
- le Provider de l'implémentation J.P.A, il s'agit de la classe fournissant l'accès aux services de persistance ;
- la liste des classes correspondant aux données stockées (inutile dans un environnement EJB3) ;
- les paramètres de connexion à la base de données ;
- la configuration éventuelle de la couche de persistance.
L'ensemble de cette configuration s'effectue à l'aide d'un fichier XML. On notera cependant une différence entre les architectures 2-Tiers et 3-Tiers (avec conteneur d'EJB3). En effet on déportera une partie de la configuration sur le serveur d'application dans le cas d'une architecture 3-Tiers, dans le but de faciliter l'administration et d'offrir plus de souplesse.
Cas d'une architecture 2-Tiers
Voici un exemple de fichier de configuration pour une architecture 2-Tiers :
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="xposejpa-javadb"> <provider>oracle.toplink.essentials.PersistenceProvider</provider> <class>fr.umlv.etudiant.acollign.jpa.bom.Person</class> ... <properties> <property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver" /> <property name="toplink.jdbc.url" value="jdbc:derby://localhost/xpose" /> <property name="toplink.jdbc.user" value="xpose" /> <property name="toplink.jdbc.password" value="china" /> <property name="toplink.ddl-generation" value="create-tables" /> </properties> </persistence-unit> </persistence>
Le lecteur attentif aura remarqué que les noms des propriétés présentées ici sont suffixées de toplink. Il est important de noter que les noms des propriétés sont propres aux implémentations.
Il est ainsi possible de faire cohabiter les configurations pour différentes implémentations pour une même unité de persistance.
Cas d'une architecture 3-Tiers
Voici un exemple de fichier de configuration pour une architecture 3-Tiers :
<persistence version="1.0"> <persistence-unit name="xpose-jpa"> <jta-data-source>jdbc/xpose</jta-data-source> </persistence-unit> </persistence>
On constate que la configuration est bien plus légère dans ce fichier. Cela s'explique par le fait que l'ensemble de la configuration de la base de données est effectué dans la datasource.
Une datasource correspond à une connexion ou un ensemble de connexions (pool) configuré sur le serveur d'application. Elle a ainsi l'avantage d'être partagée par un ensemble d'applications entreprises.
Les objets du domaine, les entités
Définition
Les objets du domaine correspondent à l'ensemble des objets métiers que l'application traite. Ainsi dans l'exemple on aura les objets suivants :
- Adresse correspond aux coordonnées postales ;
- Etudiant correspond à une personne suivant un cursus scolaire, il possède une adresse ;
- Promotion correspond à un ensemble d'étudiants.
Les entités
Les entités sont la simple traduction Java et plus précisément J.P.A des objets du domaine sus-cité. On trouvera ainsi quatre entités pour notre exemple :
Person
;Address
;Student
;Stream
.
L'entité Person n'est pas définie clairement dans les objets du domaine, cependant sa création permet de tirer pleinement partie de la puissance de la programmation objet.
Un Javabean
Pour créer une entité, il faut dans un premier temps écrire un objet JavaBean. On trouvera souvent dans la littérature le nom de Plain Old Java Object ou son acronyme P.O.J.O. Pour rappel un JavaBean possède les caractéristiques suivantes :
- un constructeur par défaut publique ;
- des accesseurs pour chaque champs :
-
setBooleanField
/isBooleanField
dans le cas des membres de type booléen ;setField
/getField
dans les autres cas.
- la propagation d'événements dans le cas de modification des champs.
La gestion des événements n'est pas requise dans le cadre de J.P.A.
Voici le P.O.J.O correspondant à l'objet du domaine Person :
public class Person { private String lastname; private String firstname; private Address address; public Person() {} public Person(String lastname, String firstname, Address address) { this.firstname = firstname; this.lastname = lastname; this.address = address; } public String getLastname() { return lastname; } public void setLastname(String lastname) { this.lastname = lastname; } ... }
Une clé primaire
Pour identifier notre entité au sein de la base de données, il est obligatoire de définir un identifiant unique, la clé primaire. La clé primaire peut être de deux formes différentes :
- simple, elle est constituée d'un simple champ de type primitif ;
- composée, elle est constituée de plusieurs champs de type primitif, d'un ou plusieurs objets.
Une fois la clé primaire définie, il est nécessaire de mettre en place la génération de cette dernière. Pour cela, la norme J.P.A dispose d'un certain nombre de règles :
- AUTO, la clé est générée au libre choix du provider de persistance ;
- TABLE, la valeur d'une table est utilisée pour toutes les entités ;
- SEQUENCE, la fonction SEQUENCE de la base de données sous-jacente est utilisée ;
- IDENTITY, la fonction IDENTITY de la base de données sous-jacente est utilisée.
Les deux méthodes SEQUENCE et IDENTITY ne sont pas toujours portables et peuvent ainsi nuire à la migration et l'évolution des entités et de l'application.
Les méta-données
Les méta-données permettent de réaliser l'association entre les membres des objets et leurs représentations au sein de la base de données relationnelles. Il s'agit de la mise en place d'un mapping à proprement parler.
La spécification de J.P.A étant basée sur la version 5 de la plate-forme Java, on trouve naturellement l'usage des @Annotations dans la description des méta-données. On pourra cependant préférer l'usage d'un fichier de description XML.
Usage des annotations
On va donc augmenter notre code d'un ensemble d'annotations. Il existe de nombreuses annotations dont voici les plus couramment utilisées :
@Entity
, cette annotation est obligatoire. Elle s'applique à une classe et la marque comme entité ;@Id
, annote la clé primaire ;@GeneratedValue
, annote la clé primaire. Elle peut prendre un paramètre type qui permettera de définir la méthode de génération utilisée (voir la section ci-dessus) ;@Column
, annote un champ et permet de définir le nom de la colonne correspondante dans la base de données.
Voici un exemple de mise en place des méta-données par
annotation sur la classe Person
:
@Entity public class Person { @Id @GeneratedValue private long id; @Column(name="LASTNAME") private String lastname; private String firstname; private Address address; public Person() {} public String getLastname() { return lastname;} public void setLastname(String lastname){ this.lastname = lastname; } ... }
Usage du fichier orm.xml
Une autre approche consiste à utiliser un fichier de description XML ; cette approche est celle utilisée dans la norme EJB2 qui est basée sur Java 1.4 :
<?xml version="1.0" encoding="UTF-8" ?> <entity-mappings version="1.0"> <entity class="fr.umlv.etudiant.acollign.jpa.bom.Person"> <attributes> <id name="id"> <generated-value /> </id> <basic name="lastname"> <column name="LASTNAME" /> </basic> </attributes> </entity> </entity-mappings>
Usage simultanné des deux méthodes
Les deux méthodes peuvent être utilisées simultanément, cette technique s'appelle le merged meta-data. Cependant, une telle utilisation peut apporter certaines confusions. En effet, le développeur plongé dans son code, peut croire en la configuration définie par les annotations alors que celles-ci sont potentiellement écrasées par un fichier de description annexe.
On notera une utilisation courante qui est de définir l'environnement de développement par les annotations puis lors du passage en production, une réécriture de la configuration est effecutée grâce au fichier de description.
On peut constater dans l'exemple donné ci-dessus que tous les champs ne sont pas annotés. En l'absence de méta-données, un comportement par défaut est appliqué. Ce principe s'applique à la fois aux paramètres optionnels et aux annotations.
Pour exemple, le mapping d'un champ non renseigné se fera sur la colonne ayant le même nom que le champ lui même.