Article n° 32

En ligne: 8 juillet 2011
Modifié le: 1er septembre 2012
Popularité: 9
Nb visites: 15167

mots clés

Java

Java/JPA

Premier programme avec JPA

Comment rendre persistant un objet dans une base de données
Par: Chris

Java Persistance API (ou JPA) est une API intégrée à JEE depuis la version 5 dans le but de fournir un mécanisme de persistance simple et standard. Ce chapitre va vous montrer comment utiliser cette API.

 Avertissement

Cet article n’a pas pour but de vous apprendre le mapping objet/relationnel avec JPA mais simplement de vous en présenter le principe.

 Présentation de JPA

JPA pour Java Persistence API est une API qui a pour but de résoudre le problème de persistance d’objets dans une base de données relationnelle. Ce mécanisme qui gère le lien entre des objets d’une application avec les tables de la base de données se nomme Object-Relational Mapping ou ORM. Les classes dont l’état doit être sauvegardé sont dites entités et sont marquées dans le code source par l’annotation @Entity. Les fonctions permettant de gérer la persistance et de faire le lien avec la base de données physique se trouvent implantées par une classe particulière qui se nomme EntityManager.

La figure précédente montre une application qui utiliserait une classe Client persistante dont l’état serait sauvegardé dans la table T_CLIENT se trouvant en base de données. Si, depuis la classe Main() de l’application on désire définir un nouveau client, nous devrons créer une instance de la classe Client puis utiliser la méthode persist() d’un objet EntityManager que l’on peut obtenir de la manière suivante :

EntityManagerFactory emf = Persistence.createEntityManagerFactory( "TestJpa01PU");
EntityManager em = emf.createEntityManager();

Le paramètre chaine de caractère "TestJpa01PU" correspond au nom de l’unité de persistance définie dans le fichier persistence.xml se trouvant dans le projet. C’est également dans ce fichier que sont définis les paramètres de connexion à la base de données ainsi que le framework (librairie implémentant les fonctionnalités correspondant aux spécifications JPA) de mapping utilisé.

Il suffit après d’appeler la méthode persist() pour enregistrer le nouveau client dans la base.

em.persist( client );

Nous allons voir dans le paragraphe suivant comment mettre cela en œuvre dans un exemple très simple (voir simpliste) pour montrer le principe.

 Premier exemple d’utilisation de JPA.

Présentation

Comme JPA peut être utilisé dans une simple application Java SE, nous allons créer une petite application autonome avec une base de données Derby en mode embarqué. Cet exemple étant le plus simple possible nous n’utiliserons qu’une seule table dans notre base. D’autre part, nous partirons du principe que notre base de données n’existe pas encore et que cette dernière devra être crée automatiquement à partir de la définition de nos Entity Beans. Nous n’aurons donc pas besoin d’utiliser la moindre commande SQL.

Remarque : Nous utiliseront NetBeans version 6.9 dans cet exemple.

La classe à persister aura les caractéristiques suivantes :

Création du projet

Lancez NetBeans.

Créez un nouveau projet Java Standard à l’aide des options de menu :

Fichier → Nouveau projet

Sélectionnez la catégorie Java et projet Application Java

Cliquez sur Suivant

Donnez un nom à votre projet, exemple : TestJpa01

Au besoin, modifiez le répertoire dans lequel sera crée le projet

Cliquez sur Terminer

Ajout du pilote JDBC

Comme nous allons utiliser une base de données, nous allons maintenant importer le driver. Pour une base Derby en mode embarquée le fichier se nomme derby.jar et se trouve dans le dossier $DERBY_HOME/lib

Dans NetBeans, à partir de la vue Projet, sélectionnez le dossier Bibliothèques et cliquez sur le bouton droit pour sélectionner l’option de menu : Ajouter un fichier jar ou un dossier.

A partir de la boite de dialogue, sélectionnez le fichier derby.jar.

Vous pourrez constater à droite du dialogue, que l’option Copier dans le dossier des bibliothèques est sélectionné.

Cliquez sur Ok.

Création d’une classe persistante

Nous allons maintenant créer la classe entité nommée Client . Depuis la vue Projets, sélectionnez le dossier testjpa01 qui correspond au paquet java créé par NetBeans.

Cliquez sur le bouton droit puis, sélectionnez l’option : Nouveau → Classe Entité...

Dans le dialogue saisissez Client comme Class Name et définissez testJpa01.persistence comme package pour que la classe entité soit dans un paquet spécifique.

Toute classe entité doit posséder un attribut identifiant (ou clé primaire). Par défaut NetBeans considère qu’il sera de la classe Long ce qui est un bon choix pour un champ créée automatiquement par le système.

Vous remarquerez que l’option Create Persistence Unit est cochée. Comme nous sommes en train de créer notre première classe entité de notre projet et que nous n’avons pas encore définie d’unité de persistance, NetBeans nous propose d’en créer une.

Cliquez sur Suivant . L’écran suivant va nous permettre de définir notre unité de persistance ainsi que la connexion à la base de données.

