JPA : une magie qui se mérite (retour aux sources de JPA)

Cet article fait partie d’une série sur les technologies incluses dans Java EE 6. Vous pouvez également lire l’article d’introduction de cette série : Java EE 6 ici et maintenant et celui sur JSF : JSF je t’aime, moi non plus

L’une des technologies les plus populaires dans le monde Java EE est sans aucun doute JPA. Il suffit de voir le nombre de livres et d’articles sur le sujet. Toutefois, si JPA est très répandu, il s’agit aussi de l’une des spécifications Java EE les moins bien maîtrisées par les développeurs et donc une source récurrente d’échecs des projets. Fort de ce constat je ne vais pas m’apesentir sur les nouveauté de JPA 2.0 dans ce post (en dehors de l’API criteria, celles-ci sont pluôt à la marge) pour me permettre de faire un retour aux sources sur JPA qui pourra profiter à certains. Ceux qui pensent maîtriser ces concepts pourraient être quand même intéressés par la fin de la’article qui traite de l’extended persistence context et de ses mérites.

Une “magie” complexe

La popularité de JPA et les échecs que cette techno engendre sont probablement liées au mélange de magie, de concepts compliqués qu’elle camoufle et de dissimulation de ces concepts au sein de frameworks destinés à faciliter son utilisation. La magie et les raccourcis offert par Spring (entre autres) permettent  de se lancer dans l’utilisation de l’outil sans avoir besoin de trop comprendre les couches sous-jacentes. Lorsque l’on arrive dans des cas concrets qui posent des problèmes, une grande partie des développeurs désinvestissent l’outil en critiquant ses limitations sans chercher à creuser plus avant. Parce que JPA nous fait sortir du confort du code pour aller nous battre avec des notions parfois mal digérés de gestion de transaction et base de données, il reste une énigme pour beaucoup. Certains développeurs pensent que le concept d’ORM en général et JPA en particulier permet de négliger SQL et les mécanismes bas niveau d’échange avec la BDD en les ramenant au rang de technologies inférieures. En pensant cela ils commettent une erreur : JPA rend l’utilisation de ces technologies plus facile et puissante mais ne nous permet pas des les occulter totalement. Il est un facilitateur pas une technologie de substitution.

Les 4 piliers de JPA

JPA s’inscrit autour de ces 4 notions fondamentales :

  • Les entités
  • Les Persistence Units (au runtime on les manipulera via la classe PersistenceManagerFactory)
  • L’Entity Manager (ou persistence manager)
  • Les transactions

Le schéma ci-contre illustre le fonctionnement de JPA et comment ces 4 éléments fonctionnent ensemble. Pour synthétiser :

  • La Persistence Unit organise les meta données qui définissent le mapping entre les entités et la base
  • La Persistence Manager Factory récupère ces metas données de la Persistence Unit et les interprètent pour créer des Persistence Manager
  • Le Persistence Mangager gère les échanges entre le code et la base de donnée, c’est à dire le cycle de vie des entités
  • Enfin, le travail du Persistence Manager est englobé dans une Transaction. Cette dernière peut éventuellement gérer les opérations de plusieurs Persistence Managers comme un traitement unitaire.

Sans faire un cours exhaustif, il parait intéressant de faire un point sur les concepts clés liés à chacune de ces notions clés

Les transactions

Les transactions ne faisant pas stricto sensu partie de JPA, mais étant une composante indispensable au fonctionnement et donc à la compréhension de cette technologie, il est intéressant de commencer notre tour d’horizon par un rappel sur les concepts liés au transactionnel. Les transactions sont en effet aussi importantes que les mécanismes de persistence décrits plus loin. Sans elles les données échangées avec la base risqueraient d’être corrompues ou incohérente. C’est pourquoi il est important de vérifier que le périmètre des transactions soit bien défini lors de l’interaction de votre code avec la base de données.

L’objet de ce post n’est pas de faire un cours magistral sur le transactionnel, mais il me semble intéressant de rappeler quelques concepts de base puisqu’on parle de base de données et de persistence.

Retour sur les différentes APIs transactionnelles

Une transaction est un regroupement d’instructions SQL effectuant un traitement fonctionnel atomique (par exemple l’ajout d’un article dans un panier d’achat accompagné d’une décrémentation des stock pour la référence concernée). Ce regroupement est orchestré à l’aide d’instructions SQL dédiées comme : BEGIN, COMMIT ou ROLLBACK. Bien sûr, si on ne souhaite pas agir à si bas niveau on aura plutôt recours en Java à des API chargées d’automatiser la gestion des transactions pour nous. A cette fin, nous disposons de trois possibilités :

  • Les transactions gérées par JDBC
  • Les transactions de type Resource-local
  • Les transactions gérées par  JTA
