Archives de Catégorie: Hibernate 4

Hibernate (4) vers JPA

Dans le cadre d’une migration d’un applicatif tournant sous JBoss AS 7 il est désormais voulu passer de l’objet SessionFactory vers son pendant en JPA : EntityManagerFactory.

Via JNDI JBoss expose l’objet EntityManagerFactory avec la propriété suivante dans le fichier persistence.xml :

<property name="jboss.entity.manager.factory.jndi.name" value="java:/jboss/MyEntityManagerFactory" />

Le problème c’est qu’actuellement tout le code de notre application exploite un SessionFactory et il n’est pas question de repasser partout où un SessionFactory est exploité pour le remplacer par un EntityManagerFactory.

En regardant, en mode debug, quelle est la classe implémentant EntityManagerFactory sous Hibernate on trouve qu’il s’agit de la classe org.hibernate.ejb.EntityManagerFactoryImpl. En regardant son code source via grepcode (içi) et pour la version 4.0.1 d’Hibernate (celle exploitée par JBoss AS 7.1.1.Final) on s’aperçoit qu’un objet SessionFactory est directement embarqué dans l’implémentation.

Au final il suffit donc d’utiliser ce code :

SessionFactory sessionFactory = ((EntityManagerFactoryImpl) entityManagerFactory)
    .getSessionFactory();

Note : cette classe se trouve dans le JAR jboss-as-7.1.1.Final/modules/org/hibernate/main/hibernate-entitymanager-4.0.1.Final.jar

A noter qu’il y a une deuxième possibilité : préciser à Hibernate d’exposer l’objet SessionFactory via JNDI. Cela s’effectue via une propriété dans le fichier persistence.xml :


<property name="hibernate.session_factory_name" value="java:/hibernate/HibernateVPCFactory" />

Cette solution n’a cependant pas été retenue pour forcer l’exploitation d’un objet EntityManagerFactory. Mais après rien n’empêche le développeur de récupérer un SessionFactory cependant la méthode permettant de le faire a été marquée @Deprecated pour lui indiquer d’utiliser EntityManagerFactory.

Publicités

JBoss AS 7 : cache Hibernate de second niveau avec une stratégie « read-write »

Par défaut JBoss AS 7 exploite Infinispan en tant que cache Hibernate de second niveau.

Cela se traduit, dans une entité JPA, par l’utilisation d’une annotation @org.hibernate.annotations.Cache. En migrant une application utilisant un cache applicatif de second niveau de type EhCache je suis tombé sur des entités utilisant une stratégie de cache de type « read-write », via l’annotation suivante :

@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)

Au déploiement de l’application, et sans utilisation de EhCache, il y a une erreur d’Infinispan précisant qu’il n’est pas possible d’avoir un cache de type « read-write ». Via la documentation d’Hibernate il est indiqué qu’Infinispan ne gère pas le mode « read-write ». Il est donc, dans ce cas, obligatoire de s’affranchir d’Infinispan.

Pour installer EhCache 2.5.3 en tant que cache de second niveau en tant que module au sein de JBoss AS 7 / Hibernate 4.x il faut exploiter la librairie « hibernate-ehcache » qui permet de faire un lien entre Hibernate 4.x et EhCache alors que pour Hibernate 3.x le lien avec EhCache était directement inclus au sein d’EhCache via la classe net.sf.ehcache.hibernate.EhCacheRegionFactory.

Dans le dossier ${JBOSS_HOME}/modules/net/sf/ehcache/main il faut déposer le fichier ehcache-core-2.5.3.jar et créer le fichier module.xml suivant :

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

<module xmlns="urn:jboss:module:1.1" name="net.sf.ehcache" slot="main">
    <resources>
        <resource-root path="ehcache-core-2.5.3.jar"/>
    </resources>

    <dependencies>
        <module name="org.slf4j"/>
        <module name="javax.transaction.api"/>
        <module name="javax.api" />

        <system export="false">
            <paths>
                <path name="org/xml/sax" />
                <path name="org/xml/sax/ext"/>
                <path name="org/xml/sax/helpers" />
            </paths>
        </system>
    </dependencies>
</module>

Dans le dossier ${JBOSS_HOME}/modules/org/hibernate/cache/ehcache il faut déposer le fichier hibernate-ehcache-4.0.1.Final.jar (pour Hibernate 4.0.1 packagé avec JBoss AS 7.1.1.Final) et créer le fichier module.xml suivant :

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

<module xmlns="urn:jboss:module:1.1" name="org.hibernate.cache.ehcache">
    <resources>
        <resource-root path="hibernate-ehcache-4.0.1.Final.jar"/>
    </resources>

    <dependencies>
        <!-- ce module effectue la liaison entre ehcache et hibernate -->
        <module name="net.sf.ehcache" />
        <module name="org.hibernate" />

        <!-- dépendances d'hibernate -->
        <module name="org.jboss.logging"/>
        <module name="javax.api"/>
        <module name="javax.transaction.api"/>
    </dependencies>
</module>

Enfin au niveau du déploiement d’un ear / d’un war / etc il faut indiquer l’utilisation des modules net.sf.ehcache et org.hibernate.cache.ehcache. Par exemple avec un fichier jboss-deployment-structure.xml d’un ear :

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

<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.1">
    <ear-subdeployments-isolated>false</ear-subdeployments-isolated>

    <deployment>
        <dependencies>
            <module name="net.sf.ehcache" />
            <module name="org.hibernate.cache.ehcache" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>

JBoss AS 7, UserTransaction et new Thread()

Quand, dans JBoss AS 7, on utilise Hibernate 4 le UserTransaction est récupéré automatiquement depuis l’adresse JNDI java:jboss/TransactionManager.

Cependant si un EJB crée un nouveau Thread (new Thread()) et que ce dernier a besoin d’exploiter ce UserTransaction cela provoque une exception indiquant que le UserTransaction ne peut pas être trouvé. La faute en est à la classe org.hibernate.service.jta.platform.internalJBossAppServerJtaPlatform qui récupère le UserTransaction avec un nom JNDI qui correspond aux anciennes version de JBoss : java:comp/UserTransaction.

Ainsi pour résoudre ce problème il suffit de créer une nouvelle classe qui étend la classe JBossAppServerJtaPlatform qui, pour Hibernate 4, doit être configurée par le fichier persistence.xml et par la propriété hibernate.transaction.jta.platform .

public class MyJBossAppServerJtaPlatform extends JBossAppServerJtaPlatform {

	public static final String UT_NAME = "java:jboss/UserTransaction";

	@Override
	protected UserTransaction locateUserTransaction() {
		return (UserTransaction) jndiService().locate(UT_NAME);
	}
}

Cette nouvelle classe retrouve le UserTransaction avec le bon nom JNDI. Pour l’exploiter il suffit juste de change la propriété hibernate.transaction.manager_lookup_class (toujours dans le fichier persistence.xml) :

<property name= »hibernate.transaction.jta.platform » value= »my.package.MyJBossAppServerJtaPlatform » />