Au sein d'une architecture Java EE, les EJB sont utilisés pour créer les services. Cette technologie ne s'arrête cependant pas à cette couche, mais permet aussi de créer l'abstraction de l'accès aux données. Ce sont les EJB Entités qui remplissent cette fonction.
Tout comme les beans sessions, entre le client et la logique métier, les beans entités forment la passerelle entre la logique applicative et les sources de données. Ils offrent une abstraction quasi complète du stockage des données, permettant à l'application de rendre persistantes ou de charger des données de manière totalement transparente.
Les beans entités ont été créés pour simplifier la gestion des données au niveau d'une application, mais aussi pour faciliter la sauvegarde en base de données. Plus concrètement, ces beans entités vous permettent de prendre en charge la persistance des données de votre application dans une ou plusieurs des sources de données, tout en gardant les relations entre celles-ci.
Ces composants établissent donc la relation entre votre application et vos bases de données. Contrairement aux beans sessions, les données d'un bean entité sont conservées, même après l'arrêt de l'application.
Les données de l'application sont typiquement : des utilisateurs, des factures, des produits, des adresses, des informations sur une image, ... Dans le monde de Java, et plus généralement dans le monde objet, il est commun d'utiliser des classes pour chacun des types d'objets utilisés. On parle souvent d'objet métier ou entité (Entity) pour représenter les caractéristiques de ces objets.

La liaison entre les données et l'application par un objet s'appelle le mapping (relier). On parle de mapping Objet/Relationnel lorsque nous connectons, de cette manière, une base de donnée avec une application objet.
Attention : l'utilisation des beans entités permet de représenter une entité de l'application et non une fonctionnalité. Par exemple, Photo est un bean entité, mais StockerPhoto serait plutôt un bean session ; une photo étant voué à rester persistante (garder un état particulier pendant un bon moment), alors que le stockage effectif de la photo est une opération, qui par définition est de courte durée.
Contrairement au bean session, les données du bean entité ont donc généralement une durée de vie longue ; elles sont enregistrées et stockées dans des systèmes de persistance (base de données).
L'objet à rendre persistant, via un mapping Objet/Relationnel, correspond à une table de la base de données.
Chaque propriété de cet objet est liée à un champ de la table.
Chaque instance de cet objet représente généralement un enregistrement (une ligne) de la table.
Toutefois, il est possible qu'un bean entité soit réparti sur plusieurs tables. A l'inverse, une table dans une base de données regroupe un ensemble de champs. Ces champs représentent soit des informations directement liées à la table, soit des liens vers d'autres tables. Toutes ces propriétés ne sont pas pour autant des propriétés directes de l'objet en question.
Il existe quatre relations possibles entre les beans entités :
Les beans entités sont exécutés dans le conteneur EJB, qui apporte aux développeurs de nombreux services facilitant le travail du développeur.
Le principal service est, bien entendu, la gestion de la persistance qui gère l'ensemble des accès aux données dans la mémoire ou dans les sources de données. L'utilisation de ce service, et donc des beans entités, procure de multiples avantages comparés à l'accès direct à la base de données. Cette solution apporte un mécanisme simple pour l'accès et la modification des données. En effet, il est plus facile, pour modifier le prénom d'un utilisateur au sein de votre application, d'appeler la méthode Utilisateur.setPrénom() que d'exécuter une requête SQL brute. De plus, les beans entités étant standardisés, votre code est plus clair et plus facilement réutilisable (la définition d'un utilisateur se retrouve dans la majorité des applications).
Un bean entité étant un EJB, il hérite des services tels que la gestion des transactions, de la sécurité... ainsi que de nombreux outils de développement optimisés pour leur création.
Partie intégrante du framework Java EE 5, l'unité de persistance est la boîte noire qui permet de rendre persistants les beans entités. Plus qu'un simple fournisseur de persistance, celle-ci va permettre aux développeurs d'optimiser leurs applications selon la gestion de leurs beans entités.
L'unité de persistance est l'élément clé de la gestion des beans entités au sein d'une application. Effectivement, les beans entités étant des objets simples, ils doivent être managés par une unité de persistance qui permet d'intégrer l'utilisation de ces beans entités au sein d'applications Java EE et même au travers de Java SE (sans passer par un bean session).
Bien que la mise en place des beans entités EJB 3 au sein d'une application soit assez simple, la persistance des informations doit être configurée pour permettre de sauvegarder les données dans la ou les source(s) de données.
La solution adoptée pour la gestion de ces beans entités est l'utilisation d'une unité de persistance (ou contexte de persistance) qui prend en charge la sauvegarde des informations dans la source de données, de manière autonome (indépendamment des beans entités).
Une unité de persistance est caractérisée par les points suivants :
Une unité de persistance étant vouée à être enregistrée dans une source de données, le rôle de l'unité de persistance est :
Il est possible d'avoir un bean entité attaché ou détaché de l'unité de persistance. Quand un bean entité est attaché à un contexte de persistance, les modifications appliquées à l'objet sont alors automatiquement synchronisées avec la base de donnés, via le gestionnaire d'entité (EntityManager). A l'inverse, un bean entité est dit détaché lorsqu'il n'a aucun lien avec le gestionnaire d'entité (EntityManager).
Auparavant, les beans entités se cantonner uniquement au sein de Java EE. Depuis la version EJB 3, l'utilisation des beans entités n'est plus fermée au monde Java EE ou à un conteneur EJB. Vous pouvez désormais déployer vos entités dans de nombreuses applications :
Ce dernier point est, sans doute, le plus appréciable. En effet, il vous permet d'inclure facilement et simplement un système de persistance de données à une application Java SE.
La nouvelle spécification définit un fichier de description qui regroupe l'ensemble des informations de persistance. Ce fichier décrit les beans entités qui vous seront utiles pour l'application, l'emplacement de la base de données, ainsi que le fournisseur de persistance qui correspond généralement au serveur d'applications.
Ce fichier sera alors lu par le conteneur d'EJB lors du déploiement de l'application (EAR, EJB-JAR, WAR, ...). Au moment du déploiement le conteneur vérifie la description proposée et contrôle la concordance avec les éléments qu'il connaît : les beans entités sont-ils présents dans le serveur d'application ? la base de données est-elle accessible ? etc. Si tout se passe bien, le conteneur crée alors une instance de persistance avec les paramètres demandés. Cette unité de persistance sera ainsi toujours opérationnelle et active à tout instant.
Vous devez nommer ce fichier de description de persistance : persistence.xml et placer ce dernier dans le répertoire <META-INF> à la racine du projet.
.

Attention : vous devez nommer correctement le fichier persistence.xml. Si le nom ne correspond pas, le conteneur n'associera aucun contexte de persistance à l'application.
Voici deux exemples de fichier de description de persistance qui seront d'ailleurs utilisés dans les chapitres qui suivent :
| persistence.xml |
|---|
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="Photos-ejbPU"> <jta-data-source>photos</jta-data-source> <properties> <property name="toplink.ddl-generation" value="drop-and-create-tables"/> </properties> </persistence-unit> </persistence> |
| persistence.xml |
|---|
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="Photos-ejbPU" transaction-type="RESOURCE_LOCAL"> <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider> <class>photos.Photo</class> <properties> <property name="toplink.jdbc.user" value="manu"/> <property name="toplink.jdbc.password" value="manu"/> <property name="toplink.jdbc.url" value="jdbc:derby://localhost:1527/photos"/> <property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/> <property name="ddl-generation" value="drop-and-create-table" /> </properties> </persistence-unit> </persistence> |
Ce fichier est un document XML. Comme tout document XML, il doit respecter un certain canevas. Ici, l'élément racine est la balise <persistence>. La balise racine ne contient ensuite qu'une ou plusieurs balises <persistence-unit>. Cette dernière décrit comment mettre en place une unité de persistance. Il est ainsi possible d'avoir plusieurs unités de persistance dans le cas, par exemple, où notre application utilise plusieurs bases de données (une unité de persistance par base de données).
<persistence-unit> : déclare une unité de persistance.
- L'attribut name affecte un nom unique à cette unité dans votre application. Le nom est utilisé pour identifier l'unité de persistance lors de son utilisation avec les annotations @PersistenceContext et @PersistenceUnit pour la création, respectivement, d'un EntityManager ou d'un EntityManagerFactory (voir plus loin dans notre étude).
- L'attribut type définit si l'unité de persistance est gérée et intégrée dans une transaction Java EE (JTA) ou si vous souhaitez gérer de façon manuelle les transactions (RESOURCE_LOCAL) via l'EntityManager (ou l'EntityManagerFactory). La valeur par défaut en environnement Java EE est JTA et RESOURCE_LOCAL en environnement Java SE. Des détails concernant ce type sont donnés plus loin.
Je le rappelle, pensez bien qu'il est nécessaire de définir plusieurs unités de persistance si vous souhaitez utiliser plusieurs sources de données.
.
Voici maintenant une description des balises qui spécifie les paramètres de l'unité de persistance. Vous devez donc placer ces balises à l'intérieur d'une balise <persistence-unit> :
Une unité de persistance mappe un ensemble de beans entités. Par défaut, dans un environnement Java EE, le conteneur analyse l'ensemble des classes du fichier *.jar contenant le fichier persistence.xml. Pour définir manuellement les classes à mapper, vous pouvez utiliser les balises suivantes :
Finalement, l'ensemble des classes à mapper est défini par l'ensemble des :
Généralement, vous n'avez pas à utiliser les balises <jar-file> et <class> sauf si vous souhaitez mapper une classe à plusieurs unités de persistance.
.
Le fait de proposer un fichier de description est un très gros atout. Il vous est possible, à tout moment, de changer votre serveur de base de données ou même de serveur d'applications, sans que ayez à recompiler et à reconstruire votre application d'entreprise. Ce fichier sert ainsi de fichier de configuration.
Après avoir découvert le rôle d'un bean entité et comment le rendre persistant, nous allons mettre en pratique ces différentes définitions. Ainsi, nous verrons comment construire un bean entité et comment le gérer au travers de l'unité de persistance. L'unité de persistance, elle-même, sera mis en oeuvre suivant deux cas d'utilisation. Dans un premier temps, je propose de l'implémenter au travers d'un bean session. Dans un deuxième temps, nous ferons un accès direct au bean entité, sans passé cette fois-ci par un bean session. Nous verrons ainsi comment réaliser un mapping Objet/Relationnel directement depuis le client, c'est-à-dire en utilisant uniquement Java SE. Notre étude se fera donc en trois phases.
Afin de visualiser l'intérêt d'utiliser des beans entités, je vous propose de faire la démonstration au travers d'un projet. Ce projet consiste à réaliser un serveur de photos qui me permettra de stocker un ensemble de clichés numériques qui sera bien sûr possibles de consulter par la suite. Je voudrais pouvoir m'y retrouver dans l'ensemble des photos qui seront archivées. Ainsi, depuis un poste quelconque sur le réseau local, l'utilisateur devra identifier impérativement chacune des photos qu'il archivera dans le serveur.