JDBC est hors sujet pour cet article, gardons juste à l’esprit que les appels JDBC sont toujours exécutés par défaut dans une transaction. Vous pouvez décider de gérer vous-même le début et la fin de celle-ci avec setAutocommit(false) dans une java.sql.Connection puis lorsque vous le souhaitez, lancer un commit() ou un rollback() à partir de cette même connexion. La gestion de la transaction reste simpliste et ne permettra pas de l’utiliser correctement au niveau de l’ORM,  mais elle est présente.
Les transactions de type Resource-Local sont elles gérées à un niveau supérieur, celui du Persistence Manager. Ce type de transaction rencontré dans Spring est axé sur la base et permet de gérer le cycle de vie de la transaction au niveau de l’ORM et d’automatiser facilement certains comportement (Rollback systématique en cas d’exception par exemple).
Enfin, les transactions JTA sont gérées par le container Java EE et permettent de travailler avec plusieurs datasources facilement. JTA est une spec Java EE, aussi on sera amené à l’utiliser dès qu’on voudra effectuer des tâches transactionnelles avec les EJB.
Pour conclure, gardez à l’esprit que la base de données ouvre et ferme une transaction à chaque opération si aucune démarcation de transaction n’est spécifiée dans votre code. C’est ce qui s’appelle la transaction implicite. Donc quand vous négligez de gérer les transactions, la base s’en charge à votre place à chaque instruction SQL y compris les lectures. Le mécanisme de la transaction étant assez coûteux, il est donc important d’opérer cette démarcation de manière explicite ne serait-ce que pour améliorer les performances de vos lectures.

Les entité et le Mapping

JPA permet donc d’associer des objets dans notre code à des données dans une ou plusieurs bases. Les metas données du mapping (sous forme d’annotations) apportent la souplesse indispensable qui évite d’avoir un modèle de données mémoire totalement calqué que le modèle de donnée relationnel dans la base. On pourra ainsi gérer les colonnes associées à un champ (avec @column), créer une entité sur plusieurs tables (avec @SecondaryTable) ou gérer des champs ne devant pas être stockés (avec @Transient). Bien sûr la magie n’est pas absolue et il peut arriver un moment où votre structure de base de données est trop complexe pour assurer un mapping satisfaisant et où le choix de JPA peut commencer à être remis en cause. Une notion qu’il est capitale de comprendre est la persistance transitive : les associations entre entités avec les fameux @OneToOne, @OneToMany ou @ManyToMany. Le débutant en JPA rencontrera très vite ses premiers problèmes avec ces concepts. Soit parce que les associations déclencheront des chargements en cascade et créeront des grappes d’objets monstrueuses en mémoire, soit parce qu’il aura eu recours au mécanisme de Lazy loading qui peut devenir un cauchemar si on ne maîtrise pas le cycle de vie des entités et les concepts liés au transactionnel. On se reportera à la documentation des annotations JPA d’Hibernate pour avoir une liste exhaustive des possibilités de mapping disponibles dans JPA 2.

La Persistence Unit

C’est le point de départ du moteur JPA. Elle permet de lister les entités à manager et la façon dont elles sont mappées vers la base. Elle indique également le type de transaction utilisée lors des échanges avec la base. Elle est constituée à partir de trois parties :

  1. Les meta données sur les entités (sous forme d’annotations, elles peuvent être surchargées par du XML)
  2. Le descripteur de la Persistence Unit : le fichier de configuration Peristence.xml qui permet de configurer la connexion à la base et la nature des transactions et des paramètres spécifiques à l’implémentation de JPA utilisée.
  3. La Persistence Manager Factory : Objet au runtime représentant la configuration complète de la persistence unit. Cette factory permet de créer un ou plusieurs persistence manager fournissant les services nécessaires à la gestions des entités

Mais n’oublions pas la plomberie JDBC sous JPA et la fameuse datasource, point de départ à la connexion vers la base…

JDBC et pool de connexion

JDBC reste donc la brique de base pour interagir avec la base de données et comme a priori votre application effectuera plusieurs requêtes simultanément vers la base vous avez besoin de fournir à JPA un pool de connexions JDBC. Dans un container Java EE tout cela se fait en général en définissant une data source, soit via un fichier (comme dans Glassfish ou JBoss) soit via une interface d’admin (comme dans Websphere). Cette data source est ensuite appelée via JNDI dans le fichier persistence.xml. La datasource consitue donc un pool de connexions JDBC. son rôle est de pouvoir vous fournir une connexion déjà “prête à l’emploi” (ouverte) quand vous voulez requêter la base et de faire le ménage quand un échange s’est mal passé (timeout, connexion mal fermée, etc…). Ce pool est fourni avec votre serveur d’application. exemple de data source JBoss :

<!--?xml version="1.0" encoding="UTF-8"?-->


		DefaultDS
		jdbc:hsqldb:/Data/hypersonic/localDB
		org.hsqldb.jdbcDriver
		sa
		
		5
		20
		0


La plupart des containers légers permettent de faire la même chose, quasiment tous fournissent un gestionnaire de Pool JDBC. Pour les utilisateurs de Spring, il y a dans le framework de quoi déclarer un bean Datasource auquel on pourra adjoindre le pool JDBC de son choix. Je vous laisse le soin de lire cette doc croustillante qui fourmille de trolls vintage sur Java EE, (heu… pardon J2EE). Toute pique mise à part, Spring sera très utile pour gérer cet aspect ainsi que les problématiques transactionnelles dans un environnement Java SE, l’alternative étant de créer ce pool dans votre code. Quoiqu’il en soit, on se gardera bien de définir sa connexion JDBC dans le fichier persistence.xml car celle-ci ne permet pas (en standard) de choisir la nature du pool utilisé. Ainsi si votre implémentation JPA est hibernate, déclarer votre connexion JDBC dans persistence.xml vous amènera à utiliser le pool de connexion Hibernate par défaut qui n’est pas recommandé en production. Des propriétés propres à Hibernate permettent dans persistence.xml d’activer d’autres gestionnaires de pool fournis par le framework comme C3PO ou Proxool. Ces gestionnaires sont également peu recommandés, on essaiera d’utiliser en priorité le gestionnaire de pool du serveur d’application.

Le fichier persistence.xml

Là non plus je ne rentrerai pas dans les détails de la configuration du fichier de persistence. Je donnerai juste deux versions de la même unité de persistence. L’une avec une gestion du transactionnel et de la datasource au niveau container :

<!--?xml version="1.0" encoding="UTF-8"?-->
org.hibernate.ejb.HibernatePersistence
		java:/DefaultDS
		

La persistence unit définie dans ce fichier exploite la Datasource définie plus haut. Elle utilise Hibernate comme implementation JPA d’où la propriété hibernate.dialect propre à cette implémentation. La deuxième PU, définie une connexion et un pool JDBC géré en local dans l’application (en l’occurence par Hibernate)

<!--?xml version="1.0" encoding="UTF-8"?-->
org.hibernate.ejb.HibernatePersistence

Spring propose une configuration en partie propriétaire dans son fichier de configuration pour la Datasource, puis dans persistence.xml, ce qui n’est pas toujours très clair. Comme on parle ici de Java EE , on se reportera à la documentation du Framework printanier qui fait pousser le XML,  pour plus d’informations sur ce dernier.

La gestion des persistence managers

La aussi il y a plusieurs possibilités pour gérer le cycle de vie des persistence manager JPA :

  1. Géré par l’application dans le code en créant des instances via le Persistence Manager Factory
  2. Géré par un framework du type Spring, Seam  (si on on est sur un serveur léger). Cette solution est techniquement équivalente à la première mais vous décharge d’avoir à gérer les instanciations
  3. Dans le cas d’un serveur Java EE, ceux-ci sont gérés par le container ce qui permet par exemple de partager les mêmes persistence units entre plusieurs war au sein d’un même EAR
En fonction de qui gère les persistences manager (le Serveur ou l’application) on pourra utiliser différents  modes transactionnels comme on l’a vu ci-dessus.
Techniquement cette gestion revient à savoir qui bootstrap JPA en transformant la persistent unit en persistence manager factory.

Le persistence manager Factory

Le chargement de la persistence unit (par le serveur ou l’application) donne lieu à la création d’un objet immuable : le persistence manager factory (ou entity manager factory) qu’on va récupérer automatiquement dans un context Java EE  via :

@PersistenceUnit
private EntityManagerFactory pu;

si vous devez le faire à la main (sans un container ou framework qui le fait pour vous) cela donnera

EntityManagerFactory entityManagerFactory=Persistence.createEntityManagerFactory("my_pu");

Le code précédent bootstrap JPA et déclenche la lecture du Persistence Context “my_pu”. Ce code est comme on s’en doute extrêmement coûteux, aussi on fera en sorte de ne lancer qu’au démarrage de l’application. On travaillera rarement avec l’EntityManagerFactory et on préferera demander au container directement un EntityManager.

Au coeur du Persistence manager

Le persistence manager (ou entity manager, la littérature utilise l’un ou l’autre) est l’API responsable de gérer le cycle de vie des entités JPA. Au delà des échanges explicites avec la base de données,  il est en charge de vérifier l’état des entités qu’il manage pour propager les modifications automatiquement vers la base et gérer le cache  de niveau 1 permettant d’optimiser les accès SQL. Pour l’obtenir à partir du container on utilisera

@PersistenceContext
private EntityManager em;

si on veut le faire à la main (après avoir récupéré l’entityManagerFactory avec le code ‘”à la main” précédent):

EntityManager em=entityManagerFactory.createEntityManager();

Dans ce cas gardez à l’esprit que vous devrez gérer le cycle de vie de l’entityManager vous-même.

Une fois cela fait, nous parvenons enfin au coeur de JPA avec le Persistence Manager dont les responsabilités sont les suivantes :

  • Gérer les instances des entités : l’API du Persistence manager permettent de gérer les entités. Cette API dispose des méthodes pour créer, chercher, requêter, mettre à jour et détruire les instances des entités. Ce faisant, il gère les quatre états possibles pour chaque objet entité : transient, persisté, détaché et détruit. (c.f. le schéma sur le cycle de vie ci-dessous)
  • Maintenir le context de persistance (Pesistence Context en VO) : Le contexte de persistance correspond à un cache mémoire des objets entité qui ont été chargés via le persistence manager. C’est le point d’entrée pour l’optimisation des performances dans la technologie JPA. vous le connaissez probablement aussi sous le nom de cache de premier niveau. Notez aussi que le terme Persistence Context est aussi parfois utilisé à la place de Persistence Manager (ou Entity Manager ;-) ) bien que ce ne soit pas exactement la même chose.
  • Réaliser des contrôles automatiques des objets managés (dirty checks dans le texte) : l’état des objets présents dans le contexte de persistance est automatiquement surveillé pendant toute la durée de vie de ce dernier. Quand un événement de “flush” intervient sur le persistence manager, les modifications detectées par ce mécanisme sont poussées vers la base de données  sous forme de de commandes SQL Lorsque une instance d’entité entre dans l’état “détaché” (detached) ses modifications ne sont plus surveillées.
Le persistence manager travaille à plus haut niverau que le SQL. Grace aux annotations de mapping, il comprend que les données transitant depuis la base ont une structure objet (l’entité) et que cette structure à un cycle de vie (voir ci-contre).
Les instances d’entité débutent leur vie comme étant non managées (stade transient), puis elles deviennent managées ce qui leur permet d’être synchronisées avec la base de données. Si elles sont détruites cette synchronisation devient une suppression. Lorsqu’elles sont supprimées de la base ou que le persistence context est fermé elles passent à l’état détaché et ne sont plus gérées par le persistence manager.
L’atout majeur du persistence manager est probablement le persistence context qui donne tout son intérêt à JPA. En effet, il peut optimiser ses échanges avec la base de données en les évitant lorsqu’il detecte qu’une instance demandée est déjà dans le persistence context (souvenez-vous, c’est le cache de niveau 1). Plus intéressant encore: le persistence manager vous garantit l’unicité de chaque instance d’entité présente dans le persistence context grâce à son identifiant et son type. Conséquence : l’entity manager peut surveiller l’état des instances des entités et peut propager leurs modifications vers la base éventuellement même en cascade pour les entités ou collections associées. En outre, tant que le persistence manager reste ouvert, il est possible d’effectuer des chargements tardifs (le fameux Lazy Loading) de collections associées sans avoir à requêter le base.
Ces fonctionnalités font tout l’intérêt du persistence manager et le rendent bien plus riche que la simple couche d’accès aux données à laquelle on l’assimile trop souvent.
Cela dit pour pouvoir tirer partie de ces fonctionnalités il est important de savoir (et pouvoir) gérer le scope du persistence context.

Scoper le persistence context : une approche trop souvent négligée

Le persistence Manager (et par association le contexte de persistence) est souvent victime d’une méconnaissance des développeurs qui le pensent lié à la connexion avec la base ou à la durée de vie d’une transaction.  Cette erreur provient principalement des architectures stateless dont le framework Spring a fait la promotion depuis ses débuts. A cause de cette vision simpliste et restrictive, beaucoup de développeurs considèrent comme une mauvaise pratique (voire ignorent complètement) le fait de laisser ouvert le persistence manager pour une durée supérieure à une transaction ou une requête HTTP et correspondant à un cas d’utilisation de leur application. En fait le persistence manager est bien plus polyvalent que l’approche hyper partielle et pauvre prêchée par Spring. Oui, il peut être stateful (le mot est lâché), et donc laissé ouvert le temps de réaliser un cas d’utilisation complet (plusieurs écrans et actions de l’utilisateur). Contrairement à ce qu’on pourrait craindre, le persistence manager ne laissera pas la connexion vers la base ouverte tout le temps de sa durée de vie mais saura se reconnecter de manière transparente si le besoin s’en fait sentir.

Le mauvais réglage du scoping du Persistence Manager est la cause principale des critiques adressées à JPA. Avoir recours à des outils ou patterns qui ne permettent que de l’associer à la requête HTTP ou à la transaction en cours amène les développeurs à bricoler des solutions de contournement qui sont autant de mauvaises pratiques : comme le hideux pattern Open Session in view, une redéfinition du mapping JPA pour mettre en Fetch.Eager des associations qui auraient du rester lazy ou du requetage HSQL en pagaille pour  récupérer la grappe d’objets nécessaire pour traiter la requête et ses suites (comme des échanges Ajax par exemple). Comme je l’ai écrit dans de précédents articles, si Spring a été un apport précieux pour la mise en place de bonnes pratiques en terme d’architecture, on atteint là ses limites qui sont liées à des choix idéologiques de ses concepteurs qui n’ont autorisé pendant très longtemps qu’une approche purement stateless des architectures.

Ce qu’on doit faire pour que le persistence manager ait une durée de vie supérieure à la requête HTTP, c’est le gérer en mode stateful. C’est cette approche qui permettra de gérer un scope conversation comme le fait Seam 2 depuis 2006 et comme le propose CDI en standard dans Java EE 6. Cette approche nécessite de faire attention où le persistence manager est stocké, car il ne doit pas être dans un scope partagé (scope application par exemple) puisqu’il n’est pas thread safe. Voyons comment Java EE 6 gère ce mécanisme.

Le peristence context étendu

Depuis Java EE 5,  nous avons la possibilité de créer un persistence context étendu. Comme dit, plus haut, c’est un persistence context qui reste actif au dela de la transaction (mode par défaut du persistence context).

Avoir recours à un Persitence context étendu :

  • Permet de gérer proprement le lazy loading des entités ou collections associées
  • Evite d’avoir à merger des entités détachées pour les synchroniser avec la base
  • Vous assure de n’avoir qu’une seule référence à un objet pour un id d’entité donné
  • Fonctionne avec les mécanisme d’Optimistic Locking pour supporter les unités de traitement à durée de vie longue (l’annotation JPA @Version est très utile pour ce dernier point)

Le persistence context étendu doit donc résider dans un scope stateful, et non partagé. La session ou la conversation pourraient faire l’affaire, mais la gestion de son cylce de vie resterait un peu fastidieuse. C’est pourquoi, la meilleure place pour le gérer est de le placer au sein d’un EJB Session Sateful (Stateful Session Bean ou SFSB).

On ne va pas trop entrer dans les détails des EJB ici (je garde cela pour mon prochain post), il faut juste savoir qu’un SFSB est géré par un container spécifique qui n’a rien à voir au conteneur servlet. Notre Bean a donc un cycle de vie indépendant des scopes servlet mais pourra être manipulé depuis ces fameux scopes soit en étant injecté via @EJB, soit, encore mieux en étant injecté ou directement utilisé via CDI. Le persistence context étendu ainsi utilisé ne sera fermé que lorsque le SFSB sera recyclé.  Nous reviendrons sur tout ça dans un post ultérieur. Pour le moment voici un petit exemple que j’ai emprunté à Adam Bien .

@Stateful
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
@Named
@ConversationScoped
public class OrderGateway{

  @PersistenceContext(type=PersistenceContextType.EXTENDED)
  EntityManager em;

  private Load current;

  public Load find(long id){
    current = em.find(Load.class, id);
    return current;
  }

  public Load getCurrent() {
    return current;
  }

  public void create(Load load){
    em.persist(load);
    current = load;
  }

  public void remove(long id){
    Load ref = em.getReference(Load.class, id);
    em.remove(ref);
  }

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public void save(){
    //nothing to do
  }
}

La ligne 1 fait de notre classe un SFSB (EJB Stateful), la deuxième ligne désactive le support transactionnel sur l’EJB en question, la ligne 3 fait que la version CDI de cet EJB pourra être utilisé dans une page JSF via l’expression language. Et la quatrième stock le composant CDI dans le scope conversation.

On voit que dans le SFSB on inject un entity manger étendu en ligne 7. Diverses méthodes permettent de chercher, créer et supprimer des entités et en ligne 31 on annote une methode save() avec l’attribut REQUIRES_NEW qui va créer une transaction et la fermer dans la foulée commitant ainsi toutes les modifications effectuées (rappelez-vous l’entity manager empile les  modification jusqu’à ce qu’une transaction lui permette de pousser ces modifications). Ce scénario simplifié permet d’imaginer les possibilités offertes par l’extended persistence context.

Conclusion

L’empilement de technologies masquées par JPA  constitue à la fois l’origine de son succès et la principale raison des critiques que la spécification essuie. Les cas d’utilisations restreints mis en avant par Spring en ne permettant que de concevoir des architecture stateless pour les services, sont également à l’origine de pas mal de galères sur JPA. Pour utiliser cette technologie au mieux je ne vous donnerai que 3 conseils :

  • Apprenez Java EE 6, ça ouvre des horizons.
  • Si vous ne pouvez pas travailler avec Java EE, utilisez Seam 2 qui marche très bien sur des conteneurs léger type Tomcat et qui reprend ces concepts.
  • Lisez la doc de JPA (vous pouvez commencer par ici) et non pas la version Springifiée de celle-ci.

Bibliographie 

Cet Article s’appuie sur les livres suvants

