Préparation d'un projet
Présentation du projet du livre
1. Description
Pour commencer, il faut avant tout un projet de logiciel utilisant une base de données. Pour cela, le livre se base sur un exemple permettant de couvrir la plupart des cas.
Le projet se base sur un logiciel de gestion de personne contenant la plupart des cas possibles pouvant être trouvés sur un projet de ce type.
Pour cela, il faut reprendre le projet nommé PROJET_ENI créé dans la section Premier lancement de NetBeans - Création de projet du chapitre Environnement de développement.
2. Schéma de la base de données
Ci-dessous, le schéma de la base de données utilisée dans le livre :
Les possibilités de mapping
Pour gérer le mapping entre la base de données et les objets de l’application, la configuration de l’ORM peut être faite de deux façons :
-
Par fichier XML externe au code source qui définit comment l’ORM peut accéder aux données.
-
Depuis java 1.5, le mapping peut être effectué via annotation.
Les annotations étant la version de mapping la plus courante, le livre se focalise sur cette possibilité.
1. Fichier de mapping
Le fichier de mapping permet de configurer entièrement JPA et Hibernate par fichier XML.
Ce fichier décrit l’interaction entre chaque classe java utilisée pour modéliser les données avec les tables de la base de données. Ainsi, il y aura un élément XML par entité, dans lequel se trouvent la table concernée et le lien entre les attributs et les colonnes.
Voici un exemple de fichier de mapping :
<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
version="1.0">
...
Paramétrage de l’ORM
Afin de gérer la configuration de l’ORM, l’application utilise le fichier persistence.xml qui doit se trouver dans le dossier META-INF du projet.
Dans ce fichier, plusieurs propriétés de configuration sont accessibles mais sept paramètres sont primordiaux :
-
L’unité de persistance
-
Le framework que doit utiliser JPA
-
Les entités à mapper
-
Le driver pour accéder à la base de données
-
L’URL de la base de données
-
Le login
-
Le mot de passe
1. Création du fichier persistence.xml
Depuis NetBeans, la création de ce fichier se fait simplement par l’interface graphique de l’éditeur.
Pour cela, il faut dans un premier temps créer un nouveau fichier en choisissant depuis un clic droit sur le package par défaut New - Other.
Choisissez Persistence Unit (unité de persistance) depuis la catégorie Persistence.
Renommez l’unité de persistance avec le nom voulu, ici PROJET_JPA. Choisisssez la librairie Hibernate (JPA 2.1) fournie par défaut par NetBean et créez une nouvelle connexion à la base de données.
Choisissez un nouveau driver pour utiliser celui téléchargé (cf. chapitre Environnement de développement - Installation de MySQL).
La fenêtre de création d’un nouveau driver s’ouvre. Cliquez sur le bouton Add pour choisir le driver MySQL téléchargé.
Dans la fenêtre d’arborescence, allez choisir le fichier jar du driver MySQL puis cliquez sur le bouton Ouvrir.
Dans la fenêtre suivante, vérifiez qu’il s’agit du bon driver, donnez-lui un nom puis cliquez sur le bouton OK.
Dans la fenêtre de création de connexion, vérifiez que c’est bien le driver nouvellement créé qui est sélectionné, puis cliquez sur Next.
Dans la fenêtre de customisation de connexion, saisissez les informations sur l’host, le port, le login et le mot de passe, lancez un test de connexion pour vérifier que les paramètres sont bons puis cliquez sur le bouton Next.
Sur la fenêtre de choix de base de données, cliquez sur le bouton Next.
Sur la fenêtre de choix de nom de connexion, cliquez sur le bouton...
Les types persistables
L’expression de "type persistable" désigne toutes les données qui peuvent être stockées en base de données. Ces types peuvent être catégorisés en quatre familles :
-
Classes définies par l’utilisateur
-
Données Java simples
-
Valeurs multiples
-
Divers
1. Classes définies par l’utilisateur
Pour faire une comparaison simplifiée avec les systèmes de gestion de base de données, ces classes peuvent être apparentées aux tables. C’est-à-dire que ce sont ces objets qui seront manipulés pour avoir accès aux différentes données stockées.
Ce sont les classes d’entités (Entity classes), les superclasses mappées (Mapped superclasses) et les classes intégrables (Embeddable classes). Elles servent de socle pour l’ORM.
a. Classe d’entité
C’est la classe de base de toute modélisation. C’est la seule à pouvoir être manipulée directement.
Toute classe d’entité, lors de sa déclaration, doit être précédée de l’annotation @Entity. Cette annotation permet aux différents ORM de retrouver les classes d’entités. Si l’entité n’a pas de particularité (par exemple : la table a le même nom que l’entité…), l’annotation se suffit à elle-même.
Ainsi, pour mapper la table personne avec l’entité Personne, cette déclaration est suffisante :
@Entity
public class Personne {
...
}
Le nom de l’entité sera par défaut celui de la classe. Il va servir dans les différentes requêtes JPQL. Par convention, il est préférable de ne pas le changer afin de simplifier la compréhension. Mais pour différentes raisons, telles que la présence de classes du même nom dans différents packages, il est possible de changer le comportement par défaut pour éviter les doublons sur le nom.
Pour cela, il suffit d’ajouter la propriété name à l’annotation et ce sera la valeur de celle-ci à utiliser dans les requêtes :
@Entity(name="NomDeLEntite")
Il est fort probable qu’une entité...
Les différents champs
Maintenant que l’introduction aux différents types persistables est faite, il faut pouvoir stocker les données dans la base de données. Pour cela, les entités sont composées de différents champs pour pouvoir stocker les chaînes de caractères, les valeurs numériques, les dates, etc.
1. Les champs temporaires
Comme vu précédemment, lorsque le mapping par annotation est utilisé, chaque propriété d’une entité est stockée en base de données.
Il est possible de vouloir qu’une donnée d’une entité ne soit pas stockée en base de données, comme les champs transients pour la sérialisation.
Ainsi, les champs static et/ou final ne sont pas mappés avec la base de données et ne sont donc pas stockés. Il est aussi possible d’utiliser le mot-clé transient comme pour la sérialisation ou encore d’utiliser l’annotation @Transient qui n’affecte que la persistance.
Par exemple, une entité représentant les quatre possibilités pour déclarer des champs temporaires :
@Entity
public class EntitAvecChampTemporaire {
static int tmp1; // non persistant car static
final int tmp2 = 0; // non persistant car final
transient int tmp3; // non persistant car transient
@Transient
int tmp4; // non persistant car @Transient
}
2. Les champs persistables
Les champs persistables peuvent être séparés en deux catégories :
-
Les champs basiques, qui représentent la donnée en elle-même et peuvent être associés dans la plupart des cas à une primitive.
-
Les relations, qui représentent un lien vers une entité ou une liste d’entités.
Dans cette partie, seuls les champs basiques sont abordés. Pour les relations entre les entités, il faut se reporter à la section Les relations de ce chapitre.
a. L’annotation @Basic
Les différents champs basiques qui peuvent être sauvegardés en base de données ont la possibilité d’être précédés de l’annotation @Basic. Cette annotation est facultative...
Les clés primaires
Après la définition des différentes données à sauvegarder au sein de l’entité, il faut définir la clé primaire. En SQL, deux types de clés primaires sont possibles, les clés primaires simples (sur une seule colonne) et les clés primaires composées (sur plusieurs colonnes).
1. Clé primaire simple
Les clés primaires simples sont les clés les plus souvent rencontrées. C’est-à-dire que principalement, une entité a un champ unique qui sert de clé primaire, comme un identifiant technique qui est géré par l’application ou encore le numéro de Sécurité sociale pour une personne.
JPA permet de déclarer très simplement cette clé primaire, il suffit de rajouter l’attribut @Id au-dessus du champ concerné.
Par exemple, l’entité Personne, après ajout de cette déclaration, ressemble à :
@Entity
public class Personne {
@Id
@Basic(optional = false)
private Integer id;
private String nom;
private String prenom;
@Column(name = "date_naissance")
@Temporal(TemporalType.DATE)
private...
Les valeurs générées
Les valeurs générées par les systèmes de gestion de base de données peuvent être classées en deux catégories :
-
Les valeurs fixes
-
Les valeurs incrémentielles
1. Valeur fixe
La génération d’une valeur fixe dans un système de gestion de base de données se fait par l’ajout de la propriété Default sur une colonne. C’est-à-dire que si cette valeur n’est pas renseignée lors de la création d’une ligne, la valeur sera insérée automatiquement.
JPA n’a pas d’annotation permettant de gérer cette valeur et si le schéma de la base de données est généré par des scripts non gérés par JPA, cette propriété est à gérer normalement en l’initialisant avec la valeur voulue.
Par contre, si le schéma est généré via l’ORM, il est possible de contourner la limitation de JPA avec la propriété columnDefinition de l’annotation @Column.
Par exemple, pour rajouter une valeur par défaut égale à 10 sur une colonne, il suffit de rajouter :
@Column(columnDefinition="default '10'")
2. Valeur incrémentielle
Dans la plupart des cas, la gestion de l’identifiant unique est déléguée au système de...
Les relations
Une fois les données définies grâce aux entités, aux champs et aux clés primaires, il reste à définir les interactions entre ces entités, c’est-à-dire les clés étrangères.
Ces relations peuvent être de quatre types :
-
Les relations 1-1 (OneToOne)
-
Les relations n-1 (ManyToOne)
-
Les relations 1-n (OneToMany)
-
Les relations n-n (ManyToMany)
Ces différentes relations vont permettre la navigabilité entre les entités. Ainsi, lors de la conception du programme, avec la réalisation du diagramme de classes, cette navigabilité est représentée et elle impactera les différentes entités à créer.
1. Les relations 1-1 (OneToOne)
Les relations les plus simples entre deux entités sont les relations OneToOne. Comme vu à la section Classe intégrable, il est possible de stocker ces deux entités dans la même table, mais pour des raisons de conception il est possible de sauvegarder ces données dans deux tables différentes.
La représentation de cette relation se fait avec l’annotation @OneToOne.
Par exemple, une classe Personne ayant une relation OneToOne avec une classe PersonneDetail est déclarée de la façon suivante :
@Entity
public class Personne {
@OneToOne
private PersonneDetail personneDetail;
}
@Entity
public class PersonneDetail {
@OneToOne
private Personne personne;
}
L’inconvénient de cette écriture est que la classe Personne connaît le lien avec la classe PersonneDetail et inversement. Ce qui veux dire que ces deux tables ont une clé étrangère l’une vers l’autre.
La solution pour ne plus avoir ces deux clés étrangères est de supprimer une des navigabilités.
Par exemple, la clé étrangère va être gardée au niveau de la table PersonneDetail. De ce fait le nouveau code source ressemble à l’exemple ci-dessous :
@Entity
public class Personne {
}
@Entity
public class PersonneDetail {
@OneToOne
private...
Type de chargement des données
Le type de chargement des données est un concept essentiel des ORM. Il permet de définir la stratégie de récupération en mémoire des informations, y compris des relations entre les entités. La norme JPA l’appelle le "fetch type".
Il est de deux types différents :
-
FetchType.LAZY
-
FetchType.EAGER
1. Lazy
Le chargement de type LAZY est le mode qui charge les données voulues au moment de leur première utilisation.
Pour une entité A ayant une relation ManyToMany avec une entité B, avec un fetch type LAZY, lors du chargement de l’entité A, les entités B en relation avec elle ne sont pas chargées automatiquement. Leur chargement ne se fait qu’au moment de l’appel de la liste.
Ce type de chargement a différents avantages :
-
Le chargement d’une nouvelle entité est rapide car seules les données voulues sont récupérées.
-
La gestion de la mémoire est plus légère.
Mais il a aussi ses inconvénients :
-
La session ne doit pas être interrompue avant la fin du traitement.
-
La navigation entre les entités est moins rapide.
FetchType.LAZY est le comportement par défaut des relations ManyToMany et OneToMany. Pour simplifier, ce sont les annotations de relation de type "liste".
2. Eager
Le chargement de type EAGER est le mode...
Particularités sur les entités
Une fois les entités créées, il reste à implémenter la sérialisation et les méthodes equals() et hashCode().
1. Sérialisation
En soi, avec Hibernate, les entités n’ont pas besoin d’être sérialisables si l’application stocke directement les données en base. Par contre, lors d’une configuration un peu plus avancée de l’ORM avec certaines librairies de gestion de cache, l’entité doit être sérialisable. De ce fait, une bonne pratique lors de la création des entités est de les rendre sérialisables en implémentant l’interface java.io.Serializable.
Par exemple, en appliquant cette pratique à la classe Personne :
@Entity
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
...
}
2. Equals et HashCode
En programmation orientée objet, il faut souvent redéfinir les méthodes equals() et hasCode() afin de pouvoir comparer les objets entre eux et/ou les stocker dans des listes. Par définition, les méthodes equals() et hashCode() ne doivent pas renvoyer de résultats différents tout au long de la durée de vie de l’objet. Ainsi, il y a trois façons principales d’utiliser...