NetBeans donne un nom par défaut à l’unité de persistance. Il a également choisi par défaut EclipseLink comme framework de persistance. Ce dernier est la version OpenSource de TopLink et correspond à l’implémentation de référence de JPA version 2.

Nous devons par contre définir les paramètres de connexion à la base de données. Pour cela, déroulez la liste correspondant au champ Database Connection puis, sélectionnez Nouvelle connexion base de données...

Dans le dialogue, cliquez sur Saisie directe de l’URL.

Choisissez le nom du driver en sélectionnantJava DB (Embedded) depuis la liste déroulante du champ Nom du pilote. Dans cet exemple il est inutile de saisir un nom d’utilisateur et un mot de passe.

Saisissez maintenant l’URL de connexion dans le champ correspondant : jdbc:derby:data/testjpa01db ;create=true

Cela aura pour effet de créer une base derby dans un dossier du projet nommé data.

Cliquez sur Ok.

NetBeans tente une connexion à la base puis affiche un écran permettant de sélectionner un schéma.

Ne modifiez rien et cliquez sur Ok.

NetBeans revient sur l’écran précédent. Dans le cadre de cet exemple, vous pouvez sélectionner l’option Supprimer et créer, ce qui aura pour effet de recréer une nouvelle base vierge à chaque exécution du programme.

ATTENTION : Dans le cas d’un développement réel, assurez-vous bien que l’option Supprimer et créer n’est pas cochées lorsque vous utiliserez une base de données contenant déjà des données.

Cliquez sur Terminer.

Nous pouvons voir que NetBeans a ajouté un certain nombre de choses dans notre projet.

Si l’on visualise la vue projet et que l’on affiche le détail des différents dossiers nous devons nous retrouver avec les ajouts suivants :

D’abord, dans le dossier Bibliothèques a été ajouté les fichiers correspondant à EclipseLink puisque c’est ce dernier que nous avons choisi comme framework ORM.

Ensuite un squelette de classe Client a été créé avec le code suivant :

Nous voyons que cette classe est bien une entité du fait de son annotation @Entity. Elle possède également un attribut id de type Long.

@Id  
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

L’annotation @Id indique qu’il s’agit de l’identifiant de la classe.

L’annotation @GeneratedValue spécifie que la valeur de cet attribut sera généré automatiquement par le moteur de bases de données lors d’un nouvel ajout d’un objet dans la base.

Il nous reste à créer les attributs manquants ce qui donne :

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String nom;
private String prenom;

Il reste à ajouter les getters et setters correspondant et notre classe persistante est terminée.

Dans le dossier META-INF, NetBeans nous a créé le fichier persistence.xml qui doit contenir les paramètres de notre unité de persistance. Si nous affichons le contenu de ce fichier en mode xml nous devrions avoir le contenu suivant :

Nous pouvons également afficher ce fichier de configuration de manière plus visuelle en cliquant sur l’onglet Conception.

Attention : vérifiez bien que dans le cadre du bas, la classe Client apparaît bien sinon cette dernière ne sera pas gérée par le gestionnaire d’entités. Pour l’ajouter, il suffit de cliquer sur le bouton Ajouter une classe. Notez également le nom de l’unité de persistance car nous devrons l’utiliser dans le programme lors de la création de l’EntityManager. Comme il s’agit d’un programme de test, nous avons également sélectionné l’option Supprimer et créer pour que la base soit recréée à chaque lancement du programme. Pour tester notre programme, nous allons entrer le code dans la méthode main() chargé d’ajouter un client dans la base. Nous utiliserons également un objet EntityManager qui nous fournira les méthodes pour rendre persistant notre objet client.

Ce qui donne :

Remarque concernant le code : En plus de la gestion des exceptions nous avons utilisé un objet pour géré les transactions. Dans cet exemple, nous sommes obligé de gérer explicitement les transactions car notre application est autonome et n’utilise pas un serveur d’application comme GlassFish.

Exécution du programme

Une fois le programme exécuté vous devriez avoir sur la console de sortie de NetBeans les messages suivants :

Nous constatons qu’il y a eu un message d’erreur SQL suite à une tentative de suppression de table (DROPE TABLE CLIENT). C’est parce que nous avons spécifié dans notre fichier persistence.xml de détruire la base et de la créer à nouveau. Comme la base n’existe pas encore cela produit une erreur. Nous n’avons pas besoin d’en tenir compte puisque cela n’a pas empêché le programme de s’exécuter d’après les messages suivants. Il nous suffit maintenant de vérifier que notre base de données a bien été créée. Normalement, vous devriez voir à la racine du projet un nouveau répertoire data/testjpa01db de créé.

Vérification de la création de la base de données

Vérifions maintenant qu’une table avec un enregistrement a bien été créé. Pour cela, nous pouvons ouvrir une console dans le répertoire de notre projet et lancer l’utilitaire ij pour les bases derby. Puis tapez les commandes suivantes :

