Mise en place de JPA managé par Spring

Spring Bonjour,
L’utilisation de JPA dans un environnement non managé peut se revéler délicate et problématique (la fameuse LazyInitException en est un exemple).

Ca vient surtout de la gestion de la session de persistence, qui dans le mode non-managé doit être gérée à la main par le développeur, or la méthode la plus simple qui consiste à ouvrir une session de persistence chaque fois qu’on en a besoin est pour lemoins inefficace, lourde, et ne marche pas avec le chargement lazy, ne permet pas une gestion correcte des transactions (étalées sur plusieurs actions), etc.

C’est pour cela qu’il vaut mieux (faut) utiliser un conteneur pour la gestion de la session de persistence (JPA, Hibernate, etc.) comme par exemple Spring.

Je vais présenter rapidement dans ce billet les étapes à suivre pour configurer Spring 2.5 et JPA dans le cadre d’une application Web (ne nécessite pas un serveur d’application, marche sur Tomcat et Jetty).

Je tiens juste à neter un point important: Je n’ai réussi à faire fonctinner ce qui suit qu’avec Hibernate comme fournisseur de persistance.
Je n’ai pas réussi à le faire avec Toplink à cause de soucis de LoadTimeWeaving, et je n’ai pas testé avec OpenJPA et JPox.

Je vais utiliser hsqldb comme base de données ici, mais il est facile de chnager la chose pour utiliser MySQL par exemple ou tout autre SGBD.

On commence par créer une application web et ajouter les dépendances suivantes:

  • Spring.jar
  • hsqldb.jar
  • javassist.jar
  • antlr-2.7.6.jar
  • cglib-nodep-2.1_3.jar
  • dom4j-1.6.1.jar
  • hibernate-annotations.jar
  • hibernate-commons-annotations.jar
  • hibernate-entitymanager.jar
  • hibernate3.jar
  • jboss-archive-browsing.jar
  • jta.jar
  • persistence.jar

Ensuite, déclarer les listeners de Spring dans web.xml:

	
<listener>	
		<listener-class>	
				org.springframework.web.context.ContextLoaderListener	
		</listener-class>	
</listener>	
<listener>	
		<listener-class>	
				org.springframework.web.context.request.RequestContextListener	
		</listener-class>	
</listener>	

Il faut aussi créer le fichier de configuration de Spring: applicationContext.xml dans WEB-INF pour y déclarer:

  • La source de données qu’on va utiliser (hsqldb ici).
  • Le gestionnaire de l’EntityManager
  • Le gestionnaire de transactions
  • La DI par annotations au lieu de l’XML

Voici donc ce que ça donne:

	
<?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:p="http://www.springframework.org/schema/p"	
		xmlns:context="http://www.springframework.org/schema/context"	
		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/beans/spring-beans-2.5.xsd
				http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd	http://www.springframework.org/schema/context/spring-context-2.5.xsd
				http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
	
		<bean id="dataSource"	
				class="org.springframework.jdbc.datasource.DriverManagerDataSource"	
				p:driverClassName="org.hsqldb.jdbcDriver"	
				p:url="jdbc:hsqldb:file:db/test" p:username="sa" p:password="" />	
	
		<bean id="entityManagerFactory"	
				class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"	
				p:dataSource-ref="dataSource" p:persistence-unit-name="jpa">	
				<property name="jpaVendorAdapter">	
						<bean	
								class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"	
								p:database="HSQL"	
								p:databasePlatform="org.hibernate.dialect.HSQLDialect"	
								p:showSql="true" p:generate-ddl="true" />	
				</property>	
		</bean>	
	
		<bean id="transactionManager"	
				class="org.springframework.orm.jpa.JpaTransactionManager"	
				p:entityManagerFactory-ref="entityManagerFactory" />	
				
		<context:annotation-config />	
		
		<context:component-scan base-package="package-de-base" />	
		
		<tx:annotation-driven />	
</beans>	

ça ne devrait pas poser de problème si vous connaissez déjà Spring.
La seule chose à signaler est de remplacer “package-de-base” par le nom de package parent des tous les classes bénificiant de DI.

Voici maintenant persistence.xml:

<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_1_0.xsd"
		version="1.0">	
	
		<persistence-unit name="jpa" transaction-type="RESOURCE_LOCAL">	
				<class>model.dto.User</class>	
				<class>model.dto.Log</class>	
		</persistence-unit>	
	
</persistence>

Veuillez noter qu’on utilise RESOURCE_LOCAL au lieu de JTA, qu’on ne déclare aucunepropriété spécifique à Hibernate (url, usr, pwd, etc.) vu que c’est déjà fait dans applicationContext et qu’on utilise le même nom pour l’unité de persistence que dans applicationContext.xml.

Je vais maintenant montrer comme créer un DAO qui utilise un EntityManager injecté par Spring:

@Repository
public class UserDao {
	@PersistenceContext(type = PersistenceContextType.EXTENDED)
	private EntityManager em;

	public User findById(Long id) {
		return em.find(User.class, id);
	}
	
	@SuppressWarnings("unchecked")
	public List<User> findAll() {
		return em.createQuery("select u from User u").getResultList();
	}

	public User makePersistent(User entity) {
		return em.merge(entity);
	}

	public void makeTransient(User entity) {
		em.remove(entity);
	}
}

Deux choses à signaler:

  • J’annote le DAO par @Repository pour le marquer en tant que Spring Bean (plus autres choses, comme la translation des exceptions, etc.).
  • J’annote l’EntityManager par @PersistenceContext(type = PersistenceContextType.EXTENDED) pour indiquer à Spring qu’il doit l’injecter et qu’en plus il doit être en mode étendu, c’est à dire que sa durée de vie est celle de l’application, et non pas crée à la demande.

Le reste est du JPA ordinaire.
C’est avec le mode étendu qu’on bébéficie de la puissance de Spring/JPA en mode managé. Plus de LazyInitException ou encore de Detached Entity ;)

Passons maitnenant à la gestion des transactions.
Je ne l’ai pas mis dans le DAO (comme on le faisiat en mode non managé) car ça n’a pas de sens.
Une transaction doit englober une opération de la couche métier, et nnon pas dans la couche DAO pour être vraiment utile et garantir un état homogène des données.

Suppososns donc qu’on a une classe UserService de la couche Service doit voici le code:

	
@Service	
public class UserService {	
	 @Resource	
	 private UserDao userDao;	
	
	 @Resource	
	 private LogDao logDao;	
	
	 @Transactional	
	 public User createUser(user){	
	   User res=userDao.makePersistent(user);	
	   logDao.makePersistent(new Log("Created user "+user));	
	   return res;	
	 }	
}	

Place aux explications:

  • J’ai annoté la classe avec @Service pour la marquer en tant que Spring Bean, et plus précisément comme faisant partie de la couche Service.
  • J’ai déclaré deux champs (de type MachinDao) annotés avec @Resource pour les marquer comme dépendances que Spring doit remplir
  • J’ai annoté la méthode createUser avec @Transactional, pour indiquer à Spring qu’il doit l’englober dans une trasaction.

Ainsi, on aura jamais le cas ou l’utilisateur ne sera pas crée à cause d’une erreur tandis qu’une entrée dans le log a été crée.

Voilou voili ! C’est la fin de ce (long long) billet, en attendant que je pondes un article sur la chose.

Une application exemple avec les sources est disponible en téléchargement ici.

—-

About these ads

One Response to Mise en place de JPA managé par Spring

  1. Pingback: Application exemple (JPA managé par Spring) « Jawher's Blog

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: