PersistenceUnit dynamique
Par Cédric Tabin le mardi 13.05.2008, 23:14 - Java - Lien permanent
Les développeurs Java verront tout de suite de quoi je parle en disant JPA (Java Persistence API). L'idée est de pouvoir complètement rendre son programme indépendant de la base de données en mappant ses tables sur de simples objets POJOs et en utilisant une PersistenceUnit pour faire la connexion.
Pour ceux qui ne connaissent pas la puissance de JPA, il y a un tutorial intéressant (en français) par ici !
Le problème dans un projet JavaEE est que la configuration de la PersistenceUnit se fait au déployement. A la base le fichier de configuration (persistence.xml) se présente de la manière suivante (je suis sous NetBeans 6.1) :
<?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="myPersistenceUnit" transaction-type="JTA"> <jta-data-source>myDB</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties/> </persistence-unit> </persistence>
Et il est très simple de l'utiliser au sein d'un SessionBean :
//import... @Stateless public class MySessionBean { @PersistenceContext private EntityManager em; public void myFunction() { //... em.persist(myEntity); //... } }
A cette étape, rien de bien sorcier. Les choses se compliquent lorsqu'une deuxième PersistenceUnit est définie au sein du fichier de configuration :
<?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="myPersistenceUnit" transaction-type="JTA"> <jta-data-source>myDB</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties/> </persistence-unit> <persistence-unit name="secondPersistenceUnit" transaction-type="JTA"> <jta-data-source>secondDB</jta-data-source> <exclude-unlisted-classes>false</exclude-unlisted-classes> <properties/> </persistence-unit> </persistence>
Lors du redéployement, l'erreur suivante est balancée par NetBeans :
Deploying application in domain failed; Could not resolve a persistence unit corresponding to the persistence-context-ref-name [MySessionBean/em] in the scope of the module called []. Please verify your application. Deployment error: The module has not been deployed. See the server log for details. at org.netbeans.modules.j2ee.deployment.devmodules.api.Deployment.deploy(Deployment.java:166) at org.netbeans.modules.j2ee.ant.Deploy.execute(Deploy.java:104) at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:288) at sun.reflect.GeneratedMethodAccessor66.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:105) at org.apache.tools.ant.Task.perform(Task.java:348) at org.apache.tools.ant.Target.execute(Target.java:357) at org.apache.tools.ant.Target.performTasks(Target.java:385) at org.apache.tools.ant.Project.executeSortedTargets(Project.java:1329) at org.apache.tools.ant.Project.executeTarget(Project.java:1298) at org.apache.tools.ant.helper.DefaultExecutor.executeTargets(DefaultExecutor.java:41) at org.apache.tools.ant.Project.executeTargets(Project.java:1181) at org.apache.tools.ant.module.bridge.impl.BridgeImpl.run(BridgeImpl.java:277) at org.apache.tools.ant.module.run.TargetExecutor.run(TargetExecutor.java:460) at org.netbeans.core.execution.RunClassThread.run(RunClassThread.java:151)
Un message super expressif n'est-ce pas ? En développant sur JavaEE, je trouve que c'est une des choses les plus pénibles : les messages d'erreur qui ne veulent rien dire ! Ce qui se passe ici en fait, c'est que le serveur n'est pas capable de choisir dynamiquement quelle PersistenceUnit il doit attributer à la variable 'em'. Il est donc très facile de corriger cela :
//import... @Stateless public class MySessionBean { @PersistenceContext(unitName="myPersistenceUnit") private EntityManager em; public void myFunction() { //... em.persist(myEntity); //... } }
Et tout refonctionne normalement. J'en arrive maintenant au problème évoqué plus haut : le choix de la PersistenceUnit est fait au déployement. Dans un projet récent, l'idée était de pouvoir changer de PersistenceUnit dynamiquement au runtime. L'information n'est pas facile à trouver et nécessite un petit remodelage de la classe !
Tout d'abord, il faut lister les PersistenceUnit au début de la classe comme ceci :
@PersistenceContexts({ @PersistenceContext(name="persistence/myPU1", unitName="myDB"), @PersistenceContext(name="persistence/myPU2", unitName="secondDB") })
Et il est ensuite possible de récupérer un EntityManager correspondant à une PersistenceUnit définie via une requête JNDI :
EntityManager em1 = (EntityManager) new InitialContext().lookup("java:comp/env/persistence/myPU1"); EntityManager em2 = (EntityManager) new InitialContext().lookup("java:comp/env/persistence/myPU2");
Il ne reste plus qu'a encapsuler cela dans une méthode et quelques autres fioritures pour pouvoir dynamiquement changer de base de donnée au runtime (merci à Raphael Tagliani pour le bout de code) !
Commentaires
Je suis débutant dans la pérsistence avec JPA.
Je reste en Afrique.
JPA m'a beaucoup fasciné.
J'utilise netbeans 6.0.1 et j'ai remarqué sa puissance.
Jusque là je n'ai utilisé qu'une seule unité de persistence.
Je vais essayé avec 2.
J'essaye de combiner le JSF(présenetation) et le JPA(persistence) pour mes applications web.
J'aime bcp le Java.
J'aimerai echangé souvent avec vous.
Merci.
Dabord, merci pour cet excellent article
TOujours dans le but de rendre mon aaplis indépendante de la localistation de la base de donnée, je souhaite pouvoir modifier en runtime le paramètres de mon unité de persistence. Faut-il pour cela modifier directement le fichier xml de l'unité? Existe - il une méthode plus propre?
Merci
Salut,
D'après mes tests, il me semble que tu es forcé de modifier ton XML et de redéployer le serveur... Je ne pense pas que ce soit possible de pouvoir changer dynamiquement l'URL de la base de donnée au runtime, il faut créer une PU par URL.
@++
Bonjour,
Dans le cadre du développement d'un service web, j'essaye de configurer l'accès à deux base de données distincts, l'une contenant les données métiers, l'autre contenant l'historique, les logs et autres statistiques.
Du coup, débutant en spring, j'ai essayé de mettre en pratique ton article, mais je comprend pas très bien la ligne suivante:
@PersistenceContext(name="persistence/myPU1", unitName="myDB")
d'ou sort le "persistence/myPU1", myPU1 est il le nom donné au persistenceUnit ?
et "myDB" serait le nom du datasource ??
Jusque là je pensais que unitName etait le nom du persistenceUnit à utiliser ! mais alors a quoi correspond la propriété name ??
Merci
Hello,
En fait le persistence/myPU* est le nom JNDI qui va être utilisé sur le serveur (GlassFish, Weblogic, ...) tandis que myDB est le nom de la persistenceUnit à l'intérieur du fichier persistence.xml
@++
Humhum
du coup nameUnit serait pas plutot "myPersistenceUnit" ou "secondPersistenceUnit", non ?
et le persistence/myPU* il faut le configurer où ?
Merci
Re,
En fait le persistence/myPU*, ben c'est un nom à choix... Tu peux mettre ce que tu veux. Lorsque tu vas déployer ton application sur ton Application Server, il va automatiquement le créé et faire tout ce qui est nécessaire. Le nom JNDI est utilisé pour faire le pont entre le code Java (le InitialContext utilise le nom JNDI) et ta persistence Unit (qui utilise le nom de persistence).
@++
Ok, je comprend mieux.
Merci pour tes réponses et ta réactivité
Erwan
Merci beaucoup pour cet article très utile et instructif, qui , accessoirement, m'a fait gagner un temps considérable ^^. Bonne continuation !!