Nous voyons après la liste des tables systèmes propre à derby qu’il y a également une table CLIENT dans la base de données. Une table nommée SEQUENCE a également été créée mais nous verrons cela plus tard.

Nous pouvons visualiser sa structure à l’aide de la commande suivante :

Vérifions maintenant son contenu :

Nous pouvons bien vérifier qu’un enregistrement correspondant à notre client a bien été enregistré.

Modifier les règles de mapping par défaut

Nous avons pu créer une base de données avec une table, des champs et ajouté un enregistrement sans avoir eu à taper une seule ligne de SQL ou avoir manipulé des outils propre au SGBD. Pour afficher le contenu de la table nous aurions pu parfaitement le faire en Java en utilisant le même principe. D’autre par nous avons pu définir notre mapping en n’ajoutant que quelques annotations dans notre code. En fait JEE6 et JPA procèdent à une configuration par défaut. Tant que le comportement par défaut convient il n’y a donc pas grand chose à faire. Nous allons voir dans ce premier exemple comment modifier le comportement par défaut de JPA.

Nous avons vu dans le paragraphe précédent qu’en plus de la table CLIENT, un table SEQUENCE a été créée. Cela vient du fait que nous avons spécifié avec des annotations que l’identifiant d’un objet persistant Client serait généré automatiquement par le système de bases de données derby. On peut également constater cela en regardant la définition de la base de données en langage SQL en utilisant la commande suivante :

Nous voyons bien dans le script la création des 2 tables en plus de la définition des clés. Cela a été généré à partir de l’ajout des annotations sur l’attribut Id correspondant au code suivant :

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

En spécifiant le mode AUTO comme stratégie de génération on demande au SGBD de généré automatiquement l’identifiant en utilisant de choisir lui-même le mode de génération. Si cela a l’avantage d’être portable d’un SGBD à l’autre nous ne pouvons pas savoir en fonction du choix de ce dernier quel sera le mécanisme de génération qui sera choisie.

Remarque Comme l’attribut AUTO est le mode de génération par défaut on aurait pu définir l’annotation plus simplement comme suit :

@Id
@GeneratedValue
private Long id;

Si maintenant nous désirons choisir nous même un autre mode de génération (à condition que ce dernier soit supporté par le SGBD utilisé) nous devrons le spécifier en modifiant l’annotation. En spécifiant le mode IDENTITY cela donne :

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

Supprimez le répertoire data pour être sur que la nouvelle définition est prise en compte puis relançons le programme et visualisons à l’aide de la commande dblook, nous voyons que le script SQL de la structure a changé et qu’il n’y a plus de table SEQUENCE. La commande CREATE TABLE correspond sans doute plus à ce que nous aurions saisie dans un script de génération de shéma.

CREATE TABLE "APP"."CLIENT" ("ID" BIGINT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1), "PRENOM" VARCHAR(255), "NOM" VARCHAR(255));

Remarque : Si, lorsque vous modifiez la structure de votre base de données, vous ne supprimez pas l’ancienne base (répertoire data) vous risquez à ce que la nouvelle structure soit ajoutée à l’ancienne. Si vous modifiez le nom d’une table, par exemple, vous risquez de vous retrouver avec 2 tables.

Maintenant, si l’on regarde la définition des champs NOM et PRENOM de la table CLIENT, nous nous apercevons que que les String Java ont été traduit en types SQL VARCHAR( 255 ). Nous pourrions avoir envie de limiter leurs tailles à 40 caractères par exemple. Imaginez maintenant que votre application utilise une grosse base de données qui sera gérée par un DBA qui vous impose un formalisme particulier pour les noms des objets SQL : noms de tables et noms de colonnes par exemple. Nous allons pouvoir le spécifier encore grâce aux annotations.

Pour préciser un nom de table, nous ajoutons l’annotation @Table comme suit :

@Entity
@Table( name = "T_CLIENTS_CLI" )
public class Client implements Serializable
{
...
}

Et pour spécifier un nom et une longueur aux champs nous utilisons l’annotation @Column comme suit :

@Column( name="CLI_NOM", length = 40 )
private String nom;

@Column( name="CLI_PRENOM", length = 40 )
private String prenom;

Supprimez l’ancienne base (voir remarque précédente) puis, relancez le programme. Vérifions la nouvelle structure. Ce qui donne :

 Conclusion

Nous avons, grâce à un petit exemple, montré comment définir des objets persistants dans une base de données relationnelle de façon assez simple en utilisant des annotations et sans connaissance particulière du langage SQL. On voit également, que même dans cet exemple simpliste, qu’en fonction de ces annotations la base physique générée pourra être différente. Dans une application réelle avec de nombreuses classes persistantes ayant des relations vous devrez sans doute vous intéresser encore plus à la manière dont la base de données est générée pour que le schéma relationnel soit optimal. Ce travail vous demandera sans doute un minimum de connaissance des bases de données relationnelles et de leurs règles de normalisations.

— -

SPIP |