On trouve de nombreux tutoriels pour débuter avec JPA. La question s’est posée récemment de l’utilisation de JPA dans un contexte Web simple, ce que l’on peut résumer par « Comment utiliser JPA dans un Servlet ? ». Là, plusieurs petits problèmes surviennent et le web est étonnement avare en explications claires et « bouts de code prêts à l’emploi ». Voici donc comment faire.
Exemple autonome
Tout d’abord, commençons par refaire un exemple simple avec une application autonome.
Pour cela, il nous faut :
- Créer un projet Java standard dans son IDE préféré (j’utilise Eclipse)
- Configurer son classpath pour utiliser JPA (j’ajoute la librairie JBoss 5.1 Runtime)
- Créer une entité persistante (une simple classe annotée)
package com.oxiane;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class EntitePersistente {
@Id @GeneratedValue
private Long id;
private String valeur;
//accesseurs
...
} - Créer un fichier persistence.xml dans le répertoire src/META-INF
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
<persistence-unit name="TestUnit">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.username" value="root" />
<property name="hibernate.connection.password" value="" />
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/test" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence> - Et une classe avec un « main » pour utiliser tout ça :
package com.oxiane;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class TestJPA {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestUnit");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
EntitePersistente entite = new EntitePersistente();
entite.setValeur(""+System.currentTimeMillis());
em.persist(entite);
tx.commit();
System.out.println("Entité persistée, id:"+entite.getId());
} catch (Exception e) {
e.printStackTrace();
System.out.println("Oops ! petit problème");
tx.rollback();
} finally {
em.close();
emf.close();
}
}
} - Il ne reste plus qu’à démarrer son MySql et à exécuter pour obtenir une trace dans la console du genre :
Hibernate: insert into EntitePersistente (valeur) values (?)
Entité persistée, id:1
On trouve facilement des infos pour faire la même chose dans un EJB
Dans ce cas, c’est même plus simple.
- On passe par une DataSource, ce qui modifie le fichier persistence.xml :
- <?xml version= »1.0″ encoding= »UTF-8″?>
<persistence version= »2.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_2_0.xsd« >
<persistence-unit name= »TestUnit »>
<jta-data-source>jdbc/TestDS</jta-data-source>
<properties>
<property name= »hibernate.hbm2ddl.auto » value= »update » />
<property name= »hibernate.show_sql » value= »true » />
</properties>
</persistence-unit>
</persistence> - Le code est beaucoup plus simple puisqu’on se fait injecter l’EntityManager et que la transaction est gérée par le conteneur :
- package com.oxiane;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless(mappedName= »ejb/Test »)
@Remote(JPAServiceRemote.class)
public class JPAServiceBean implements JPAServiceRemote, JPAServiceLocal {
@PersistenceContext
private EntityManager em;
public JPAServiceBean() {
}
public Long test() {
EntitePersistente entite = new EntitePersistente();
entite.setValeur(« » + System.currentTimeMillis());
em.persist(entite);
return entite.getId();
}
}
Que se passe-t-il maintenant si on veut faire la même chose dans un servlet ?
Là c’est moins simple et moins clair.
Tout d’abord, l’injection avec @PersistenceContexte n’est plus permise. Cela conduirait à partager l’EntityManager en cas de requêtes simultanées. A la place, il faut utiliser l’annotation @PersistenceUnit pour injecter l’EntityManagerFactory qui lui est partageable et « Thread-Safe ».
- Injection du PersistenceUnit :
@PersistenceUnit
private EntityManagerFactory emf; - Il est donc à nouveau nécessaire de créer explicitement un EntityManager à partir de la factory :
// !!! ne fonctionne pas !!!
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
EntityManager em = emf.createEntityManager();
EntitePersistente entite = new EntitePersistente();
entite.setValeur("" + System.currentTimeMillis());
em.persist(entite);
out.write("Entité persistée, id:" + entite.getId());
}
Bien qu’il n’y ait pas d’erreur à l’exécution, cela ne fonctionne pas. En effet, on ne bénéficie plus de la gestion de transaction du conteneur. Il faut à nouveau gérer explicitement la transaction.
Ici deux approches sont possibles que l’on peut résumer par « JTA ou pas ? »
L’approche non-JTA
Elle signifie que l’on va gérer les transactions directement avec l’EntityManager (comme dans l’exemple autonome initial).
- Dans ce cas, le fichier persistence.xml doit indiquer explicitement ce mode de gestion :
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.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_2_0.xsd">
<persistence-unit name="TestUnit" transaction-type="RESOURCE_LOCAL">
<non-jta-data-source>jdbc/TestDS</non-jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence> - Le code du Servlet devient alors :
@PersistenceUnit
private EntityManagerFactory emf;
//gestion des transactions non JTA
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
EntitePersistente entite = new EntitePersistente();
entite.setValeur("" + System.currentTimeMillis());
em.persist(entite);
tx.commit();
out.write("Entité persistée, id:" + entite.getId());
em.close();
}
L’approche JTA
L’autre approche consiste à utiliser des transactions JTA. Dans ce cas, le fichier de persistence.xml est le même que dans le cas des EJB mais il faut maintenant gérer explicitement une transaction JTA.
Le plus simple est de se faire injecter la UserTransaction avec l’annotation @Resource, en plus de la factory.
- Injection de la UserTransaction
@Resource
private UserTransaction tx;
@PersistenceUnit
private EntityManagerFactory emf; - Remarque : il peut paraître surprenant d’avoir un tel attribut dans le Servlet. Il faut juste comprendre que le UserTransaction n’est pas un objet représentant la transaction mais une interface, Thread-Safe, de manipulation des transactions. Elle peut donc être partagée sans problème.
- Le code devient alors :
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
EntityManager em = null;
try {
tx.begin();
em = emf.createEntityManager();
EntitePersistente entite = new EntitePersistente();
entite.setValeur("" + System.currentTimeMillis());
em.persist(entite);
tx.commit();
out.write("Entité persistée, id:" + entite.getId());
} catch (Exception e) {
try {
tx.rollback();
} catch (Exception e1) {
e1.printStackTrace();
}
out.write("Oops ! petit problème");
}
finally {
if (em != null) {
em.close();
}
}
}
Notez au passage une subtilité un peu déroutante : dans le cas des transactions JTA, l’EntityManager doit être créé après le début de la transaction pour qu’il soit géré par celle-ci.
Si on ne respecte pas cette contrainte, on retombe dans la situation précédente où il ne se produit pas d’erreur mais rien n’est enregistré.