ORM – Java & Moi https://javaetmoi.com Développeur Java, Spring & co, et fier de l'être Wed, 16 Feb 2022 14:16:58 +0000 fr-FR hourly 1 https://wordpress.org/?v=6.9.4 https://javaetmoi.com/wp-content/uploads/2022/05/cropped-java-icon-32x32.png ORM – Java & Moi https://javaetmoi.com 32 32 Utilisez Hibernate 4.3 sous JBoss 5 avec Spring 4 https://javaetmoi.com/2014/04/hibernate4-sous-jboss5-avec-spring4/ https://javaetmoi.com/2014/04/hibernate4-sous-jboss5-avec-spring4/#respond Thu, 10 Apr 2014 18:50:07 +0000 http://javaetmoi.com/?p=1051 Continuer la lecture de Utilisez Hibernate 4.3 sous JBoss 5 avec Spring 4 ]]>
logo-hibernate

Dans mon précédent billet, je vous expliquais comment réintroduire le support de VFS 2 abandonné dans Spring Framework 4.0. Et ceci, dans l’optique de pouvoir déployer mon application dans le serveur d’application JBoss 5.1 EAP.
Outre ce problème survenant lors de la détection de beans Spring dans le classpath, la montée de version de Spring 3.2 à Spring 4 a révélé un autre problème lié au format VFS de JBoss.  Cette fois-ci, c’est le framework Hibernate 4.3 qui n’arrive pas à détecter les entités JPA du classpath.

Certifié conforme à Java EE 5, JBoss 5.1 EAP utilise Hibernate 3.3 comme implémentation de JPA 1.  Dans mon cas, Hibernate 4.3 est utilisé en mode standalone et est donc directement embarqué dans les librairies de mon EAR. La configuration JPA 2.1 est assurée par le support JPA offert par Spring, et plus particulièrement par la classe LocalContainerEntityManagerFactoryBean.

Problème rencontré

Au démarrage de l’application, Hibernate s’appuie sur l’interface Scanner pour détecter les classes, les packages et les ressources d’une Unité de Persistance JPA. L’implémentation StandardScanner délègue le parcours du WAR à la classe StandardArchiveDescriptorFactory. Ligne 72 de cette dernière, l’appel à la méthode file.exists() échoue sur le chemin « vfszip:/C:/jboss-eap-5.1/server/default/deploy/myapp-ear-1.0.0-SNAPSHOT.ear/myapp-war-1.0.0-SNAPSHOT.war/WEB-INF/classes/ ». Une IllegalArgumentException est levée :

21:51:24,472 INFO  [STDOUT] 21:51:24.472 |  INFO |  | Building JPA container EntityManagerFactory for persistence unit 'persistenceUnit'| org.springframework.orm.jpa.LocalContainerEntityManagerFactoryB ean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:332)
21:51:24,550 INFO  [STDOUT] 21:51:24.550 |  INFO |  | HHH000204: Processing PersistenceUnitInfo [ name: persistenceUnit ...]            | org.hibernate.jpa.internal.util.LogHelper.logPersistenceUnitInformation(LogHelper.java:46)
21:51:24,754 INFO  [STDOUT] 21:51:24.754 |  INFO |  | HHH000412: Hibernate Core {4.3.4.Final}                                           | org.hibernate.Version.logVersion(Version.java:54)
21:51:24,754 INFO  [STDOUT] 21:51:24.754 |  INFO |  | HHH000206: hibernate.properties not found                                         | org.hibernate.cfg.Environment.<clinit>(Environment.java:239)
21:51:24,769 INFO  [STDOUT] 21:51:24.754 |  INFO |  | HHH000021: Bytecode provider name : javassist                                     | org.hibernate.cfg.Environment.buildBytecodeProvider(Environment.java:346)
21:51:33,848 INFO  [STDOUT] 21:51:33.848 | ERROR |  | Context initialization failed                                                     | org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:331)
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactoryBean' defined in class com.javaetmoi.sample.config.InfrastructureConfig: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: File [/C:/jboss-eap-5.1/server/default/deploy/myapp-ear-1.0.0-SNAPSHOT.ear/myapp-war-1.0.0-SNAPSHOT.war/WEB-INF/classes/] referenced by given URL [vfszip:/C:/jboss-eap-5.1/server/default/deploy/myapp-ear-1.0.0-SNAPSHOT.ear/myapp-war-1.0.0-SNAPSHOT.war/WEB-INF/classes/] does not exist
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1553) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:300) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE] 
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:195) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:973) ~[spring-context-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:750) ~[spring-context-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482) ~[spring-context-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403) ~[spring-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306) ~[spring-web-4.0.2.RELEASE.jar:4.0.2.RELEASE] at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106) [spring-web-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3910) [jbossweb.jar!/:na]
        at org.apache.catalina.core.StandardContext.start(StandardContext.java:4389) [jbossweb.jar!/:na]
        at org.jboss.web.tomcat.service.deployers.TomcatDeployment.performDeployInternal(TomcatDeployment.java:321) [jboss-web-service.jar!/:5.1.1 (build: SVNTag=JBPAPP_5_1_1 date=201105171607)]
        ...
        at org.jboss.Main$1.run(Main.java:556) [run.jar:5.1.1 (build: SVNTag=JBPAPP_5_1_1 date=201105171607)]
        at java.lang.Thread.run(Thread.java:662) [na:1.6.0_26]
Caused by: java.lang.IllegalArgumentException: File [/C:/jboss-eap-5.1/server/default/deploy/myapp-ear-1.0.0-SNAPSHOT.ear/myapp-war-1.0.0-SNAPSHOT.war/WEB-INF/classes/] referenced by given URL [vfszip:/C:/jboss-eap-5.1/server/default/deploy/myapp-ear-1.0.0-SNAPSHOT.ear/myapp-war-1.0.0-SNAPSHOT.war/WEB-INF/classes/] does not exist at org.hibernate.jpa.boot.archive.internal.StandardArchiveDescriptorFactory.buildArchiveDescriptor(StandardArchiveDescriptorFactory.java:73) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.hibernate.jpa.boot.archive.internal.StandardArchiveDescriptorFactory.buildArchiveDescriptor(StandardArchiveDescriptorFactory.java:48) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.hibernate.jpa.boot.scan.spi.AbstractScannerImpl.buildArchiveDescriptor(AbstractScannerImpl.java:95) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.hibernate.jpa.boot.scan.spi.AbstractScannerImpl.scan(AbstractScannerImpl.java:70) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.scan(EntityManagerFactoryBuilderImpl.java:723) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:219) ~[hibernate-entitymanager-4.3.4.Finl.jar:4.3.4.Final]
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:186) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.hibernate.jpa.boot.spi.Bootstrap.getEntityManagerFactoryBuilder(Bootstrap.java:45) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.hibernate.jpa.boot.spi.Bootstrap.getEntityManagerFactoryBuilder(Bootstrap.java:57) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.hibernate.jpa.HibernatePersistenceProvider.createContainerEntityManagerFactory(HibernatePersistenceProvider.java:150) ~[hibernate-entitymanager-4.3.4.Final.jar:4.3.4.Final]
        at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:336)~[spring-orm-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318) ~[spring-orm-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549) ~[spring-beans-4.0.2.RELEASE.jar:4.0.2.RELEASE]
        ... 82 common frames omitted

 Solution

Comme précisé en introduction, notre application utilise la fabrique de beans LocalContainerEntityManagerFactoryBean pour créer l’EntityManagerFactory de JPA. Lors de la configuration de ce bean, la méthode setPackagesToScan permet d’indiquer à Spring quel package Java il doit scanner au démarrage de l’application pour détecter les entités JPA. Spring utilise alors le même mécanisme d’auto-détection que pour les beans Spring et scanne tous les JAR du classpath.
Dans l’exemple ci-desssous, le package com.javaetmoi.demo.model ainsi que tous ses sous-packages sont scannés :

@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(dataSource());
    em.setPackagesToScan("com.javaetmoi.persistencedemo.model");

    em.setJpaVendorAdapter(new HibernateJpaVendorAdapter(););
    em.setJpaProperties(additionalProperties());

    return em;
}

Le support JPA proposé par Spring fait que le fichier pesistence.xml n’a plus de raison d’être. Et vous l’aurez compris, le scan d’Hibernate ait redondant à celui de Spring. Il peut donc être désactivé.

La classe NopScanner disponible sous forme de Gist permet de court-circuiter le scan d’Hibernate :

No-operation Hibernate Scanner

Indiquer à Hibernate d’utiliser la classe NopScanner revient à déclarer la propriété Hibernate suivante :
hibernate.ejb.resource_scanner=com.javaetmoi.core.persistence.hibernate.NopScanner

En utilisant la syntaxe Java, cette propriété peut être ajoutée dans la méthode additionalProperties() utilisée lors de la déclaration du bean LocalContainerEntityManagerFactoryBean dans l’exemple précédent :

 private Properties additionalProperties() {
    return new Properties() {
        {
            setProperty("hibernate.ejb.resource_scanner", "com.javaetmoi.core.persistence.hibernate.NopScanner");
            setProperty("hibernate.dialect",env.getProperty("hibernate.dialect"));
        }
    };
}

Conclusion

La possibilité offerte par JPA de pouvoir être utilisée en dehors d’un conteneur Java EE ainsi que la souplesse du support JPA proposé par le framework Spring font qu’il est possible d’utiliser JPA 2.1 (introduit ans JEE 7) dans un serveur JEE 5.

Ayant utilisé cette approche dans ce billet, je vous donnerai prochainement un exemple complet de configuration Spring en Java d’une application basée sur Spring MVC et JPA. Restez connectés !!

]]>
https://javaetmoi.com/2014/04/hibernate4-sous-jboss5-avec-spring4/feed/ 0
Dites adieu aux LazyInitializationException https://javaetmoi.com/2012/03/hibernate-dites-adieu-aux-lazy-initialization-exception/ https://javaetmoi.com/2012/03/hibernate-dites-adieu-aux-lazy-initialization-exception/#comments Fri, 30 Mar 2012 20:39:43 +0000 http://javaetmoi.com/?p=54 Continuer la lecture de Dites adieu aux LazyInitializationException ]]>
logo_hibernate

Dans ce deuxième ticket, j’aimerais vous parler du projet Hibernate Hydrate [1] que j’ai récemment publié sur GitHub. Au cœur de ce projet : une seule classe Java proposant une unique fonctionnalité. En quelques années, c’est la seconde fois que j’ai eu besoin de coder ce genre de fonctionnalité. Aussi, je me suis dit qu’il serait pratique de l’avoir sous le coude pour une prochaine fois et, au passage, vous en faire profiter.

Origine des lazy exceptions

En quoi consistent ce projet et cette fameuse fonctionnalité ? Eh bien, sous certaines conditions, résoudre un problème récurrent lors de l’utilisation d’Hibernate. En effet, lorsque l’on tente d’accéder à un objet détaché de la session Hibernate, ce dernier n’est pas forcément entièrement chargé en mémoire : son proxy ou ses propriétés peuvent ne pas être initialisés, ce qui est par exemple le cas d’une relation déclarée comme paresseuse (ou lazy). Et c’est à cet instant-là, qu’Hibernate lève la tant redoutée LazyInitializationException.
Par objet détaché, j’entends un objet évincé de la session (retirée du cache de premier niveau par un session.clear() ou un evict()) ou dont la session est fermée (session.close())
Dans une application, ce phénomène est susceptible de se produire à plusieurs niveaux :

  • Couche présentation : contrôleur (ex : action Struts) ou rendu de la vue (ex : JSP)
  • Exposition d’un web service : marshalling XML, mapping dozer …

La documentation d’Hibernate propose plusieurs solutions pour remédier à ce problème. Plus encore, elle explique ce qu’il faut éviter de faire, comme par exemple ouvrir une autre unité de travail (transaction) pour charger les données manquantes.
Solutions préconisées

Pattern Open Session In View

Une première solution consiste à utiliser le pattern Open Session In View [2]. Dans une application web, ce pattern peut par exemple être implémenté à l’aide d’un filtre de servlets. L’arrivée d’une requête HTTP initie l’ouverture d’une transaction et de la session Hibernate correspondante. Une fois la vue rendue et prête être renvoyée au client, la session est fermée et la transaction est validée.
Le pattern Open Session In View ne peut pas s’appliquer dans une architecture technique 3 tiers où la couche présentation et la couche métier sont déployées physiquement sur 2 serveurs différents, et donc 2 JVMs. Ce pattern n’est également pas valable dans le cadre d’une application web riche utilisant Ajax et JavaScript pour récupérer puis parcourir le modèle métier.

Pré-chargement sur mesure

Personnellement, je recommande généralement à ce que les transactions soient gérées au niveau de la couche métier. En effet, à ce niveau, le service métier connait l’usage des objets qu’il va charger depuis la base de données. Il est capable d’utiliser le DAO ayant la stratégie de pré-chargement [3] (ou fetching) adaptée au traitement métier.

Pour rappel, le pré-chargement des relations peut être configuré de 2 manières :

  1. statiquement au niveau du mapping Hibernate (en XML ou par annotations)
  2. dynamiquement lors du requêtage en HQL (JOIN FETCH) ou par l’API Criteria (méthode setFetchMode())

Par défaut, dans Hibernate 3.x, les associations vers une autre entité ou une collection d’entités sont chargées tardivement ; c’est-à-dire à la demande, lorsque l’on essaie d’accéder à l’entité ou à la collection (et que la session est ouverte). Qui plus est, les stratégies définies statiquement ne sont pas forcément utilisées lors d’un requêtage HQL.

Une bonne pratique issue du guide de référence d’Hibernate [4] consiste à conserver le comportement par défaut d’Hibernate et à redéfinir la stratégie de pré-chargement à chaque usage. Cela permet d’optimiser votre code et de ramener les données dont vous avez strictement besoin.

Comme illustré dans l’article Hibernate Survival Guide [5], et grâce au cache de premier niveau d’Hibernate, il est parfois plus performant de découper sa requête en plusieurs requêtes, notamment lorsque la grappe d’objets est complexe et la cardinalité des associations importante.


Hybernate Hydrate à la rescousse

La solution que je vais vous présenter peut être utilisée conjointement avec la solution du pré-chargement sur mesure.

La méthode statique deepHydrate de la classe LazyLoadingUtil permet de charger dans sa globalité la grappe d’objets qui lui est passée en paramètre. Seule contrainte, cette méthode doit être appelée avant que la session Hibernate et la transaction associée ne soient clôturées.

Voici un exemple d’utilisation :

Employee employee = (Employee) session.get(Employee.class, 1);
LazyLoadingUtil.deepHydrate(session, employee);

Techniquement, la méthode deepHydrate() utilise le méta-modèle Hibernate (interface ClassMetadata) pour parcourir l’ensemble du graphe des objets persistés et déterminer le type des propriétés et des relations du modèle. Ainsi, elle peut naviguer récursivement dans le graphe. Les proxy rencontrés sont initialisés puis résolus. Les collections persistantes sont initialisés puis itérés.

Dans le cas d’un graphe cyclique, un mécanisme de garde permet d’éviter toute boucle infinie.

La classe TestLazyLoadingUtil propose des exemples d’utilisation.

Une variante est disponible pour les applications utilisant JPA avec Hibernate pour provider : JpaLazyLoadingUtil.

Pour l’essayer, vous avez le choix entre un copier / coller, un git clone ou bien l’ajout d’une dépendance maven et du repo qui va avec :

<dependency>
    <groupId>com.javaetmoi.core</groupId>
    <artifactId>javaetmoi-hibernate4-hydrate</artifactId>
    <version>2.0</version>
</dependency>

Les artefacts du projet Hybernate Hydrate sont disponibles sur Maven Central.

Conclusion

Pour terminer cette présentation, voici un tableau récapitulatif qui devrait vous permettre d’orienter votre choix sur l’usage ou non de cette petite librairie :

Scénarios pour lesquels écarter cette solutionScénarios favorables à son utilisation
  1. Utilisation du pattern Open Session in View
  2. Problématiques fortes de performance
  3. Grappes d’objets risquant de remonter toute la base de données
  1. Grappe d’objets constituée de nombreuses classes
  2. Développement rapide d’une application avec chantier d’optimisations portant sur les requêtes critiques
  3. Exploitation du cache de niveau 2 d’Hibernate pour les données en « bout de grappe » (ex : données du référentiel).
  4. Outil ayant besoin de charger toute une grappe d’objets en mémoire

De mon côté, ce modeste travail de capitalisation m’a permis de renouer avec l’open-source ;  en tant que contributeur j’entends. Mon dernier code poussé sur Source Forge datait en effet de l’an 2000 …
Cela m’aura également permis de me familiariser davantage avec GitHub : syntaxe MarkDown, pages wiki gérées par git ou bien encore les releases avec maven. Ce dernier point fera d’ailleurs l’objet d’un prochain article.
J’ai également pu adhérer au programme Free and OpenSource (FOSS) de CloudBees, ce qui vous permet d’accéder librement au Jenkins d’Hibernate Hydrate [6].

Enfin, si vous êtes un jour amené à utiliser ce code, je serais intéressé de le savoir. Et si vous voulez contribuer (ex : support d’autres ORM, annotation + post-processeur Spring …), les portes sont grandes ouvertes.

Références :

  1. Projet Hibernate Hydrate hébergé sur Github
  2. Open Session In View du wiki Hibernate de JBoss
  3. A Short Primer On Fetching Strategies du wiki Hibernate de JBoss
  4. Performance fetching du guide de référence d’Hibernate
  5. Hibernate Survival Guide du wiki d’Object Direct
  6. Jenkins d’Hibernate Hydrate hébergé sur la plateforme DEV@Cloud de CloudBees[6].
]]>
https://javaetmoi.com/2012/03/hibernate-dites-adieu-aux-lazy-initialization-exception/feed/ 4