TwitterFacebookGoogle+LinkedIn
  • http://twitter.com/wdrai William Draï

    Malheureusement comme c’est dit à la fin, les persistence contexts étendus ne sont en pratique utilisables qu’avec Seam 2 ou Java EE 6 + CDI. Autant dire que c’est assez difficile à faire passer quand quasiment tout est Springifié.
    Et de toute façon cette approche stateful est systématiquement écartée pour une obscure raison de scalabilité, comme si toutes les applications d’entreprise étaient destinées à scaler jusqu’à des millions d’utilisateurs comme twitter ou facebook…

    • http://www.next-presso.fr Antoine Sabot-Durand

      Le Persistence Context étendu est un argument de plus pour passer à Java EE et lacher Spring, donc je vois ça plus comme une chance que comme un malheur :-).

      Bien sûr la scalabilité du stateful n’est pas infinie, mais elle est bien suffisante pour gérer une appli intranet ou un petit site. J’ai développé un site web en Seam 2 en 2008, avec ce pattern partout, et il tourne encore…
      Merci pour ton retour. 

    • Nicolas R.

      Spring WebFlow propose la pattern Flow Managed Persistence
      Voir: http://static.springsource.org/spring-webflow/docs/2.3.x/reference/htmlsingle/spring-webflow-reference.html#flow-managed-persistence

      C’est au coeur des applications JSF2/PrimeFaces/WebFlow générées par SpringFuse (voir post précédent d’ippon sur le sujet)

      • http://www.next-presso.fr Antoine Sabot-Durand

        En effet, mais quelle usine à gaz ! Pour bénéficier de ce pattern, il faut donc avoir besoin d’un flow (définit dans un format propriétaire à la Spring, très verbeux en XML) pour en bénéficier. Le pattern est simple (c.f. mon EJB), il peut être mis en oeuvre sans EJB avec l’extension CDI Seam persistence et Seam Transaction et lorsque le module d’extension CDI Seam Drools sera dispo (début 2012) vous aurez la possibilité de rajouté un flow comme extension du standard. En attendant Seam 2 et pageflow font le Job.

  • http://twitter.com/nmartignole Nicolas Martignole

    Très bon article, et bon travail de documentation. Bravo.

    • http://www.next-presso.fr Antoine Sabot-Durand

      Merci Nicolas. Ton blog est pas mal aussi ;-)

  • brice

    “Les cas d’utilisations restreints mis en avant par Spring en ne permettant que de concevoir des architecture stateless pour les services”

    • brice

      Peut-on encore le dire puisque dans le cas cité de Webflow, chaque étape du flow est sérialisée (y compris l’entity manager dans le cas d’utilisation de flow managed persistence)? Ca fait un peu staeful non? Si webflow implique effectivement des XML, en revanche l’activation du contexte de persistence étendu ne requiert qu’une ligne.
      Moi j’aime bien les guerres de clocher! :D Je vais peut être aller voir un blog Spring pour leur dire que JavaEE c’est de la bombe!

      • Nicolas R.

        Brice, chaque étape du flow est sérialisée, si tu le veux… comme d’habitude avec Spring, la souplesse prime, tu peux désactiver la sérialisation simplement en spécifiant le max-execution-snapshots=”0″.

        Que perds-tu alors? La possibilité de remonter dans le temps… Tout dépend de ton besoin.

        Je suis aussi impatient que vous de bosser sur un standard, mais en attendant il faut bien avancer, et n’oubliez pas que jee a emprunté qq idées à Spring… soyez sportifs les gars :)

        • http://www.next-presso.fr Antoine Sabot-Durand

          Pour ma part je n’ai jamais dit le contraire. Mon article “Les rendez-vous manqué de Spring” (http://blog.ippon.fr/2010/03/30/les-rendez-vous-manques-de-spring/) évoque longuement la grosse influence de ce framework sur Java EE directement ou à travers d’autre frameworks comme Seam.

          Sur Webflow, je ne suis franchement pas assez qualifié pour répondre de manière pertinente. Les quelques jours d’expérience que j’ai avec ne me donne pas une bonne impression surtout en comparaison avec Seam 2 et son pageflow.
          Donc aujourd’hui si j’avais besoin d’un extended context dans un flow je prendrais Seam 2, framework propriétaire, mais qui s’appuie suffisamment sur Java EE pour éventuellement basculer sur Seam Drools quand l’extension CDI sera prête.

  • kerny

    “(rappelez-vous l’entity manager empile les  modification jusqu’à ce qu’une transaction lui permette de pousser ces modifications).”Je suis surpris par cette phrase (je ne connais pas bien JPA, mais assez bien Hibernate).
    S’il n’y a pas de transaction démarrée et que l’entity manager décide qu’il est utile de faire un flush (par exemple pour exécuter une recherche sur une table pour laquelle des entités ont été modifiées) alors ces entités seront persistées en dehors du tout contexte transactionnel.

    • http://www.next-presso.fr Antoine Sabot-Durand

      Je vous renvoi à la documentation sur l’extented persistence context sur le site de Red Hat :
      “In an EXTENDED persistence context, all operations made outside an active transaction are queued. The EXTENDEDpersistence context is flushed when executed in an active transaction (at worse at commit time).”L’integral est consultable ici : http://docs.redhat.com/docs/en-US/JBoss_Enterprise_Web_Platform/5/html/Hibernate_Entity_Manager_Reference_Guide/transactions-optimistic-longsession.html

      • kerny

        Je ne connaissais pas ce mécanisme de rétention des update, merci pour cette information.

        Exécuter des requêtes en dehors du contexte transactionnel est quelque chose de risquée : une tx sert aussi pour l’isolation en lecture selon le parametrage, mais surtout l’entity manager va prendre des décisions en fonction de la démarcation des transactions. Même si techniquement les requêtes en lecture sont exécutées, elles peuvent renvoyer des résultats différents (cf tests ci-joints) :
        * En dehors de la transaction, l’entity manager va ignorer totalement les requêtes en écriture en attente, même si celles-ci peuvent avoir une influence sur le résultat de la lecture (2 voitures retournées ligne 43)
        * Dans la transaction, l’entity manager va ‘flusher’ les lignes concernées (pas forcément toutes les écritures en attente) susceptibles d’avoir un impact sur la lecture. Dans le 2ème test, il y a 3 voitures retournées ligne 58.

        Pour cette raison le pattern OpenSessionInView, même s’il est un peu trop simple parfois, possède l’avantage de ne pas laisser de risque de lecture déroutante ‘par défaut’.
        Par ailleurs conserver une session/manager ouverts sur plusieurs requêtes peut faire augmenter considérablement le risque de concurrence d’accès sur les objets compte tenu de la durée de rétention des objets en mémoire. Cela présente un avantage parfois, mais necessite des équipes aguerrie pour jouer sur ce type tunning.

        Enfin avoir plusieurs transactions durant une requête sur le serveur présente le risque de se retrouver dans un état incohérent (comme en autocommit), ce qui est possible selon la démarcation de transaction via les annotations de transaction. Pour cette raison je pousse fréquemment le pattern ‘OpenTransactionInView’ par défaut.

        • http://twitter.com/wdrai William Draï

          Avec un extended persistence context, on est quand même supposé ouvrir une transaction à chaque méthode qui accède au contexte (justement pour les lectures). C’est le cas par défaut quand on stocke le xpc dans un stateful bean et il n’y a normalement aucun risque d’avoir les lectures inconsistantes. Ce sont simplement les écritures qui sont retardées jusqu’au dernier moment. Si on veut c’est plutôt un OpenTransactionInView, le contexte servant en gros de cache de données de niveau 1.
          Le xpc est beaucoup plus simple dans de nombreux cas et évite les problèmes de LazyInitializationException de manière assez élégante. Cela dit c’est effectivement un peu plus délicat à manier qu’un OpenSessionInView et ça peut assez facilement menez à du gros n’importe quoi (du style des sessions de 50 Mo…).

      • http://twitter.com/wdrai William Draï

        Avec Hibernate on peut aussi utiliser le mode de flush manuel (comment on dit flush en français, c’est quand même incroyable de ne pas pouvoir écrire deux lignes sans un mot en anglais ???) qui permet d’éviter des accès à la base à chaque étape. Comme ça on peut tout flusher d’un coup à la fin d’un flow. C’est ce qui est conseillé dans Seam en tout cas.

  • linus nova

    Très bon article, synthétique et concis.

    PersistenceContextType.EXTENDED :
    ———————————————–
    Si nous souhaitons ne pas avoir recours au mode ?ush manuel spéci?que d’Hibernate,cela veut dire que nous devons trancher entre les modes standards auto et commit. Écartons d’of?ce le mode auto puisque celui-ci peut exécuter un ?ush avant l’exécution decertaines requêtes.Le choix  @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) pour toutes les méthodes, excepté celle de validation (méthode de flush). Pour rappel, avec NOT_SUPPORTED, si un accès à une ressource transactionnelle est requis, celui-ci s’effectue en dehors d’un contexte transactionnel en exploitant la fonctionnalité d’autocommit. Le autocommit n’est pas intercepté par le conteneur comme un commit traditionnel et donc que le fush ne sera pas déclenché.Seule la méthode de validation surcharge ce comportement : on l’annote avec @TransactionAttribute(TransactionAttributeType.REQUIRED). Cette méthode peut être vide, le simple fait que le conteneur commit le contexte transactionnel en fin de méthode, c’est  suffisant à déclencher le fush.

  • Philippe Bastiani

    Bonjour Antoine,
    Je veux bien te suivre et retourner aux sources de JPA… mais, il n’y a pas que JavaEE dans la vrai-vie ! En particulier dans un environnement Standalone ! Quel est l’équivalent des transactions locales de Spring ? Y a-t’il une solution CDI pour la gestion multi persistence unit ? Dans la doc de Seam3 je lis : ”You should avoid EntityTransaction if you have more than one persistence unit in your application”… Bref, je reste perplexe !

    • Philippe Bastiani

      J’ai zieuté les sources sur Github… et, effectivement il semble qu’un seul et unique EntityManager ne puisse être injecté dans le scope applicatif ! Grosse limitation pour moi :(  N’était-il pas possible de maintenir une liste d’EMs dans le scope applicatif ? A voir avec l’auteur donc…

      Dans tous les cas, merci pour cet article Antoine! Cela, me donne envie d’approfondir le sujet même s’il ne semble pas encore y avoir de solution industrielle hors JTA…

      • http://www.next-presso.fr Antoine Sabot-Durand

        Bonjour Philippe,
        En effet, tout ça marche mieux en Java EE avec JTA donc. Mais comme tu le fais remarqué il serai possible via une extension CDI comme Seam Peristence 

        • Philippe Bastiani

          Ok merci Antoine… Donc tjrs les mêmes chez Redhat ;-) Je vais tout de même parler de ce point avec Shane Bryzak ! On verra bien…

          Côté licence, cette ouverture du code est effectivement une très bonne chose. Dommage… actuellement, il semble qu’il y ait peu de contributeurs hors Redhat sur les modules:( mais bon, les projets sont a suivre. Je vais me monter un POC chez moi pour secouer le cocotier…

          Curiosité des Licences: Seam Persistence en Apache qui s’appuie sur SeamTransaction en GPL v2… très logique ! Sans parler des fichiers sans licence dans les 2 projets…

        • Anthony Patricio

          “JBoss ne préfère pas donner trop de possibilités d’utiliser du transactionel hors leur serveur d’application.”D’ou tiens tu cette affirmation? La plupart des projets sont totalement indépendants du serveur d’app et vivent leur vie séparément. Hibernate et Seam ne tournent pas que sur JBoss AS s’il était besoin de le signaler. Quant à créer sa propre extension CDI pour manipuler plusieurs persistence units, effectivement facile, c’est bien pour cela que focus est donné sur CDI désormais, non pas que Seam soit sans intérêt, loin de là, mais il faut faire des choix.
          Anthony Patricio
          RedHat

          • http://www.next-presso.fr Antoine Sabot-Durand

            Je tiens cette affirmation sur ma pratique de Seam 2 et des extensions Seam 3. Bien sûr ces outils fonctionnent sans JBoss AS mais il fonctionnent mieux avec (j’aime beaucoup e troisième pragraphe de cette page de doc : http://docs.jboss.org/seam/2.2.0.GA/reference/en-US/html/persistence.html#d0e7442). Cela dit, ça me parait tout à fait normal que JBoss pousse son serveur d’appli.
            Concernant le focus CDI, je serai assez critique là-dessus : l’aventure Seam 3 (à laquelle j’ai activement participé) a  créé un vrai imbroglio (à tel point que même des gens de Red Hat n’ont toujours pas compris que c’était une collection de composants CDI c.f ton commentaire) et a retardé de 2 ans l’adoption de CDI et donc de Java EE chez les clients finaux.
            J’aime beaucoup CDI (pour un membre de l’EG CDI  1.1 c’est un minimum), j’apprécie beaucoup l’approche pionnière de JBoss sur plein de sujet et ai noué beaucoup de contacts chez vous (Dan Allen, Pete Muir, Jason Porter ou Lincoln Baxter qui m’a fait rentré sur Seam 3), mais pour être totalement honnête cette boîte a de grosses difficulté à finir ce qu’elle commence.
            Si on se place uniquement sur la trajectoire de CDI : le focus sur CDI a gelé Seam 2 pendant 2 ans (cet investissement était bien sûr nécessaire mais pourquoi ne pas avoir laissé quelques ressources pour maintenir le projet et éviter la fuite des devs vers Spring ?), puis une fois cette super spec livrée  une série de choix malheureux ont été fait qui on mené à Seam 3 (le nom du projet, l’idée d’avoir le même nom de packages que Seam 2 rendant impossible le développement de passerelles entre les 2, …). L’idée derrière Seam 3 était super mais les moyens ridicules (3 internes de RedHat et 25 volontaires externes pour un projet aussi majeur).
            Je ne vise (presque) personne, mais il est évident que JBoss avait plus interrêt  a enrichir l’écosystème CDI à travers Seam 3 que de développer un framework de test (Arquillian c’est génial mais si personne ne fait de Java EE c’est un peu inutile) ou pire créer un nouveau Langage.
            Vu d’ici ça ressemble quand même à une série de tirage de balle dans le pied assez incompréhensible et encore une fois je dis ça avec toute la bienveillance et le respect que j’ai pour cette entreprise qui a innové sur plein de sujets.
            Bref, 2 ans après la sortie de Java EE 6 est enfin lancé un projet majeur pour constituer un ecosystème autour de CDI : Deltaspike. En attendant Spring reste le standard de fait de part la richesse de son ecosystème et son adoption pendant la période noire de J2EE. Vraiment dommage je trouve…
            Maintenant, il est encore temps de corriger le tir c’est ce qui semble se passer avec JBoss Developer Framework mais que de temps perdu !

  • msaad

    Bonsoir,
    merci pour cet article.
    Je suis confronté à un probleme de migration de jboss as5 vers jboss as 7.1.1.final.
    Avant j’utilisais un service hibernate (har) qui n’est plus apparemment supporté par jboss 7 .
    quelle serait la meilleur solution pour gérer plusieurs datasources avec une seule sessionfactory comme dans service-hibernate?
    quid de persistenceContext dans mon cas?

    merci d’avance, je galère depuis plusieurs à trouver une solution.