Java Persistence Api

publicité
[email protected]
Java Persistence Api
Cet article décrit une application qui illustre comment la sauvegarde d’un graphe d’objets peut être
réalisé en Java Standard Edition via Java Persistence Api.
1. Introduction
Java Standard Edition intègre à présent une API de persistance : Java Persistence Api (JPA). JPA
permet de réaliser un Mapping Objet Relationnel en assurant la synchronisation d’un graphe d’objets
avec des tables dans une base de données. Le but étant de masquer le plus possible la base de
données sous-jacente. Il est par exemple possible de générer automatiquement les tables de la base
à partir du code Java. De plus, le code, bien qu’à la norme Java Standard Edition, est compatible avec
la norme Enterprise Java Bean (EJB 3.0).
Technologies utilisées :
-
Eclipse
Hibernate 3
Spring 3
2. Un premier exemple
Commençons par étudier un exemple d’utilisation de JPA :
EntityManagerFactory emf = …
EntityManager em = emf.createEntityManager();
// 4
EntityTransaction tx = em.getTransaction();
tx.begin();
// 3
Voiture
// 1
voiture = new Voiture();
em.persist(voiture);
// 2
tx.commit();
La partie essentielle sont les lignes 1 et 2 où on créé un objet de type Voiture, mais ce pourrait être
n’importe quel objet Java, puis on demande sa sauvegarde dans la basse de données via la méthode
em.persist(voiture). Le reste du code, notamment la ligne 3, montre qu’il est possible de
réaliser des transactions. L’EntityManager (ligne 4) est le point d’entrée principal de JPA car c’est via
un EntityManager qu’on accède aux méthodes de sauvegarde, de recherche, de suppression… dans
la base de données.
3. L’application
L’application utilisée pour illustrer l’utilisation de JPA permet à des particuliers de louer des voitures.
Elle dispose d’une interface utilisateur basique dans laquelle on saisit le nom d’une personne
désirant louer une voiture définit par son numéro d’immatriculation :
Page 1
[email protected]
Le code de cette interface graphique occupe la couche Présentation dans l’architecture de
l’application (voir la figure suivante).
4. La couche Application
L’interface graphique accède à la couche Application via une interface :
package application;
public interface Location {
public void louer(String nomPersonne, String immatriculation) throws
VoitureException;
public void ajouterVoiture(String immatriculation) throws
VoitureException;
/**
*
* @param nomPersonne
* @return Le numero d'immatriculation de la voiture louee par
nomPersonne ou null si la personne n'a pas louee de voiture
Page 2
[email protected]
* @throws PersonneException si il n'y a pas de personne ayant le nom
nomPersonne
*/
public String rechercheVoiture(String nomPersonne) throws
PersonneException;
}
Une implantation est bien évidement associée à cette interface : la classe LocationImpl qui n’est pas
détaillée ici.
5. La couche métier (les classes persistantes)
La couche Métier est composé de 2 classes, Voiture et Personne associées entre elles par une
association de 1 vers plusieurs.
Ce sont ces 2 classes qui vont être mappées en base de données avec 2 tables. Voici la partie
principale du code de la classe Personne :
package business;
@Entity
public class Personne {
Voiture voiture;
private long id;
@ManyToOne
public Voiture getVoiture() {
return voiture;
}
public void setVoiture(Voiture voiture) {
this.voiture = voiture;
}
@TableGenerator(
name = "personneGen",
table="PERSONNE_SEQUENCE_GENERATOR",
pkColumnName="GEN_KEY",
valueColumnName = "GEN_VALUE",
pkColumnValue = "PERSONNE_ID",
allocationSize = 10)
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator =
"personneGen")
public long getId() {
return id;
}
public void setId(long id) {
Page 3
[email protected]
this.id = id;
}
}
On remarque la présence d’annotations telles que @Entity, @ManyToOne ou @Id . C’est grâce à ces
annotations que JPA peut générer automatiquement une table dans une base de données ainsi que
le code JDBC pour y accéder : l’annotation @ManyToOne permet de traduire des associations entre
objets d’un diagramme de classes en relation entre table dans la base de données ; l’annotation @Id
est obligatoire et sert à définir quel champ de la classe sera mappé avec la clef primaire de la table.
Ici, on demande à JPA de générer automatiquement les valeurs pour les clefs primaires grâce à
l’annotation @TableGenerator mais c’est optionnel.
6. La couche Services (où est géré la persistance)
La couche Application accède à la couche Services via une interface en utilisant le pattern Data
Access Object (DAO) :
package services;
import business.Personne;
import business.Voiture;
public interface DAO {
public void createVoiture(Voiture voiture);
public void createPersonne(Personne personne);
public Personne retrievePersonne(String nom);
public Voiture retrieveVoiture(String numImmatriculation);
public Voiture retrieveVoiture(Personne personne);
public void updateVoiture(Voiture voiture);
public void updatePersonne(Personne personne);
public void removeVoiture(Voiture voiture);
public void removePersonne(Personne personne);
}
Ce pattern DAO est très utilisé dans les applications JEE en général. C’est dans l’implantation de cette
interface que toute la persistance est gérée. Voici un extrait de cette implantation :
package services;
public class DOAImpl implements DAO{
EntityManager em = MyEntityManagerFactory.getEntityManager();
// …
public void createVoiture(Voiture voiture) {
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(voiture);
tx.commit();
}
Page 4
[email protected]
public void removeVoiture(Voiture voiture) {
EntityTransaction tx = em.getTransaction();
tx.begin();
em.remove(voiture);
tx.commit();
}
public Personne retrievePersonne(String nom) {
List liste = em.createQuery( "SELECT p FROM Personne p WHERE
p.nom LIKE :nomPersonne" ).
setParameter( "nomPersonne", nom ).
getResultList();
if(liste.size() == 0){
return null;
}
return (Personne)liste.get(0);
}
}
Le code des méthodes createVoiture et removeVoiture ressemble à celui de l’introduction de
ce document. La partie la plus intéressante, est contenue dans la méthode retrievePersonne. On
y voit une requête qui est envoyée à la base de données :
"SELECT p FROM Personne p WHERE p.nom LIKE :nomPersonne"
De fait, JPA possède un langage proche du SQL pour extraire de la base de données un ensemble
d’objets.
7. JPA + Spring
Dans cette application, nous utilisons aussi Spring (bien que JPA puisse être utilisé sans Spring dans
une application Java Standard Edition ou dans une application à la norme EJB). Spring est utilisé pour
assembler les couches de l’application. Le fichier XML applicationContext.xml, conforme à la
syntaxe Spring, où est défini cet assemblage est reprit en partie ici :
<bean id="louerVoitureDialog"
class="presentation.LouerVoitureDialog">
<property name="location">
<ref bean="location"/>
</property>
</bean>
<bean id="location" class="application.LocationImpl">
<property name="dao">
<ref bean="dao"/>
</property>
</bean>
<bean id="dao" class="services.DOAImpl"/>
Nous y retrouvons les trois beans correspondant aux implantations des couches Présentation,
Application et Services respectivement.
Page 5
[email protected]
8. La configuration de JPA
Il ne reste plus à présent qu’à étudier la configuration de JPA. Cela consiste principalement à choisir
une base de données et une implantation de JPA. N’importe quelle base de données qui dispose d’un
pilote JDBC de bonne qualité convient. Il y a plusieurs implantations de JPA : deux implantations
couramment utilisées sont celle d’Hibernate et de OpenJPA. Ici, nous utilisons Hibernate 3. La
configuration se fait essentiellement dans le fichier persistence.xml qui doit être placé dans un
répertoire META-INF, lui-même placé dans le répertoire qui contient les sources de l’application.
Voici un extrait de ce fichier :
<persistence 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_2_0.xsd"
version="2.0">
<persistence-unit name="manager1" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/DefaultDS</jta-data-source>
<class>business.Voiture</class>
<shared-cache-mode>NONE</shared-cache-mode>
<validation-mode>CALLBACK</validation-mode>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.cache.provider_class"
value="net.sf.ehcache.hibernate.SingletonEhCacheProvider"></property>
<property name="javax.persistence.jdbc.driver"
value="org.hsqldb.jdbcDriver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url"
value="jdbc:hsqldb:."/>
<property name="hibernate.max_fetch_depth" value="3"/>
</properties>
</persistence-unit>
</persistence>
Des explications détaillées sur cette configuration sont données dans la documentation d’Hibernate.
Comme indiqué dans l’exemple au début de ce document, le point d’entrée de JPA est la classe
EntityManager. Bien que Spring ne soit pas indispensable pour récupérer un EntityManager, il fournit
des Helper Classes pour cela. Comme d’habitude avec Spring, c’est en XML qu’on définit quelles
classes du framework on souhaite utiliser. Voilà le second extrait du fichier
(applicationContext.xml) de définition des composants de Spring :
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost/"/>
<property name="username" value="sa"/>
Page 6
[email protected]
<property name="password" value=""/>
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="persistenceUnitName" value="manager1" />
</bean>
On y voit la déclaration de deux beans : le premier définit la source de données utilisée (ici c’est la
base de données HSQLDB en local) ; le deuxième est la fabrique d’EntityManager. Cette fabrique
s’utilise de la façon suivante :
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
EntityManagerFactory emf =
(EntityManagerFactory)context.getBean("entityManagerFactory");
em = emf.createEntityManager();
9. Déploiement et lancement de l’application sous Eclipse
Les codes sources de ce projet ainsi que les libraires utiles (Hibernate, Spring, la base de données
HSQLDB) sont à l’adresse :
perso.efrei.fr/~charroux/JPA+Spring/
Après avoir lancé Eclipse, il faut commencer par créer un projet Java (File -> New -> Java Project) :
Une fois le projet créé, décompressez les sources et les librairies dans le répertoire du projet.
Rafraichissez votre projet pour qu’Eclipse prenne en compte l’ajout du code (File –> Refresh). Votre
projet doit se présenter ainsi dans Eclipse :
Page 7
[email protected]
On y voit :
-
tous les packages correspondant aux couches de l’application (application, business,
presentation…) ;
le répertoire META-INF qui contient le fichier persistence.xml ;
le fichier de déclaration des beans Spring applicationContext.xml ;
les libraries .jar.
Avant de lancer l’application, il faut démarrer la base de données. Ceci se fait en ligne de commande.
Sous Windows par exemple, cela se fait de la façon suivante :
Page 8
[email protected]
Le répertoire où exécuter cette commande est le répertoire lib du projet.
La dernière étape consiste à lancer l’application (voir la classe Test dans le répertoire test) :
package test;
public class Test {
public static void main(String[] args) {
Démarrage du conteneur léger de spring :
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
Accès à la couche application via son interface :
Location location = (Location)context.getBean("location");
Création d’une voiture dans la base de données :
try {
location.ajouterVoiture("23 AA 94");
} catch (VoitureException e) {
System.out.println(e.getMessage());
}
Lancement de l’interface utilisateur :
LouerVoitureDialog louerVoitureDialog =
(LouerVoitureDialog)context.getBean("louerVoitureDialog");
louerVoitureDialog.setVisible(true);
}
}
Page 9
Téléchargement