juin
2008
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/context 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 ListfindAll() {
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.
salut,
je viens de découvrir cet article suite à une discussion sur le forum concernant la gestion du lazy loading par spring [1].
J’en profite pour rebondir sur ce que proposait N!co (même si ça date pas mal tout ça…) avec l’utilisation de JpaDaoSupport. C’est justement ce que j’utilisais et j’ai pas réussi à mettre en place le lazy loading puisqu’on utilise plus directement l’Entity Manager, où mettre l’annotation @PersistenceContext(type = PersistenceContextType.EXTENDED) qui va bien?
J’ai ensuite trouvée une discussion[2] sur le forum Spring source Community qui cite la doc Spring déconseillant l’utilisation de JpaDaoSupport…
Je m’apprête donc à refactoriser mon code (ça va que j’en suis au début du projet avec seulement 3 DAO et 5 Services…) pour ne plus utiliser JpaDaoSupport, mais bien l’entityManager et les annotations explicites à la place.
Cet article me sera fort utile dans ce sens, en espérant qu’il n’est pas trop « out-of-date »
Désolée, je sais pas comment faire un jolie lien dans les commentaires :/
Voici le lien vers le sujet du forum qui m’a menée jusqu’ici :
[1] http://www.developpez.net/forums/d1089614/java/general-java/spring/gerer-lazy-loading/
et le lien vers la discussion sur le forum spring :
[2] http://forum.springsource.org/showthread.php?45578-How-to-inject-EntityManager-in-JpaDaoSupport-with-annotations&p=158581#post158581
Salut
Je me demandais : Si on utilise Spring sans XML (avec le scanner de bean definition et les annotations), il y a un moyen de configuration les transactions ?
Le problème est surtout pour activer sans le XML ? Ou alors, est-ce qu’il y a moyen d’utiliser en même temps le scanner et un fichier XML ?
@bula.java:
Merci pour ces retours très intéressants
Tout à fait. j’ai loupé ça. Du coup, cette solution semble moins attrayante tout d’un coup
Je vais faire des tests avec le scope session histoire de voir ce que ça donne. Je modifierais ensuite le billet en conséquence.
C’est une solution partielle ça, dans la mesure où elle empêche les exceptions de type Lazy lors du rendu de la page résultat, mais elle gère pas les beans « submittés » lors de la requête suivante.
Ca fait un bail que j’ai pas utilisé JPA dans un projet ‘réel’, mais la dernière fois que j’ai travaillé avec (une application sans Spring), j’ai crée mon propre conteneur d’EntityManager qui gère son cycle-de-vie. Les DAOs vont ensuite chercher l’EM depuis ce conteneur. C’est pas très propre je sais, mais …
Pour le problème de « la requête suivante », j’use (et j’abuse) du em.merge.
Merci encore pour tes retours
Très bon post.
J’ai juste une remarque et une question …
Une note dans la doc Spring 2.5.5 (p 311) indique que l’utilisation de @PersistenceContext(type = PersistenceContextType.EXTENDED) n’est doit pas se faire en multi-thread.
Ton composant UserDao ne pourrait donc pas être un bean Spring singleton.
Question : en passant à PersistenceContextType.TRANSACTION, le lazy loading n’est plus assurée (la session se trouve fermée).
Pour maintenir, la session ouverte, on doit pouvoir utiliser OpenSessionInViewInterceptor
(http://www.jroller.com/kbaum/entry/orm_lazy_initialization_with_dao).
As tu trouver une autre solution ?
Merci.
Extrait :
Note that the @PersistenceContext annotation has an optional attribute type, which defaults to
PersistenceContextType.TRANSACTION. This default is what you need to receive a « shared EntityManager »
proxy. The alternative, PersistenceContextType.EXTENDED, is a completely different affair: This results in a
so-called « extended EntityManager », which is not thread-safe and hence must not be used in a concurrently
accessed component such as a Spring-managed singleton bean. Extended EntityManagers are only supposed to
be used in stateful components that, for example, reside in a session, with the lifecycle of the EntityManager
not tied to a current transaction but rather being completely up to the application.
Merci à toi aussi ellene pour ce retour
content que ce billet te soit utile.
Merci pour cet exemple cela a permis de régler mon problème de Lazy-Loading. L’article est clair et bien expliqué.
Merci
C’est dans la boite
Désolé pour le reatrd, mais c’est chaud au boulot actuellement et je viens de changer ma machine à la maison, et je suis encore en train de rétablir mon environnement.
Vais essayer de fournir un projet type le plus tôt que possible.
J’ai réussi a charger le contexte.
Petit souci, comment utiliser mon Service dans une servlet ou un controler Spring?
Merci
Est ce que c’est possible d’avoir l’intégralité des fichiers de conf car j’ai problème de démarrage du contexte
persistence.xml
web.xml
et applicationContext.xml
Merci
Je serai preneur aussi d’une application de base avec un bean et par exemple l’affichage dans une JSP de la liste d’user par exemple.
A défaut, @Resource n’est pas reconnu chez moi.
C’est dans quel lib? dans le JDK?
Moi j’ai le JDK 1.5 et ne peut changer.
Si @Resource provient du 1.6 que faire pour rester en 1.5?
Désolé d’insister mais auriez vous un projet type ou un lien montrant un exemple d’application complète ?
Merci d’avance
Bonjour,
Très bonne idée en effet.
Je vais voir ce que je peux faire.
Bonjour,
Je suis un peu nouveau avec Spring (l’habitude des EJB3) j’aimerais réaliser une application sans EJB3 et je cherche quelque chose qui y ressemble avec spring… Je suis tombé sur ce billet qui m’a particulièrement intéressé.
Je voulais savoir si vous n’aviez pas un projet type exemple à fournir pour appuyer cet exemple, car j’ai beau essayer de réaliser quelque chose sur cette exemple, j’ai des pbs de création de ma BD à partir de mes entity…
Donc je voulais savoir si vous pouviez fournir un projet type en exemple afin de comprendre l’intégrité par l’exemple. Je teste actuellement les exemples de M. Tahé, mais je les trouve un peu « vieillo » par rapport à cela.
Merci d’avance pour me fournir un projet type exemple.
@n!co: C’est vrai qu’avec la méthode que tu proposes, ça a l’air alléchant (en passant par le support de Spring). C’est déjà mieux que de bosser directement avec l’EntityManager.
Seul bémol: l’héritage, ce qui peut être génant dans des cas où l’on veut implémenter sa propre Hiérarchi de services … Je pense que passer par un template injecté par Spring serait plus ou moins equivalent en fonctionnalités mais en beaucoup plus flexible, non ?
Remarque que le besoin de changer de couche de persistance est réel dans mon cas: on maintient diverses versions de nos applications, chaque version utilise une couche de persistance différente (3 généralement: HSQLDB in-memory lors du dév, une version autonome avec JPA/MySQL + une dernière en Spring JDBC/MySQL car des clients lourds C++/MFC bossent en parallèle sur la même base … arf).
Mais je vais sérieusement creuser du côté des templates JPA de Spring (certainement pas des Support, des templates).
Je viens de tomber sur un débat du forum sur « jpa avec dao ou sans », tu y participes d’ailleurs et tu as aussi raison pour ton cas de figure.
Ici, je ne souhaites pas réouvrir un débat, mais rappeler qu’avec JPA il n’est plus utile d’utiliser un DAO pour une majorité de cas
Pour le lien sur le forum :
http://www.developpez.net/forums/showthread.php?t=462739
Tien ca ma appris comment utiliser les annotations pour faire du DI
Merci
Une remarque pour dire qu’avec l’avènement de JPA et Spring, le DAO tend à devenir un anti-pattern (en son sens strict avec un DAO par pojo)
L’entitymanager devient le « pseudo DAO » que l’on utilise directement dans le service métier, pour tous les pojos persistant.
Service que l’on peut simplifier un peu plus avec l’utilisation du JpaDaoSupport de spring.
public class UserService extends JpaDaoSupport { <br />
<br />
@Transactional <br />
public void createUser(User user){ <br />
getJpaTemplate().persist(user); <br />
getJpaTemplate().persist(new Log("Created user "+user)); <br />
} <br />
}
Ca limite a max le code à maintenir
Un billet qui ose poser la question et une recherche google sur ces deux mots clés regorge de discussion similaire.
http://www.infoq.com/news/2007/09/jpa-dao
C’est le design que nous avons appliqué sur notre projet et avec une bonne gestion du mapping et des transactions, on en oubli presque qu’il y a une gestion de persistance avec la base de données
Content que ça soit utile
You’re ouelcome
Super billet
Merci beaucoup, c’est très instructif