Dans notre exemple, la base de données se situe avec le serveur d'application Java EE. Elle peut se situer, bien entendu, sur une machine distincte. Le fonctionnement demeure totalement identique.
Vous allez le découvrir, la gestion des bases de données avec les beans entités sont très simples à mettre en oeuvre puisque les tables sont automatiquement créées au moment même où vous déployez votre application d'entreprise (contenant des beans entités) dans votre serveur d'applications. Nous devons quand même réaliser une petite opération, c'est effectivement de créer la base de données, qui va accueillir les différentes tables prévues par l'application d'entreprise, et surtout qu'elle soit référencée par le service de nommage JNDI.
Je vous propose de voir comment créer cette base de données en utilisant le serveur d'applications Glassfish avec sa base de données interne Derby et au moyen de l'environnement NetBeans.
La première chose à faire, c'est bien entendu de créer la base de données que nous appelerons photos. Il suffit d'aller dans le menu Tools pour cela, et de suirvre le scénario proposé ci-dessous :

Il faut maintenant que cette base de données soit référencée par le service de nommage JNDI afin qu'elle puisse être atteinte très facilement. Il faut alors démarrer votre serveur d'application et vous connecter en tant qu'administrateur, et réaliser tous les réglages nécessaires à l'aide de votre navigateur.
Cette phase se déroule en deux étapes. Effectivement, lorsque vous proposer un nom JNDI, il faut que ce nom soit rattaché à la bonne base de données. La première étape consiste donc à mettre en oeuvre un pool de connexion qui permet de spécifier tous les différents paramètres concernant la base de données : nom de la base de données, le loggin de connexion, le nom du serveur, le numéro de service, etc.
Pour cela, vous devez prendre l'option Connection Pools de la rubrique Resources/JDBC.

Une fois que le pool de connexion est mis en place, vous pouvez maintenant choisir le nom JNDI qui sera le seul utilisé dans l'application d'entreprise. Une fois que le choix est fait, vous devez l'associer au pool de connexion qui indique comment se connecter réellement à la base de données.

Pour commencer, lorsque nous devons mettre en place un objet persistant (qui concerve donc ses propriétés), nous devons créer un bean entité qui va le représenter. Comme nous l'avons déjà évoqué, les beans entités sont de simples objets qui sera donc facile de mettre en oeuvre. En réalité, la création d'un bean entité Photo se résume à la création d'une classe Photo, tel que nous le ferions dans une application Java SE, en rajoutant toutefois quelques petites annotations. L'état persistant de l'entité est représenté par les attributs de la classe qui correspondent aux propriétés de l'objet. Ces attributs peuvent être privés (private), protégés (protected) ou non spécifiés. Les clients ne peuvent alors pas accéder directement aux attributs et doivent utiliser les accesseurs (getter ou setter) ou d'autres méthodes métier de la classe.
Cette classe doit cependant respecter certaines règles :
Les beans entités supportent l'héritage, les associations et les requêtes polymorphiques.
.
Nous allons maintenant implémenter le bean entité Photo qui va ainsi permettre le mapping vers la table correspondante :

