Dans le billet précédant [step16] nous avons introduit JPA en mode « standalone ». Dans ce billet nous allons mettre en place JPA dans un conteneur avec Spring en suivant le design pattern Service/DAO. Spring fournit à travers Spring ORM un support JPA Spring. Ce support Spring JPA permet de déclarer dans un bean Spring, la factory JPA EntityManagerFactory de 3 manières :
- LocalEntityManagerFactoryBean est une factory Spring qui permet de créer une instance de EntityManagerFactory définit dans un persistence-unit du fichier META-INF/persistence.xml. Aucune configuration n’est possible avec cette factory Spring (ex : le nom du fichier persistence.xml ne peut pas être configuré).
- JNDI qui permet de récupérer un EntityManagerFactory via JNDI. Je ne parlerais pas de cette déclaration.
- LocalContainerEntityManagerFactoryBean qui permet entre autre de configurer les informations provider et properties du fichier persitence.xml via des bean Spring. Nous verrons l’intêret d’utiliser cette factory dans le prochain billet.
Dans ce billet nous allons mettre en place JPA avec Spring en utilisant LocalEntityManagerFactoryBean avec l’implémentation JPA/Eclipselink et montrerons l’intérêt d’utiliser cette déclaration de l’EntityManagerFactory. En effet Spring jouera le rôle d’un conteneur JPA qui permettra de :
- gérer les ouvertures/fermetures des EntityManager.
- gérer les ouvertures/fermetures des EntityTransaction (commit/rollback).
REMARQUE : Ces apports sont aussi valable pour les 2 autres solutions JNDI et LocalContainerEntityManagerFactoryBean.
Vous pouvez télécharger le zip org.dynaresume_step17.zip qui contient les projets expliqués ci dessous:
- org.dynaresume.test.dao.find.mock : bouchon DAO où les Service/DAO seront instanciés en Java classique.
- org.dynaresume.test.dao.find.mock.spring : bouchon DAO où les Service/DAO seront déclarés en tant que bean XML Spring puis instanciés à l’aide de Spring.
- org.dynaresume.test.dao.find.mock.spring.annotations : bouchon DAO où les Service/DAO seront déclarés via des annotations Spring puis instanciés à l’aide de Spring.
- org.dynaresume.test.dao.find.jpa : implémentation JPA DAO où les Service/DAO seront instanciés en Java classique. JPA en mode « standalone » sera ici utilisé.
- org.dynaresume.test.dao.find.jpa.spring : implémentation JPA DAO où les Service/DAO seront instanciés à l’aide de Spring. Spring ORM sera aussi utilisé pour utiliser JPA dans un conteneur.
- org.dynaresume.test.dao.find.jpa.spring_PersistenceUnit utilisation de l’annotation JPA @PersistenceUnit pour injecter l’EntityManagerFactory à la DAO gràce au conteneur Spring.
- org.dynaresume.test.dao.find.jpa.spring_PersistenceContext utilisation de l’annotation JPA @PersistenceContext pour injecter l’EntityManager à la DAO gràce au conteneur Spring.
- org.dynaresume.test.dao.create.jpa implémentation JPA DAO avec des Transactions (EntityTransaction) où les Service/DAO seront instanciés en Java classique. JPA en mode « standalone » sera ici utilisé.
- org.dynaresume.test.dao.create.jpa.spring implémentation JPA DAO avec des Transactions (EntityTransaction) où les Service/DAO seront instanciés à l’aide de Spring. Spring ORM sera aussi utilisé pour utiliser JPA dans un conteneur.
Pré-requis
Avant de démarrer ce billet vous devez avoir :
- créé la base de données H2 et copié la base H2 dynaresume.data.db dans C:/db/h2. Pour vérifier que la base H2 est bien installée, je vous conseille de tester la base H2.
- créé la base de données Derby et copié la base Derby (répertoire dynaresume ) dans le répertoire C:/db/derby. Pour vérifier que la base Derby est bien installée, je vous conseille de tester la base Derby.
Téléchargez le zip spring-target-platform-dao.zip qui contient les librairies JARs JPA, JPA/Hibernate,… récupérés via Maven dans le billet [step14] puis importez le projet spring-target-platform-dao dans votre workspace.
REMARQUE : nous avons créé la base de données via des scripts mais JPA est capable de générer la base de données en utilisant les informations de mapping.
Design pattern Service/DAO
Le pattern DAO (Data Access Object) permet de découper son application en 2 couches bien distinctes :
- couche Service: cette couche permet de gérer le métier de l’application et fait généralement appel à la couche DAO. Le service UserService par exemple peut contenir un service createUser qui permet de valider les informations de l’utilisateur à créer (ex : le login ne doit pas déja exister dans la base) puis fait appel à la couche DAO pour créer le User en base de données (ou autres).
- couche DAO : cette couche permet de récupérer/mettre à jour des données d’une base de données (ou autres).
Voici un schéma qui représente le pattern Service/DAO avec notre implémentation de service UserService :
Le schéma montre que le service peut utiliser plusieurs implémentation de DAO comme :
- Mock qui est une DAO bouchon qui simule la base de données.
- JPA qui est une implémentation JPA de la DAO.
- JDBC qui est une implémentation JDBC de la DAO.
C’est un principe qui est très conseillé de suivre car il permet de bien découper son application et surtout de pouvoir effectuer des tests unitaires sur les services avec des classes bouchons DAO.
Implémentation DAO
Le service utilise une DAO. Pour cela il y a 2 solutions :
- le service utilise directement une implémentation d’une DAO et celui-ci est fortement couplé à l’implémentation de la DAO ce qui ne permet pas ensuite de faire des tests unitaires.
- l’implémentation de la DAO est « injecté » via un setter (ou annotations) au service qui lui travaille avec une interface DAO et pas une implémentation. C’est le principe de l' »Inversion Of Control » utilisé dans Spring.
Gestion des Connections/Transactions
Voici le code JPA qui permet d’utiliser EntityManager et qui nécessite une ouverture/fermeture d’une connection (via l’EntityManager) :
EntityManager entityManager = null; try { entityManager = entityManagerFactory.createEntityManager(); // Use entityManager here } finally { if (entityManager != null) { entityManager.close(); } }
et voici le code JPA qui permet d’utiliser EntityTransaction, qui nécéssite une ouverture/fermeture d’une transaction (via l’EntityTransaction) :
EntityManager entityManager = null; EntityTransaction transaction = null; try { entityManager = entityManagerFactory.createEntityManager(); transaction.begin(); // Create, delete, update here transaction.commit(); } finally { if (entityManager != null) { entityManager.close(); } if (transaction != null) { transaction.rollback(); } }
La question est de savoir ou effectuer ce code (dans le service, la DAO, avant l’appel du service?)? Dans l’idéal, la couche Service doit faire abstraction du code technique des DAO requis (ouverture/fermeture des connections). Autrement dit elle le code d’une méthode d’un service ne doit pas être pollué par du code d’ouverture/fermeture des connections, des transactions. Avec la programmation classique Objets, cette règle est difficile à respecter. Voici les 3 façons d’ouvrir/fermer les connections :
- ouvrir/fermer les connections dans les méthodes des DAO. Ce qui peut être catastrophique en terme de performances pour des services qui ferait appel à N DAO (N ouverture/fermeture de connections) alors qu’une seule ouverture/fermeture de connections pourrait suffire pour faire appel à N DAO. De plus l’aspect transactionnel (appel d’une DAO de création de User puis appel d’une DAO de création de Rôle) n’est pas respecté (chaque appel de DAO ouvre une nouvelle Transaction).
- ouvrir/fermer les connections dans les méthodes des services. Ce qui est généralement fait mais qui pollue le code du service avec du code de l’implémentation de la DAO. Dans notre cas nous aurrions du code JPA dans notre service UserService. Ce qui ne permet pas d’utiliser le service pour une implémentation DAO en JDBC ou Hibernate pur.
- ouvrir/fermer les connections avant les appels des services. Ce solution est souvent utilisé en JEE ou un Filtre JEE ouvre connection au début de la requête HTTP et ferme la connection en fin de la requête HTTP (lorsque la page HTML est généré). Ce pattern appelé « Open Session In View » en Hibernate ne libère pas très rapidement les connections inutilisées.
L’idéal est de pouvoir appliquer l’ouverture/fermeture les connections dans les méthodes des service sans devoir coder quoique ce soit et polluer le service avec ce code technique de connections. La solution à ce problème est l’utilisation d’un conteneur qui permet de gérer ces problématiques transverses de connections. Spring fournit un support JPA qui permet de gérer ces problématiques de connections en utilisant AOP (programmation par aspect).
Avec Spring l’annotation @Transactional permet d’indiquer au service qu’il doit ouvrir une (transaction) :
Voici un exemple de code d’une méthode d’un service qui permet de sauvegarder une entité dans la base de donnée en utilisant @Transactionnal :
@Transactional public User createUser(String login, String password) { User user = new User(login, password); return userDAO.saveUser(user); }
Le service ici ouvrira une transaction JPA (dans le cas ou l’interface userDAO est implémenté en JPA) et la commitera en fin de la méthode. Ce code est aussi utilisable pour JDBC par exemple. Aucun code spécifique au DAO n’est requis.
Service/DAO – find*
Dans cette section nous allons mettre en place le pattern Service/DAO qui utilise une connection en lecture seule (pas de transaction).
Services/DAO – find* Mock (sans Spring)
Dans cette section nous allons mettre en place une DAO bouchon UserDAOMock qui permet de retourner une liste de User statique. Ce bouchon permet de simuler l’accès à la table T_USER de la base de données dynaresume. Les Service/DAO seront instanciés en Java classique. Créez le projet Java org.dynaresume.test.dao.find.mock.
Domain – User
Créer la classe org.dynaresume.domain.User comme suit :
package org.dynaresume.domain; public class User { private static final long serialVersionUID = 1L; private long id; private String login; private String password; public User() { } public User(String login, String password) { setLogin(login); setPassword(password); } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
DAO – UserDAO/UserDAOMock
Ici nous allons mettre en place la couche DAO avec l’interface UserDAO et son implémentation bouchon UserDAOMock.
Interface UserDAO
Créez l’interface org.dynaresume.dao.UserDAO comme suit :
package org.dynaresume.dao; import java.util.Collection; import org.dynaresume.domain.User; public interface UserDAO { Collection<User> findAllUsers(); }
Implémentation UserDAOMock
Créez l’implémentation bouchon de UserDAO avec la classe org.dynaresume.dao.mock.UserDAOMock comme suit :
package org.dynaresume.dao.mock; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; public class UserDAOMock implements UserDAO { private Collection<User> users = null; public Collection<User> findAllUsers() { if (users == null) { users = createUsers(); } return users; } private Collection<User> createUsers() { List<User> users = new ArrayList<User>(); User user = new User("angelo", ""); user.setId(1); users.add(user); user = new User("djo", ""); user.setId(2); users.add(user); user = new User("keulkeul", ""); user.setId(3); users.add(user); user = new User("pascal", ""); user.setId(4); users.add(user); return users; } }
Service – UserService/UserServiceImpl
Ici nous allons mettre en place la couche Service avec l’interface UserService et son implémentation UserServiceImpl qui va utiliser l’interface UserDAO (et pas l’implémentation direct). On parle de Dépendance d’Injection.
Interface UserService
Créez l’interface org.dynaresume.services.UserService comme suit :
package org.dynaresume.services; import java.util.Collection; import org.dynaresume.domain.User; public interface UserService { Collection<User> findAllUsers(); }
Implémentation UserService
Créez l’implémentation du service UserService avec la classe org.dynaresume.services.impl.UserServiceImpl comme suit :
package org.dynaresume.services.impl; import java.util.Collection; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; public class UserServiceImpl implements UserService { private UserDAO userDAO; public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } public Collection<User> findAllUsers() { return userDAO.findAllUsers(); } }
Cette implémentation utilise l’interface DAO UserDAO qui doit etre renseigné via le setter UserDAO#setUserDAO(UserDAO userDAO).
FindAllUsersDAOMockTest (sans Spring)
Ici nous allons créer la classe de Test FindAllUsersDAOMockTest (Main) qui utilise notre Service et DAO. Créez la classe org.dynaresume.test.dao.find.FindAllUsersDAOMockTest comme suit :
package org.dynaresume.test.dao.find; import java.util.Collection; import org.dynaresume.dao.UserDAO; import org.dynaresume.dao.mock.UserDAOMock; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.dynaresume.services.impl.UserServiceImpl; public class FindAllUsersDAOMockTest { public static void main(String[] args) { // 1. Create DAO UserDAO userDAO = new UserDAOMock(); // 2. Create Service and inject DAO UserService userService = new UserServiceImpl(); ((UserServiceImpl) userService).setUserDAO(userDAO); // 3. Use the service Collection<User> users = userService.findAllUsers(); for (User user : users) { System.out.println("User [id=" + user.getId() + "login=" + user.getLogin() + ", password=" + user.getPassword() + "]"); } } }
Lancez la classe et la console affiche la liste des Users retournés par la DAO UserDAOMock :
User [id=1login=angelo, password=]
User [id=2login=djo, password=]
User [id=3login=keulkeul, password=]
User [id=4login=pascal, password=]
Services/DAO – find* Mock (avec Spring)
Dans cette section nous allons reprendre le projet org.dynaresume.test.dao.find.mock qui instancie en Java classique les Services et DAO et allons gérer l’instanciation des Services/DAO via Spring. Copiez collez le projet org.dynaresume.test.dao.find.mock et renommez le en org.dynaresume.test.dao.find.mock.spring.
Librairie
Ajoutez les JARs Spring (Core) au projet:
JAR | Description |
---|---|
lib/spring-orm/com.springsource.org.aopalliance-1.0.0.jar | |
lib/spring-orm/com.springsource.org.apache.commons.logging-1.1.1.jar | |
lib/spring-orm/org.springframework.aop-2.5.6.A.jar | |
lib/spring-orm/org.springframework.beans-2.5.6.A.jar | |
lib/spring-orm/org.springframework.context-2.5.6.A.jar | |
lib/spring-orm/org.springframework.core-2.5.6.A.jar |
applicationContext.xml
En Java classique, l‘implémentation Mock de la DAO est créé puis injecté à l’implementation UserService :
// 1. Create DAO UserDAO userDAO = new UserDAOMock(); // 2. Create Service and inject DAO UserService userService = new UserServiceImpl(); ((UserServiceImpl) userService).setUserDAO(userDAO);
Avec Spring, ce code est effectué par le conteneur Spring en déclarant les DAO et Service (dans un bean XML Spring ou via des annotations @Repository et @Service). Ici nous allons déclarer les DAO et les Services dans des bean Spring. Créez le fichier applicationContext.xml dans le package org.dynaresume comme suit :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="userDAO" class="org.dynaresume.dao.mock.UserDAOMock" /> <bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl"> <property name="userDAO" ref="userDAO" /> </bean> </beans>
FindAllUsersDAOMockTest (avec Spring)
Ici nous allons créer la classe de Test (Main) qui recupère le service UserService via le conteneur Spring. Il faut dans un premier temps charger le fichier applicationContext.xmll :
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("org/dynaresume/applicationContext.xml");
puis récupérer une instance de UserService déclaré dans le fichier XML applicationContext.xml :
UserService userService = (UserService) applicationContext.getBean("userService");
Créez la classe org.dynaresume.test.dao.find.FindAllUsersDAOMockTest comme suit :
package org.dynaresume.test.dao.find; import java.util.Collection; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class FindAllUsersDAOMock { public static void main(String[] args) { // 1. Load XML Spring file ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "org/dynaresume/applicationContext.xml"); // 2. Get service from Spring container UserService userService = (UserService) applicationContext .getBean("userService"); // 3. Use the service Collection<User> users = userService.findAllUsers(); for (User user : users) { System.out.println("User [id=" + user.getId() + "login=" + user.getLogin() + ", password=" + user.getPassword() + "]"); } } }
Dans ce cas-ci Spring joue le rôle de factory de classe DAO, Services.
Services/DAO – find* Mock (avec Spring + Annotations)
Dans cette section nous allons reprendre le projet org.dynaresume.test.dao.find.mock.spring ou les Services/DAO sont déclarés via des bean XML dans le fichier applicationContext.xml Spring et utiliser les annotations Spring @Repository et @Service pour éviter de devoir effectuer une déclaration XML des bean. Copiez collez le projet org.dynaresume.test.dao.find.mock.spring et renommez le en org.dynaresume.test.dao.find.mock.spring.annotations.
UserDAOMock – @Repository
Nous avons déclaré notre implémentation de DAO UserDAO en XML, identifié par l’ID userDAO comme ceci :
<bean id="userDAO" class="org.dynaresume.dao.mock.UserDAOMock" />
Ceci peut s’écrire de la même manière avec l’annotation @Repository de Spring :
... import org.springframework.stereotype.Repository; @Repository("userDAO") public class UserDAOMock implements UserDAO { ...}
Modifiez la classe org.dynaresume.dao.mock.UserDAOMock comme suit :
package org.dynaresume.dao.mock; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; import org.springframework.stereotype.Repository; @Repository("userDAO") public class UserDAOMock implements UserDAO { private Collection<User> users = null; public Collection<User> findAllUsers() { if (users == null) { users = createUsers(); } return users; } private Collection<User> createUsers() { List<User> users = new ArrayList<User>(); User user = new User("angelo", ""); user.setId(1); users.add(user); user = new User("djo", ""); user.setId(2); users.add(user); user = new User("keulkeul", ""); user.setId(3); users.add(user); user = new User("pascal", ""); user.setId(4); users.add(user); return users; } }
UserServiceImpl – @Service
Nous avons déclaré notre implémentation de DAO UserDAO en XML, identifié par l’ID userService comme ceci :
<bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl"> <property name="userDAO" ref="userDAO" /> </bean>
Ceci peut s’écrire de la même manière avec l’annotation @Service de Spring et @Resource de javax :
... import javax.annotation.Resource; .. import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Resource(name="userDAO") private UserDAO userDAO; ...}
L’annotation @Resource permet d’injecter une instance UserDAO identifié par le name userDAO dans l’implémentation de UserServiceImpl. Dans notre cas il est aussi possible de ne pas renseigner name de @Resource car le nom de l’instance DAO suit le nommage de l’interface. L’annotation @Resource fait partie de la JSR-250. Il est possible aussi d’utiliser l’annotation Spring @Autowired.
Modifiez la classe org.dynaresume.dao.mock.UserDAOMock comme suit :
package org.dynaresume.services.impl; import java.util.Collection; import javax.annotation.Resource; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Resource(name="userDAO") private UserDAO userDAO; public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } public Collection<User> findAllUsers() { return userDAO.findAllUsers(); } }
applicationContext.xml
Le fichier applicationContext.xml doit être modifié pour enlever les déclarations XML des beans DAO et Service mais surtout pour définir à Spring qu’il doit scruter des classes d’un package donné pour que Spring recherche les classes qui ont les annotations @Repository et @Service et instancie les classes en conséquences. Pour cela modifiez le fichier applicationContext.xml comme suit :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:component-scan base-package="org.dynaresume" /> </beans>
La déclaration :
<context:component-scan base-package="org.dynaresume" />
permet d’indiquer à Spring qu’il doit scruter les classes incluses dans le package org.dynaresume pour retrouver les annotions de nos DAO (@repository) et Service (@Service).
Services/DAO – find* JPA (sans Spring)
Dans cette section nous allons mettre en place une DAO avec JPA UserDAOJpa qui permet de retourner la liste de User de la table T_USER de la base de données dynaresume. Les Service/DAO seront instanciés en Java classique. Nous utiliserons l’implémentataion JPA/EclipseLink et la base H2. Copiez collez le projet org.dynaresume.test.dao.find.mock et renommez le en org.dynaresume.test.dao.find.jpa.
libraires JPA
Ajoutez les librairies API JPA :
JAR | Description |
---|---|
lib/javax/com.springsource.javax.persistence-2.0.0.jar | API JPA « OSGifiée ». |
Ajoutez les librairies Implémentation JPA/EclipseLink :
JAR | Description |
---|---|
lib/jpa-eclipselink/org.eclipse.persistence.antlr-2.0.0.jar | |
lib/jpa-eclipselink/org.eclipse.persistence.asm-2.0.0.jar | |
lib/jpa-eclipselink/org.eclipse.persistence.core-2.0.0.jar | |
lib/jpa-eclipselink/org.eclipse.persistence.jpa-2.0.0.jar |
Ajoutez la librairie H2 :
JAR | Description |
---|---|
lib/h2/com.springsource.org.h2-1.0.71.jar | Base H2 « OSGifiée ». |
Domain – User
Modifiez la classe org.dynaresume.domain.User avec le mapping JPA comme suit :
package org.dynaresume.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "T_USER") public class User { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "USR_ID_N") private long id; @Column(name = "USR_LOGIN_C") private String login; @Column(name = "USR_PASSWORD_C") private String password; public User() { } public User(String login, String password) { setLogin(login); setPassword(password); } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getLogin() { return login; } public void setLogin(String login) { this.login = login; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
persistence.xml
Ici nous allons créer le fichier persistence.xml dans le répertoire META-INF que vous devez créer dans le répertoire src (et pas à la racine du projet comme les bundles OSGi). En effet ce fichier doit se retrouver après compilation du projet dans le répertoire bin. Créez le fichier persistence.xml avec le contenu suivant :
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0"> <persistence-unit name="dynaresume-eclipselink-h2" transaction-type="RESOURCE_LOCAL"> <provider> org.eclipse.persistence.jpa.PersistenceProvider </provider> <class>org.dynaresume.domain.User</class> <properties> <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:h2:C:/db/h2/dynaresume" /> <property name="javax.persistence.jdbc.user" value="sa" /> <property name="javax.persistence.jdbc.password" value="" /> <property name="eclipselink.target-database" value="org.eclipse.persistence.platform.database.H2Platform" /> </properties> </persistence-unit> </persistence>
DAO – UserDAO/UserDAOJpa
Implémentation UserDAOJpa
Créez l’implémentation JPA de UserDAOJpa avec la classe org.dynaresume.dao.jpa.UserDAOJpa comme suit :
package org.dynaresume.dao.jpa; import java.util.Collection; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Query; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; public class UserDAOJpa implements UserDAO { private EntityManagerFactory entityManagerFactory; public void setEntityManagerFactory( EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } public Collection<User> findAllUsers() { EntityManager entityManager = null; try { entityManager = entityManagerFactory.createEntityManager(); Query query = entityManager.createQuery("select u from " + User.class.getSimpleName() + " u"); return query.getResultList(); } finally { if (entityManager != null) { entityManager.close(); } } } }
Cette DAO JPA doit etre initialisé avec un entityManagerFactory et a la charge d’ouvrir/fermer des connections (EntityManager).
FindAllUsersDAOJpaTest (sans Spring)
Ici nous allons créer la classe de Test (Main) qui instancie classiquement la DAO où l’on injecte l’entityManagerFactory et le Service ou l’on injecte la DAO. Créez la classe org.dynaresume.test.dao.find.FindAllUsersDAOJpaTest comme suit :
package org.dynaresume.test.dao.find; import java.util.Collection; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import org.dynaresume.dao.UserDAO; import org.dynaresume.dao.jpa.UserDAOJpa; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.dynaresume.services.impl.UserServiceImpl; public class FindAllUsersDAOJpaTest { public static void main(String[] args) { String persistentUnitName = "dynaresume-eclipselink-h2"; EntityManagerFactory entityManagerFactory = null; try { // 1. Create Jpa EntityManagerFactory entityManagerFactory = Persistence .createEntityManagerFactory(persistentUnitName); // 2. Create DAO and inject entityManagerFactory UserDAO userDAO = new UserDAOJpa(); ((UserDAOJpa) userDAO) .setEntityManagerFactory(entityManagerFactory); // 3. Create Service and inject DAO UserService userService = new UserServiceImpl(); ((UserServiceImpl) userService).setUserDAO(userDAO); // 4. Use the service Collection<User> users = userService.findAllUsers(); for (User user : users) { System.out.println("User [id=" + user.getId() + "login=" + user.getLogin() + ", password=" + user.getPassword() + "]"); } } catch (Exception e) { e.printStackTrace(); } finally { if (entityManagerFactory != null) { entityManagerFactory.close(); } } } }
Services/DAO – find* JPA (avec Spring)
Dans cette section nous allons utiliser Spring ORM pour gérer l’EntitymanagerFactory dans un conteneur JPA (joué par Spring). Nous verrons que la DAO JPA sera extrêmemnt simplifié car le code de gestion des ouvertures/fermeture de connections (EntityManager) ne devra plus être codé dans la DAO. Copiez collez le projet org.dynaresume.test.dao.find.mock.spring et renommez le en org.dynaresume.test.dao.find.jpa.spring.
- Ajoutez les JARs JPA + EclipseLink + H2 comme expliqué ici et ajouté les JAR de Spring ORM :
JAR Description lib/spring-orm/org.springframework.jdbc-2.5.6.A.jar lib/spring-orm/org.springframework.orm-2.5.6.A.jar lib/spring-orm/org.springframework.transaction-2.5.6.A.jar - Modifiez la classe User avec des annotations JPA comme expliqué ici.
- Créer le fichier persistence.xml comme expliqué ici.
- Créer la classe UserDAOJpa comme expliqué ici.
applicationContext-jpa.xml
Créez le fichier applicationContext-jpa.xml dans le package org.dynaresume comme suit :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="dynaresume-eclipselink-h2" /> </bean> <bean id="userDAO" class="org.dynaresume.dao.jpa.UserDAOJpa"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl"> <property name="userDAO" ref="userDAO" /> </bean> </beans>
Ici nous avons déclaré l’EntityManagerFactory via org.springframework.orm.jpa.LocalEntityManagerFactoryBean en lui indiquant le persistence-unit. La déclaration Spring :
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="dynaresume-eclipselink-h2" /> </bean>
est équivalent au code JPA :
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("dynaresume-eclipselink-h2");
Cette instance entityManagerFactory est injecté à la DAO UserDAOJpa :
<bean id="userDAO" class="org.dynaresume.dao.jpa.UserDAOJpa"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean>
FindAllUsersDAOJpaTest (avec Spring)
Créez la classe org.dynaresume.test.dao.find.FindAllUsersDAOJpaTest qui charge le fichier applicationContext-jpa.xml comme suit :
package org.dynaresume.test.dao.find; import java.util.Collection; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class FindAllUsersDAOJpaTest { public static void main(String[] args) { // 1. Load XML Spring file ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "org/dynaresume/applicationContext-jpa.xml"); // 2. Get service from Spring container UserService userService = (UserService) applicationContext .getBean("userService"); // 3. Use the service Collection<User> users = userService.findAllUsers(); for (User user : users) { System.out.println("User [id=" + user.getId() + "login=" + user.getLogin() + ", password=" + user.getPassword() + "]"); } } }
Services/DAO – find* (avec Spring) – @PersistenceUnit
Dans le projet org.dynaresume.test.dao.find.jpa.spring nous avons utilisé le mécanisme d’injection pour renseigner la factory entityManagerFactory à la DAO :
<bean id="userDAO" class="org.dynaresume.dao.jpa.UserDAOJpa"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean>
Il est possible d’éviter de décrire cette injection en utilisant du code pur JPA (qui pourrait être utilisé aussi dans un conteneur EJB3) en utilisant l’annotation javax @PersistenceUnit. Copiez collez le projet org.dynaresume.test.dao.find.jpa.spring et renommez le en org.dynaresume.test.dao.find.jpa.spring_PersistenceUnit.
UserDAOJpa
Modifiez la classe UserDAOJpa en ajoutant l’annotation @PersistenceUnit sur le setter de l’EntityManagerFactory :
public class UserDAOJpa implements UserDAO { private EntityManagerFactory entityManagerFactory; @PersistenceUnit public void setEntityManagerFactory( EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } ...
Il est aussi possible de placer l’annotation @PersistentUnit au niveau du champs :
public class UserDAOJpa implements UserDAO { @PersistenceUnit private EntityManagerFactory entityManagerFactory; ...
Voici le code complet de la classe UserDAOJpa :
package org.dynaresume.dao.jpa; import java.util.Collection; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceUnit; import javax.persistence.Query; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; public class UserDAOJpa implements UserDAO { private EntityManagerFactory entityManagerFactory; @PersistenceUnit public void setEntityManagerFactory( EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } public Collection<User> findAllUsers() { EntityManager entityManager = null; try { entityManager = entityManagerFactory.createEntityManager(); Query query = entityManager.createQuery("select u from " + User.class.getSimpleName() + " u"); return query.getResultList(); } finally { if (entityManager != null) { entityManager.close(); } } } }
applicationContext-jpa.xml
Modifiez dans le fichier applicationContext-jpa.xml la déclaration de la DAO en enlevant l’élement property qui permet d’injecter l’entityManagerFactory, ce qui donne :
<bean id="userDAO" class="org.dynaresume.dao.jpa.UserDAOJpa" />
Relancez FindAllUsersDAOJpaTest et vous verrez l’erreur :
INFO: Building JPA EntityManagerFactory for persistence unit 'dynaresume-eclipselink-h2'
Exception in thread "main" java.lang.NullPointerException
at org.dynaresume.dao.jpa.UserDAOJpa.findAllUsers(UserDAOJpa.java:39)
at org.dynaresume.services.impl.UserServiceImpl.findAllUsers(UserServiceImpl.java:31)
at org.dynaresume.test.dao.find.FindAllUsersDAOJpaTest.main(FindAllUsersDAOJpaTest.java:35)
Cette erreur est du au fait que les annotations JPA ne sont pas activées. Pour cela ajoutez la déclaration du processor PersistenceAnnotationBeanPostProcessor dans le fichier applicationContext-jpa.xml comme ceci :
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
Voici le fichier applicationContext-jpa.xml complet : :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="dynaresume-eclipselink-h2" /> </bean> <bean id="userDAO" class="org.dynaresume.dao.jpa.UserDAOJpa" /> <bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl"> <property name="userDAO" ref="userDAO" /> </bean> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> </beans>
Relancez FindAllUsersDAOJpaTest et vous verrez la liste des User dans la console.
Services/DAO – find* (avec Spring) – @PersistenceContext
Jusqu’a maintenant on peut se dire qu’il n’ y a pas vraiment d’interêt d’utiliser Spring ORM. Le vrai interêt d’utiliser Spring ORM est qu’il est possible d’injecter l’EntityManager à la DAO (et pas la factory EntityManagerFactory) à l’aide de l’annotation @PersistentContext et l’ouverture/fermeture de l’EntityManager ne s’effectue plus dans la DAO. C’est le conteneur Spring qui s’occupe de cela. Copiez collez le projet org.dynaresume.test.dao.find.jpa.spring_PersistenceUnit et renommez le en org.dynaresume.test.dao.find.jpa.spring_PersistenceContext.
UserDAOJpa
La classe UserDAOJpa s’occupait jusqu’à maintenant de fermer/ouvrir l’EntityManager dans sa méthode UserDAOJpa#findAllUsers () :
... public Collection<User> findAllUsers() { EntityManager entityManager = null; try { entityManager = entityManagerFactory.createEntityManager(); Query query = entityManager.createQuery("select u from " + User.class.getSimpleName() + " u"); return query.getResultList(); } finally { if (entityManager != null) { entityManager.close(); } } }
Avec l’utilisation de l’annotation JPA @PersistentContext qui permet d’injecter l’EntityManager à la DAO, le code de la méthode UserDAOJpa#findAllUsers () devient :
public Collection<User> findAllUsers() { Query query = entityManager.createQuery("select u from " + User.class.getSimpleName() + " u"); return query.getResultList(); }
Modifiez la classe UserDAOJpa comme suit :
package org.dynaresume.dao.jpa; import java.util.Collection; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; public class UserDAOJpa implements UserDAO { @PersistenceContext private EntityManager entityManager; public Collection<User> findAllUsers() { Query query = entityManager.createQuery("select u from " + User.class.getSimpleName() + " u"); return query.getResultList(); } }
Ce code est du pur JPA et permet d’être utilisé dans un autre conteneur JPA comme EJB3 par exemple.
Service/DAO – create*
Dans cette section nous allons mettre en place le pattern Service/DAO qui utilise une connection en écriture (transaction).
Services/DAO – create* (sans Spring)
Dans cette section nous allons mettre en place une DAO avec JPA UserDAOJpa qui permet d’insérer un User dans la table T_USER de la base de données dynaresume. Les Service/DAO seront instanciés en Java classique. Les Transactions devront être gérés dans le code. Nous utiliserons implémentation JPA/EclipseLink et la base H2. Copiez collez le projet org.dynaresume.test.dao.find.jpa et renommez le en org.dynaresume.test.dao.create.jpa.
Ajoutez le JAR javax/com.springsource.javax.transaction-1.1.0.jar qui contient entre autre les exceptions de Transaction.
DAO – UserDAO/UserDAOMock
Ici nous allons ajoutez la méthode UserDAO#saveUser(User user) qui permet de sauvegarder le User en base de données.
Interface UserDAO
Modifiez l’interface org.dynaresume.dao.UserDAO comme suit :
package org.dynaresume.dao; import java.util.Collection; import org.dynaresume.domain.User; public interface UserDAO { Collection<User> findAllUsers(); User saveUser(User user); }
Implémentation UserDAOJpa+UserDAOMock
UserDAOJpa
La méthode saveUser doit être implémentée comme ceci :
public User saveUser(User user) { EntityManager entityManager = null; EntityTransaction transaction = null; try { entityManager = entityManagerFactory.createEntityManager(); transaction = entityManager.getTransaction(); transaction.begin(); entityManager.persist(user); transaction.commit(); return user; } catch(Exception e) { if (transaction != null) { transaction.rollback(); } if (e instanceof RuntimeException) { throw ((RuntimeException)e); } throw new RuntimeException(e); } finally { if (entityManager != null) { entityManager.close(); } } }
Vous pouvez remarquer que la gestion des Transactions (EntityTransaction) doit être codé explicitement. Modifiez la classe org.dynaresume.dao.jpa.UserDAOJpa comme suit :
package org.dynaresume.dao.jpa; import java.util.Collection; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.Query; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; public class UserDAOJpa implements UserDAO { private EntityManagerFactory entityManagerFactory; public void setEntityManagerFactory( EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } public Collection<User> findAllUsers() { EntityManager entityManager = null; try { entityManager = entityManagerFactory.createEntityManager(); Query query = entityManager.createQuery("select u from " + User.class.getSimpleName() + " u"); return query.getResultList(); } finally { if (entityManager != null) { entityManager.close(); } } } public User saveUser(User user) { EntityManager entityManager = null; EntityTransaction transaction = null; try { entityManager = entityManagerFactory.createEntityManager(); transaction = entityManager.getTransaction(); transaction.begin(); entityManager.persist(user); transaction.commit(); return user; } catch(Exception e) { if (transaction != null) { transaction.rollback(); } if (e instanceof RuntimeException) { throw ((RuntimeException)e); } throw new RuntimeException(e); } finally { if (entityManager != null) { entityManager.close(); } } } }
UserDAOMock
Pour que le projet compile la classe UserDAOMock doit implémenter la méthode saveUser :
public User saveUser(User user) { users.add(user); user.setId(users.size() + 1); return user; }
Service – UserService/UserServiceImpl
Ici nous allons ajouter la méthode createUser qui appellera la méthode saveUser de la DAO UserDAO.
Interface UserService
Modifiez l’interface org.dynaresume.services.UserService comme suit :
package org.dynaresume.services; import java.util.Collection; import org.dynaresume.domain.User; public interface UserService { Collection<User> findAllUsers(); User createUser(String login, String password); }
Implémentation UserService
Modifiez l’implémentation du service UserService avec la classe org.dynaresume.services.impl.UserServiceImpl comme suit :
package org.dynaresume.services.impl; import java.util.Collection; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; public class UserServiceImpl implements UserService { private UserDAO userDAO; public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } public Collection<User> findAllUsers() { return userDAO.findAllUsers(); } public User createUser(String login, String password) { User user = new User(login, password); return userDAO.saveUser(user); } }
CreateUserDAOJpaTest
Créer la classe de Test de création d’un User avec la classe org.dynaresume.test.dao.create.CreateUserDAOJpaTest :
package org.dynaresume.test.dao.create; import java.util.Collection; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import org.dynaresume.dao.UserDAO; import org.dynaresume.dao.jpa.UserDAOJpa; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.dynaresume.services.impl.UserServiceImpl; public class CreateUserDAOJpaTest { public static void main(String[] args) { String persistentUnitName = "dynaresume-eclipselink-h2"; EntityManagerFactory entityManagerFactory = null; try { // 1. Create Jpa EntityManagerFactory entityManagerFactory = Persistence .createEntityManagerFactory(persistentUnitName); // 2. Create DAO and inject entityManagerFactory UserDAO userDAO = new UserDAOJpa(); ((UserDAOJpa) userDAO) .setEntityManagerFactory(entityManagerFactory); // 3. Create Service and inject DAO UserService userService = new UserServiceImpl(); ((UserServiceImpl) userService).setUserDAO(userDAO); // 4. Create User userService.createUser("jpa-user (DAO-without Spring)", ""); // 5. Display users Collection<User> users = userService.findAllUsers(); for (User user : users) { System.out.println("User [id=" + user.getId() + "login=" + user.getLogin() + ", password=" + user.getPassword() + "]"); } } catch (Exception e) { e.printStackTrace(); } finally { if (entityManagerFactory != null) { entityManagerFactory.close(); } } } }
Relancer et vous pourrez constater que le User de login « jpa-user (DAO-without Spring) » est inséré dans la table T_USER.
Services/DAO – create(avec Spring)
Dans cette section nous allons mettre en place une DAO avec JPA UserDAOJpa qui permet d’insérer un User dans la table T_USER de la base de données dynaresume. Les Service/DAO seront instanciés à l’aide de Spring. Les Transactions seront gérés par le conteneur Spring (en utilisant les annotations @Transactionnal).
Copiez collez le projet org.dynaresume.test.dao.find.jpa.spring_PersistenceContext et renommez le en org.dynaresume.test.dao.create.jpa.spring.
- Ajoutez le JAR javax/com.springsource.javax.transaction-1.1.0.jar qui contient entre autre les exceptions de Transaction.
- Modifiez l’interface UserDAO pour ajouter la méthode saveUser comme expliqué ici.
- Modifiez la classe UserDAOMock (pour que le projet compile) comme expliqué ici.
- Modifiez l’interface UserService pour ajouter la méthode createUser comme expliqué ici.
- Modifiez la classe UserServiceImpl pour ajouter la méthode saveUser comme expliqué ici.
DAO
UserDAOJpa
Lorsque l’on utilise JPA en mode « standalone », la méthode saveUser de la DAO UserDAO est implémenté en JPA comme ceci :
public User saveUser(User user) { EntityManager entityManager = null; EntityTransaction transaction = null; try { entityManager = entityManagerFactory.createEntityManager(); transaction = entityManager.getTransaction(); transaction.begin(); entityManager.persist(user); transaction.commit(); return user; } catch(Exception e) { if (transaction != null) { transaction.rollback(); } if (e instanceof RuntimeException) { throw ((RuntimeException)e); } throw new RuntimeException(e); } finally { if (entityManager != null) { entityManager.close(); } } }
Avec Spring ORM, la gestion des Transactions ne s’effectue pas par le code, ce qui simplifie grandement le code. La méthode saveUser devient :
public User saveUser(User user) { entityManager.persist(user); return user; }
Modifiez la classe org.dynaresume.dao.jpa.UserDAOJpa comme suit :
package org.dynaresume.dao.jpa; import java.util.Collection; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; public class UserDAOJpa implements UserDAO { @PersistenceContext private EntityManager entityManager; public Collection<User> findAllUsers() { Query query = entityManager.createQuery("select u from " + User.class.getSimpleName() + " u"); return query.getResultList(); } public User saveUser(User user) { entityManager.persist(user); return user; } }
CreateUserDAOJpaTest
Créer la classe de Test de création d’un User avec la classe org.dynaresume.test.dao.create.CreateUserDAOJpaTest :
package org.dynaresume.test.dao.create; import java.util.Collection; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class CreateUserDAOJpaTest { public static void main(String[] args) { // 1. Load XML Spring file ApplicationContext applicationContext = new ClassPathXmlApplicationContext( "org/dynaresume/applicationContext-jpa.xml"); // 2. Get service from Spring container UserService userService = (UserService) applicationContext .getBean("userService"); // 4. Create User userService.createUser("jpa-user (DAO-with Spring)", ""); // 5. Display users Collection<User> users = userService.findAllUsers(); for (User user : users) { System.out.println("User [id=" + user.getId() + "login=" + user.getLogin() + ", password=" + user.getPassword() + "]"); } } }
Relancez et vous pourrez constater que le User n’a pas été inséré. En effet a aucun moment nous avons indiqué que la méthode UserService#createUser était transactionnel. C’est ce que nous allons effectuer via l’annotation Spring @Transactionnal.
@Transactionnal
Nous allons indiquer que l’appel de la méthode createUser du service doit ouvrir une transaction. Pour cela nous utiliserons l’annotation @Transactional comme ceci :
@Transactional public User createUser(String login, String password) { ... }
Pour que cette annotation soit effective, nous devons déclarer dans le fichier XML Spring :
- un gestionnaire de transaction JPA qui est lié à l’entityManagerFactory :
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean>
- activez les annotations de type @Transactionnal qui s’effectue via cette déclaration :
<tx:annotation-driven transaction-manager="txManager" />
applicationContext-jpa.xml
Modifiez le fichier applicationContext-jpa.xml comme suit :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="dynaresume-eclipselink-h2" /> </bean> <bean id="userDAO" class="org.dynaresume.dao.jpa.UserDAOJpa" /> <bean id="userService" class="org.dynaresume.services.impl.UserServiceImpl"> <property name="userDAO" ref="userDAO" /> </bean> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <tx:annotation-driven transaction-manager="txManager" /> <bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> </beans>
UserServiceImpl
Ici nous allons rendre transactionnel le service createUser avec l’annotation @Transactionnal. Pour cela modifiez la classe UserServiceImpl comme suit :
package org.dynaresume.services.impl; import java.util.Collection; import org.dynaresume.dao.UserDAO; import org.dynaresume.domain.User; import org.dynaresume.services.UserService; import org.springframework.transaction.annotation.Transactional; @Transactional(readOnly=true) public class UserServiceImpl implements UserService { private UserDAO userDAO; public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } public Collection<User> findAllUsers() { return userDAO.findAllUsers(); } @Transactional public User createUser(String login, String password) { User user = new User(login, password); return userDAO.saveUser(user); } }
REMARQUES :
- @Transactional(readOnly=true) a été aussi placé dans au niveau de la classe, ce qui n’est pas obligatoire, mais cela permet de dire que par défaut toutes les méthodes de la classe sont en readOnly.
- L’annotation @Transactional a été place au niveau du service. Elle aurait peut être placé aussi dans la DAO mais il vaut mieux ouvrir une transaction au niveau du service qu’au niveau de la DAO (pour enchainer plusieurs DAO à la suite dans une même transaction).
Relancer CreateUserDAOJpaTest et vous pourrez constater que le User de login « jpa-user (DAO-with Spring) » est inséré dans la table T_USER.
Conclusion
Dans ce billet nous avons vu l’interêt d’utiliser le support Spring pour JPA qui permet de gérer l’EntityManagerFactory dans un conteneur JPA, autrement dit :
- gérer les ouvertures/fermetures des EntityManager.
- gérer les ouvertures/fermetures des EntityTransaction (commit/rollback).
Nous avons utilisé LocalEntityManagerFactoryBean pour déclarer l’EntityManagerFactory qui est la configuration la plus basique du support Spring de JPA. Dans le prochain billet nous allons utilisé LocalContainerEntityManagerFactoryBean qui permettra de configurer le provider (implémentation JPA) et les properties (URL de la base….) via des beans Spring et de pouvoir utiliser une déclaration persistence-unit du fichier META-INF/persistence.xml pour toutes les bases et toutes les implémentation JPA.
Pour terminer je vous conseille de lire Generics example: the Data Access Object qui explique comment mettre en place un système générique de DAO qui est d’ailleurs utilisé dans Dynaresume.
Vous pouvez lire le billet suivant [step18].