C'est grâce à l'annotation @Entity que le conteneur reconnait les classes qu'il doit considérer comme beans entités. Cette annotation doit se situer au niveau de la classe. Il est également possible de définir, si besoin, le nom de l'entité via l'attribut name.
@Entity
public class Photo {
ou
@Entity(name = "AutreNom")
public class Photo {
Voici le codage partiel du bean entité Photo :
| photos.Photo.java |
|---|
1 package photos; 2 3 import java.io.Serializable; 4 import javax.persistence.*; 6 7 @Entity 8 public class Photo implements Serializable { 9 private long id; 10 private String genre; 11 private String identification; 12 private int largeur; 13 private int hauteur; 14 private long poids; 15 16 @Id 17 public long getId() { return id; } 18 public void setId(long id) { this.id = id; } ... 34 } |
Le nom utilisé pour définir le bean entité doit être unique dans une application. La valeur par défaut utilisée est le nom de la classe (Photo dans notre exemple). Ce nom est ensuite utilisé pour représenter l'entité dans les requêtes EJB-QL que nous étudierons ultérieurement. Remarquez la présence de l'annotation @Id qui spécifie l'attribut qui possède une valeur unique (correspondant à la clé primaire de la table) que nous justifierons par la suite.
Nous verrons en fin de chapitre le codage complet du bean entité Photo après avoir passé en revue les différentes annotations possibles, afin que la table correspondant à notre entité soit la plus fiable possible.
Un bean entité est lié à une table de la base de données. Cette liaison s'appelle, comme nous l'avons déjà évoqué, le mapping. Par défaut, le bean entité Photo est mappé sur la table qui porte le même nom, c'est-à-dire : "PHOTO". Si pour certaines raisons, vous deviez le mapper sur une autre table, vous devez alors rajouter l'annotation @Table. Cette annotation possède différents attributs :
@Entity
@Table(name = "NomTable")
public class Photo {
Au début, la table n'existe pas. Lorsqu'un objet de la classe Photo est créé, l'unité de persistance va également créer automatiquement cette table et placer le premier enregistrement avec l'ensemble des attributs proposé par cet objet. La seule chose que vous avez à faire en ce qui concerne le serveur de base de données, c'est de créer la base de données qui va accueillir les différentes tables de l'application relatives aux beans entités existant, sans créer ces tables. Ainsi, grâce à ces beans entités, vous n'avez pas a passer beaucoup de temps avec la base de données, même si celle-ci, en finalité, possède beaucoup de tables relatives aux beans entités utilisés par l'application.
Les champs persistants représentent une caractéristique d'un bean entité. Un champ persistant, pour l'entité Photo est, par exemple : genre, identification, hauteur, largeur et poids. N'importe quel champ (attribut de l'objet) non static et non transient, d'un bean entité, est automatiquement considéré comme persistant par le conteneur.
Un jeu d'annotation standard est défini dans la spécification EJB 3. Nous pouvons considérer deux types d'annotaions liés au mapping objet/relationnel :
Ces deux types d'annotations peuvent se placer de deux façons :
Voici un exemple de bean entité Utilisateur où nous spécifions des annotations particulières sur les attributs (les champs), façon "FIELD" :
@Entity public class Utilisateur implements Serializable { public enum Sexe {Masculin, Féminin}; @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Basic // optionnel private String prénom; private String nom; @Column(unique=true) private String login; private String motDePasse; @Enumerated(value=EnumType.STRING) @Column(length=5) private Sexe sexe; @Temporal(TemporalType.DATE) private Date naissance; ... }
Voici le même exemple, mais en utilisant la façon "PROPERTY" :
@Entity public class Utilisateur implements Serializable { public enum Sexe {Masculin, Féminin}; ... @Id @GeneratedValue(strategy = GenerationType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; } @Basic // optionnel public String getPrénom() { return prénom; } public void setPrénom(String prénom) { this.prénom = prénom; } public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } @Column(unique=true) public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getMotDePasse() { return motDePasse; } public void setMotDePasse(String motDePasse) { this.motDePasse = motDePasse; } @Enumerated(value=EnumType.STRING) @Column(length=9) public Sexe getSexe() { return sexe; } public void setSexe(Sexe sexe) { this.sexe = sexe; } @Temporal(TemporalType.DATE) public Date getNaissance() { return naissance; } public void setNaissance(Date naissance) { this.naissance = naissance; } }
En localisant les annotations sur les accesseurs, le conteneur injecte les valeurs via ceux-ci. Ce sont eux qui affectent ces valeurs aux attributs.
.
L'utilisation de l'une ou de l'autre de ces méthodes dépend de ce que vous souhaitez faire avec votre bean entité. Toutefois, il est préférable d'utiliser les accesseurs afin d'avoir le meilleur contrôle des données injectées par le conteneur.
Toutes les propriétés d'un bean entité étant vouées à être rendu persistantes peuvent être paramétrées, selon les besoins du développeur. Bien que le paramétrage par défaut soit souvent suffisant, il est parfois intéressant de préciser telle ou telle spécificité pour une meilleure optimisation. Les annotations dédiées à ce paramétrage permettent de préciser au moteur de persistance les différentes informations relatives au type et aux contraintes des propriétés.
@Basic :
La politique de l'API de persistance est de considérer toute propriété comme un champ persistant. Cela signifie qu'il n'est pas nécessaire d'annoter les propriétés pour les désigner persistantes. Le conteneur considère par défaut que la propriété est annotée avec @Basic avec les valeurs par défaut des attributs suivants :
Pour spécifier qu'une propriété ne doit pas être persistante, il faut annoter son getter avec @Transient (seulement si cette méthode est de la forme getXxx().
.
Les types d'attributs qui sont considérés comme basiques et qui n'ont pas besoin, à priori, d'annotation particulière sont les suivants :
@Lob :
L'annotation @Lob s'avère utile lorsque vous souhaitez stocker des tableaux de bytes (byte[] ou Byte[]) pour représenter le contenu d'un fichier par exemple :
@Lob @Basic(fetch=FetchType.LAZY) public byte[] getFichierImage() { return fichierImage; }
L'annotation @Lob s'applique également sur des propriétés de type java.sql.Clob (Character Large Object) ou java.sql.Blob (Binary Large Object). Nous avons volontairement ajouté le mode paressseux. Effectivement, le contenu de cette propriété risque d'être de grande taille.
@Temporal
De la même manière, les types java.util.Date ou java.util.Calendar utilisés pour définir des propriétés dites temporelles peuvent être paramétrées pour spécifier le format le plus adéquat à sa mise en persistance. Ceci peut être précisé grâce à l'annotation @Temporal qui prend en paramètre en TemporalType (énumération) dont les valeurs sont les suivantes :
@Temporal(TemporalType.DATE) public Calendar getNaissance() { return naissance; } public void setNaissance(Calendar naissance) { this.naissance = naissance; }
@Enumerated
Depuis J2SE 5.0, le langage Java apporte un nouveau type de données : les énumérations. Ce type existe depuis plusieurs années au sein des bases de données. Il est enfin possible de les utiliser dans les beans entités. L'énumération permet de spécifier un ensemble de valeurs possibles pour une propriété. Par exemple, le sexe d'un utilisateur ne peut prendre que deux valeurs possibles : Masculin ou Féminin. La solution idéale dans cette situation est bien entendu l'utilisation de l'énumération. La valeur d'une énumération peut être enregistrée soit via une chaîne de caractères soit via un entier. L'annotation @Enumerated prend en paramètre un objet EnumType qui définit la façon de stocker cette valeur. Les valeurs EnumType.STRING ou EnumType.ORDINAL sont utilisées respectivement pour l'enregistrement dans une chaîne de caractères ou dans un entier.
@Entity public class Utilisateur implements Serializable { public enum Sexe {Masculin, Féminin}; @Enumerated(value=EnumType.STRING) public Sexe getSexe() { return sexe; } public void setSexe(Sexe sexe) { this.sexe = sexe; } }
Bien que les annotations de propriétés couvrent un ensemble important des types utilisés en général dans les applications, celles-ci sont destinées à être utilisées par le gestionnaire de persistance. Ces paramétrages n'ont donc pas forcément toujours l'impact voulu dans la base de données, lorsque celle-ci est générée automatiquement.
D'autres annotations permettent de préciser le paramétrage des colonnes (dans la table relationnelle) liées aux propriétés persistantes. Grâces à celles-ci, il est alors possible de spécifier le type SQL, la longueur du champ, et de nombreuses autres caractéristiques à utiliser pour une propriété persistante.
C'est l'annotation @Column qu'il faudra utiliser pour préciser ces paramétrages SQL. Celle-ci surdéfinit les valeurs par défaut déclarées par la spécification EJB 3. Cette annotation peut s'utiliser conjointement avec les précédentes. Voici une description des attributs, tous optionnels, de l'annotation @Column :
Remarque : les attributs insertable et updatable sont généralement utilisés lorsque la colonne est utilisée par plusieurs propriétés dans une même entité. Cela se produit, par exemple, lorsqu'une colonne est à la fois clé primaire et étrangère.
@Column(updatable = true, name = "prix", nullable = false, precision = 5, scale = 2) public double getPrix() { return prix; }
Les nombreux paramètres par défaut de l'API de persistance offrent un avantage certain au développeur. Celui-ci peut rapidement tester ses entités. Toutefois, il ne doit pas en rester là, mais utiliser les possiblités exposées ici pour optimiser le mapping entre ses entités et les tables de la base de données.
Un bean entité doit impérativement posséder un attribut dont la valeur est unique, dit identificateur unique ou clé primaire. Ce champ permet de différencier chaque objet entité des autres. Cette clé primaire doit être définie une seule fois dans toute la hiérarchie du bean entité.
Il existe deux types d'identifiants : simple et composite. Dans ce chapitre, nous traiterons uniquement l'identifiant de type simple. L'identifiant composite sera traité ultérieurement.
On parle d'identifiant simple lorsque celui-ci est composé par un unique champ dont le type est simple. Les types simples sont :
Remarque : en général, les décimaux (nombre à virgule) ne sont pas utilisés en tant que clé primaire. Les entités utilisant ce type pour la clé primaire risquent de ne pas être portables.
Pour spécifier au conteneur qu'un champ est une clé primaire, il faut annoter celui-ci avec @Id. Dans notre bean entité Photo, la clé primaire est assignée à l'attribut id.
7 @Entity 8 public class Photo implements Serializable { 9 private long id; 15 16 @Id 17 public long getId() { return id; } 18 public void setId(long id) { this.id = id; }
Voici également la clé primaire que nous avons sur l'entité Utilisateur :
@Entity public class Utilisateur implements Serializable { private int id; @Id @GeneratedValue(strategy = GenerationType.AUTO) public int getId() { return id; } public void setId(int id) { this.id = id; }
L'exemple précédent utilise l'annotation @GeneratedValue qui permet d'indiquer au conteneur d'utiliser la meilleure solution pour la génération de la clé primaire. Il existe quatre stratégies de génération disponibles : AUTO, IDENTITY, SEQUENCE et TABLE. Celles-ci sont définies par l'énumération javax.persistence.GenerationType.
IDENTITY
Ce type indique au fournisseur de persistance d'assigner la valeur de la clé primaire en utilisant la colonne identité de la base de données. Sous MySQL, par exemple, la clé primaire auto-générée est marquée avec AUTO_INCREMENT.
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) public int getId() { return id; } public void setId(int id) { this.id = id; }
SEQUENCE
Ce type, comme son nom l'indique, oblige le fournisseur de persistance à utiliser une séquence de la base de données. Celle-ci peut être déclarée au niveau de la classe ou au niveau du package grâce à l'annotation @SequenceGenerator et ses attributs :
@Entity @SequenceGenerator(name = "SEQ_USER", sequenceName = "SEQ_USER") public class Utilisateur implements Serializable { private int id; @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_USER") public int getId() { return id; } public void setId(int id) { this.id = id; }
Ce type de génération est utile lorsque la base de données offre un système natif de séquence et qu'il est conseillé de l'utiliser par le fournisseur de celle-ci.
.
TABLE
Ce type indique au fournisseur de persistance d'utiliser une table annexe pour générer les clés primaires numériques. Ce cas d'utilisation est plus rare :
@Entity @TableGenerator( name="C0NTACT_GEN", table="GENERATOR_TABLE", pkColumnName="key", valueColumnName="hi", pkColumnValue="id", allocationSize=25 ) public class Utilisateur implements Serializable { private int id; @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "CONTACT_GEN") public int getId() { return id; } public void setId(int id) { this.id = id; }
L'annotation @TableGenerator permet de préciser les paramètres de création de la table annexe de génération. Voici le détail des attributs de celle-ci :
AUTO
Ce type indique au fournisseur de persistance d'utiliser la meilleure stratégie (entre ENTITY, TABLE, SEQUENCE) suivant la base de données utilisée. Le générateur AUTO est le type préféré pour avoir une application portable.
Même si l'incrémentation automatique de la clé primaire soulage le développeur, elle doit être utilisée avec parcimonie. En effet, il est préférable d'utiliser un attribut de l'entité plutôt que d'en rajouter un, spécialement pour la clé primaire. Par exemple, l'entité Compte peut contenir une propriété numéroCompte qui se veut unique par la logique bancaire. Cette propriété est alors la meilleure candidate pour la clé primaire.
Après avoir pris connaissance des différentes annotations qu'il est possible de placer dans un bean entité, nous allons en profiter pour construire le bean entité Photo qui va me permettre de rendre persistant un certain nombre d'informations relatives aux photos stockées dans le serveur. Effectivement, je rappelle que je désire réaliser un serveur qui va stocker des photos numériques. Les fichiers photos vont être téléchargés et stockés dans un répertoire particulier du serveur. Par ailleurs, pour chacune des photos, j'ai besoin de préserver des informations supplémentaires qui la caractérise, notamment le genre de photo effectuée (paysage, animal, ville, ...) ainsi que son identification afin de pouvoir la retrouver par la suite. D'autres informations, moins importantes, mais qui peuvent s'avérer utiles pour la gestion de ces photos, vont également être préservées concernant les dimensions et le poids de l'image correspondante.
Pour terminer, je désire mémoriser l'instant exact du stockage de la photo dans le serveur. Cet instant va servir à la fois de nom de fichier et d'identificateur unique pour le bean entité. Cet instant est représenté par une valeur de type long.
Je rappelle qu'au niveau le plus bas, les dates et les heures sont représentées avec un long contenant le nombre positif ou négatif de millisecondes écoulées depuis minuit le 1 janvier 1970.
Je vais utiliser ici deux démarches. La première consiste à créer le bean entité le plus simplement possible en prenant les annotations qui sont justes nécessaires (ce bean entité simple fonctionne parfaitement). Ensuite, je vais étoffer mon écriture afin de proposer un bean entité qui sera plus performant au niveau de la table et de ce qu'on attend de lui.

Voici donc le premier type de codage et vous remarquez qu'il très facile de mettre en oeuvre un bean entité. Mis à part les deux annotations spécifiques, nous avons vraiment l'impression d'être en présence d'un JavaBean classique.
| photos.Photo.java |
|---|
1 package photos; 2 3 import java.io.Serializable; 4 import javax.persistence.*; 6 7 @Entity 8 public class Photo implements Serializable { 9 private long id; 10 private String genre; 11 private String identification; 12 private int largeur; 13 private int hauteur; 14 private long poids; 15 16 @Id 17 public long getId() { return id; } 18 public void setId(long id) { this.id = id; } 19 20 public String getGenre() { return genre; } 21 public void setGenre(String genre) { this.genre = genre; } 22 23 public String getIdentification() { return identification; } 24 public void setIdentification(String identification) { this.identification = identification; } 25 26 public int getLargeur() { return largeur; } 27 public void setLargeur(int largeur) { this.largeur = largeur; } 28 29 public int getHauteur() { return hauteur; } 30 public void setHauteur(int hauteur) { this.hauteur = hauteur; } 31 32 public long getPoids() { return poids; } 33 public void setPoids(long poids) { this.poids = poids; } 34 } |

Je vous propose maintenant la deuxième façon de construire notre bean entité Photo. Par exemple, pour l'instant de stockage, je rajoute un champ supplémentaire sous forme de date complète, c'est-à-dire, avec la date et l'heure en prenant donc le type d'enregistrement TIMESTAMP. Ensuite, je désire qu'à la fois l'indentification et le genre de la photo soit impérativement des informations non-nulles dans la base de données. Pour le genre, je propose d'ailleurs une limite du nombre de caractères qui ne sera effectivement jamais dépassée. Cela permet d'éviter de prendre trop de place dans la base de données. Pour terminer, je profite de l'occasion pour mettre en place des méthodes supplémentaires qui sont souvent très utiles et qui permettent de formatter des informations directement issues des attributs de la classe. Il faut juste préciser que ces méthodes ne doivent pas être persistantes (Attention, elles débutent par getXxx()).
| photos.Photo.java |
|---|
1 package photos; 2 3 import java.io.Serializable; 4 import java.text.DateFormat; 5 import java.util.Date; 6 import javax.persistence.*; 7 8 @Entity 9 public class Photo implements Serializable { 10 private long id; 11 private Date instantStockage; 12 private String genre; 13 private String identification; 14 private int largeur; 15 private int hauteur; 16 private long poids; 17 18 @Id 19 public long getId() { return id; } 20 public void setId(long id) { 21 this.id = id; 22 setInstantStockage(new Date(id)); 23 } 24 25 @Temporal(TemporalType.TIMESTAMP) 26 public Date getInstantStockage() { return instantStockage; } 27 public void setInstantStockage(Date instantStockage) { this.instantStockage = instantStockage; } 28 29 @Column(length=15, nullable=false) 30 public String getGenre() { return genre; } 31 public void setGenre(String genre) { this.genre = genre; } 32 33 @Column(nullable=false) 34 public String getIdentification() { return identification; } 35 public void setIdentification(String identification) { this.identification = identification; } 36 37 public int getLargeur() { return largeur; } 38 public void setLargeur(int largeur) { this.largeur = largeur; } 39 40 public int getHauteur() { return hauteur; } 41 public void setHauteur(int hauteur) { this.hauteur = hauteur; } 42 43 public long getPoids() { return poids; } 44 public void setPoids(long poids) { this.poids = poids; } 45 46 @Transient 47 public String getDateFormat() { 48 return DateFormat.getDateInstance(DateFormat.FULL).format(instantStockage); 49 } 50 51 @Transient 52 public String getNomFichier() { 53 return id+".jpg"; 54 } 55 } |
Maintenant que nous avons créer notre bean entité, nous allons voir comment l'utiliser au travers d'un bean session. Nous verrons par la suite comment utiliser ce bean entité directement depuis le poste client. Durant ce chapitre nous verrons également comment mettre en oeuvre notre unité de persistance.
Nous allons illustrer ce chapitre au travers de notre service d'archivage de photos. Nous allons mettre en oeuvre une application cliente fenêtrée dont le but est de prendre certaines des photos présentes sur un poste local afin de les stocker sur le serveur. Par anticipation, il est toutefois nécessaire de les identifier afin de les retrouver par la suite. Voici d'ailleurs l'aspect qu'aura notre application cliente :
Lorsqu'une photo nous plait, et lorsque nous avons procédé à son identification, il est alors possible de demander son archivage, ceci au travers d'un bean session. Effectivement, le serveur d'application dispose d'un bean session stateless FichiersPhotoBean qui remplit trois fonctions :
Voici d'ailleurs l'interface de type remote du bean session stateless :
| photos.FichiersPhotoRemote |
|---|
1 package photos; 2 3 import java.io.*; 4 import javax.ejb.Remote; 5 6 @Remote 7 public interface FichiersPhotoRemote { 8 String[] liste(); 9 byte[] getPhoto(String nom) throws IOException; 10 void setPhoto(byte[] octets, String genre, String identification) throws IOException; 11 void supprimer(String nom); 12 } |
La méthode qui va nous être utile ici est setPhoto() où nous spécifions le tableau d'octets représentant le fichier photo, le genre ainsi que l'identification du cliché.
Je le rappelle, dans un premier temps, nous devons mettre en oeuvre un fichier de configuration de la persistance nommé persistence.xml et placer ce dernier dans le répertoire <META-INF> à la racine du projet. Pour notre exemple, ce fichier décrit l'emplacement de la base de données ainsi que le fournisseur de persistance qui correspondant au serveur d'application.
Ce fichier sera alors lu par le conteneur d'EJB lors du déploiement de l'application (EJB-JAR). Au moment du déploiement, le conteneur vérifie la description proposée et contrôle la concordance avec les éléments qu'il connaît : le bean entité Photo est-il présent dans le serveur d'application, la base de données photos est-elle accessible, etc. Si tout se passe bien, le conteneur crée alors une instance de persistance Photos-ejbPU avec les paramètres demandés.
| persistence.xml avec toplink de GlassFish |
|---|
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="Photos-ejbPU"> <jta-data-source>photos</jta-data-source> <properties> <property name="toplink.ddl-generation" value="drop-and-create-tables"/> </properties> </persistence-unit> </persistence> |
C'est le bean session FichiersPhotoBean qui gère notre bean entité Photo. La persistance est donc exploitée dans un environnement Java EE. Nous devons donc utiliser la balise <jta-data-source> et spécifier ainsi le nom JNDI photos de la base de données utilisée pour stocker l'ensemble des informations relatives à une photo. Le nom de mon unité de persistance s'appelle Photos-ejbPU. J'utilise enfin le serveur de base de données intégré à Glassfish avec lequel je prévois de créer ou de modifier la table correspondante PHOTO.
Je rappelle que pour la persistance puisse se mettre en place (bean entité et unité de persistence), il est bien sûr nécessaire de créer une base de données qui va acceillir la table relative au bean entité. Par ailleurs, il est également indispensable de spécifier le nom JNDI correspondant à cette base de données. Il s'agit ici du nom JNDI photos.

A titre d'exemple, je vous propose le fichier de configuration persistence.xml dans le cas où nous utilisons JBoss et Hibernate en conjonction avec le serveur de base de données MySQL.
| persistence.xml avec Hibernate de JBoss ainsi que MySQL |
|---|
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="Photos-ejbPU"> <jta-data-source>photos</jta-data-source> <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="hibernate.hdm2ddl.auto" value="create-drop" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLInnoDBDialect" /> </properties> </persistence-unit> </persistence> |
Lors du développement d'une application Java EE, il est possible que certains objets ne soient pas utilisés de la même manière que d'autres. Il peut effectivement arriver, que certains d'entre eux doivent être rendus persistants dans la source de données alors que les autres doivent rester temporaires (en mémoire).
Le conteneur ne peut donc pas savoir à priori si tel ou tel objet doit être lié à la source de données. C'est le développeur qui décide de sauvegarder, modifier, supprimer ces objets. Pour cela, nous devons utiliser l'interface EntityManager qui permet d'effectuer ces opérations d'accès aux données. Toutefois, pour créer un objet de ce type, il faut utiliser la fabrique associée : EntityManagerFactory.
L'EntityManager étant une interface, la classe d'implémentation est différente suivant le fournisseur utilisé (éventuellement défini dans le fichier de configuration persistence.xml avec la balise <provider>). Par défaut, c'est le conteneur qui instanciera l'EntityManager et qui gérera son cycle de vie. On parle alors de gestionnaire d'entités géré par le conteneur.
Grâce à ce mécanisme, il est facile de changer de fournisseur. Le code correspondant à la gestion de l'unité de persistance demeure inchangé. C'est toujours le gros avantage d'utiliser des interfaces.
Il vous est cependant possible de gérer manuellement ce cycle de vie. On parle alors de gestionnaire d'entité géré par l'application. Ce sera le cas des applications Java SE, où seuls un EntityManager géré par l'application est disponible.
Il existe plusieurs manières d'obtenir un objet EntityManager qui ont chacunes des spécificités quant à leur utilisation :
Notre bean session accède donc au contexte de persistance et peut ensuite travailler avec les instances des beans entités, via l'EntityManager.
L'EntityManager est la pièce centrale servant à manipuler les beans entités. Grâce aux annotations, il sait comment faire le mapping entre un bean entité et une table et, plus précisément, entre un attribut et une colonne. Par contre, il lui manque toujours une information : dans quelle base de données doivent persister ces beans entités ?
Pour cela, nous devons utiliser un contexte de persistance qui le renseigne sur plusieurs informations : le type de la base de données ainsi que les paramètres de connexion. Afin d'indiquer au conteneur que le bean est dépendant d'un EntityManager, vous devez rajouter à l'attribut de type EntityManager l'annotation @PersistenceContext. Cette annotation admet plusieurs attributs :
Après toutes ces définitions, nous allons enfin mettre en oeuvre nos connaissances toutes récentes. C'est le bean session FichiersPhotoBean qui utilise le bean entité Photo et qui s'occupe de l'unité de persistance. Voici le code, avec les attributs nécessaires ainsi que la méthode setPhoto() qui est la seule utile pour cette partie de l'application :
| photos.FichiersPhotoBean |
|---|
1 package photos; 2 3 import java.awt.image.BufferedImage; 4 import java.io.*; 5 import java.util.*; 6 import javax.ejb.Stateless; 7 import javax.imageio.ImageIO; 8 import javax.persistence.*; 9 10 @Stateless 11 public class FichiersPhotoBean implements FichiersPhotoRemote { 12 @PersistenceContext(unitName = "Photos-ejbPU") 13 EntityManager persistance; 14 private final String répertoire = "J:/Photos/"; ... 33 public void setPhoto(byte[] octets, String genre, String identification) throws IOException { 34 Photo photo = new Photo(); 35 photo.setId(System.currentTimeMillis()); 36 File fichier = new File(répertoire+photo.getNomFichier()); 37 FileOutputStream stockage = new FileOutputStream(fichier); 38 stockage.write(octets); 39 stockage.close(); 40 photo.setGenre(genre); 41 photo.setIdentification(identification); 42 ByteArrayInputStream fluxImage = new ByteArrayInputStream(octets); 43 BufferedImage image = ImageIO.read(fluxImage); 44 photo.setLargeur(image.getWidth()); 45 photo.setHauteur(image.getHeight()); 46 photo.setPoids(octets.length); 47 persistance.persist(photo); 48 } ... 54 } |
Nous avons passé beaucoup de temps à décrire l'unité de persistance pour comprendre et bien maîtriser l'ensemble des relations. La déclaration de l'unité est d'une extrême simplicité, grâce notamment à l'utilisation de l'annotation @PersistenceContext qui injecte le gestionnaire d'entité persistance avec les descriptions données dans le fichier persistence.xml. Ainsi, uniquement avec deux lignes (lignes 12 et 13), le gestionanire d'entité persistance est associée à Photos-ejbPU.
A l'intérieur de la méthode setPhoto(), nous créons un objet photo correspondant à notre bean entité Photo. Nous complétons respectivement chacun de ses champs utiles au travers des méthodes que nous avons mis en oeuvre lors de la constitution de notre bean entité. Remarquez bien au passage, que nous utilisons cet objet comme si c'était un objet normal alors qu'en réalité, il s'agit bien d'un bean entité.
Une fois que tous les champs nécessaires sont bien renseignés, vous pouvez rendre votre entité persistante. Il suffit de faire une seule chose, d'appeler la méthode persist() de l'unité de persistance. Dans le prochain chapitre, nous reviendrons sur l'étude de cette méthode particulière ainsi que quelques autres qui permettent notamment de modifier ou de supprimer des lignes de la table qui ont déjà été introduites.
Grâce à cette méthode persist(), le bean entité est alors synchronisé avec la base de données. Ainsi par exemple, si la table n'existe pas, elle est automatiquement créée. Ensuite, une nouvelle ligne de la table est également constituée avec la valeur de chacune des colonnes introduite en rapport avec les attributs du bean entité.
Nous remarquons qu'il est très facile, mais aussi très intuitif, de mettre en oeuvre la persistance à l'aide d'un bean session.
.
Du point de vue du client, nous n'avons aucune relation quelconque avec un bean entité ou avec une unité de persistance. Notre seul contact avec le serveur s'effectue uniquement avec le bean session. Nous avons déjà travaillé avec ce type de bean et nous connaissons donc toute la démarche à suivre. Avec cette approche, en prenant donc un bean session qui sert d'intermédiaire avec le bean entité, nous n'avons pas besoin de déployer le descripteur de persistence persistence.xml. Avec le client, nous envoyons au serveur juste les informations nécessaires. Le bean session gère la persistance et s'occupe ensuite de formater ces informations afin de produire un bean entité en bonne et du forme. Ainsi chacun fait son travail. C'est souvent la meilleure façon de travailler.
Une remarque qui n'est pas négligeable non plus, c'est que nous n'avons pas besoin de déployer la classe du bean entité chez le client puisque ce dernier n'y accède pas directement.
| photos.Stockage.java |
|---|
1 package photos; 2 3 import javax.swing.*; 4 import java.awt.*; 5 import java.awt.event.*; 6 import java.awt.image.BufferedImage; 7 import java.io.*; 8 import javax.imageio.*; 9 import javax.naming.*; 10 11 public class Stockage extends JFrame implements ActionListener { 12 |