JBoss migration – the HAR archive

This is a continuation of the previous article regarding some migration points from JBoss 4.2.2-GA to JBoss 7.1.1 and, presumably, Tomcat 7.

2. The HAR archive

The HAR archive was a nice mechanism which allowed hibernate integration. A ${name}.har file was created, containing all the mappings (*.hbm.xml) and data classes (*.class), allong with a hibernate-service.xml (later renamed to service-hibernate.xml in JBoss 5). This took care of creating the SessionFactory and making it accessible through JNDI. Creating a session in code become:

InitialContext ctx = new InitialContext();
SessionFactory factory = (SessionFactory) ctx.lookup("XName");
return factory.openSession();

2.1 JBoss 4.2.2

This is the hibernate-service.xml from JBoss 4.2.2.

<server>
	<mbean code="org.jboss.hibernate.jmx.Hibernate" name="com.mccsoft:name=XOracleSessionFactory">
		<attribute name="DatasourceName">java:/XOracleDS</attribute>
		<attribute name="Dialect">org.hibernate.dialect.Oracle10gDialect</attribute>
		<attribute name="SessionFactoryName">java:/hibernate/XOracleSessionFactory</attribute>
		<attribute name="CacheProviderClass">net.sf.ehcache.hibernate.SingletonEhCacheProvider</attribute>
 
		<!-- @see http://www.hibernate.org/hib_docs/v3/reference/en-US/html_single/#batch-update -->
		<attribute name="JdbcBatchSize">20</attribute>
		<attribute name="ScanForMappingsEnabled">false</attribute>
		<attribute name="ReflectionOptimizationEnabled">false</attribute>
		<attribute name="ShowSqlEnabled">false</attribute>
		<attribute name="QueryCacheEnabled">true</attribute>
		<attribute name="SecondLevelCacheEnabled">true</attribute>
	</mbean>
</server>

Except for the ScanForMappingsEnabled all of the attributes are hibernate attributes with a name modification. For instance: hibernate.cache.use_query_cache => QueryCacheEnabled. I never understood the reason for this name modification which implied a lot of codding instead of just passing hibernate attributes with the same name.

2.2 JBoss 7.1.1

HAR archives do not exist in JBoss 7 anymore. In fact even the ServiceMBeanSupport does not exist anymore. One possibility is to use some mechanism to create the SessionFactory and inject it into the JNDI. Another possibility is to “use and not use” the new JPA api. By “use” I mean define Hibernate configuration in a persistence.xml file and use the mapping detection feature available. This would allow the plain renaming of the .har to a .jar with an added META-INF/persistence.xml file without the need to hardcode all the mappings and classes in a long list somewhere. By “not use” I mean to have the JPA initialized but use the old SessionFactory instead because there is no reason to change to the new API when the old one works quite well.
However, another problem is that JBoss 7 is bundled with Hibernate 4 and the migration might not be straightforward. However there is still a possibility to bundle a Hibernate as lower as 3.5 in your application. Here is the 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_2_0.xsd"
        version="2.0">
  <persistence-unit name="X">
    <description>X</description>
    <jta-data-source>java:/XOracleDS</jta-data-source>
    <properties>
    	<!-- This tells JBoss to use Hibernate 3 (as low as 3.5) bundled into the application  -->
        <property name="jboss.as.jpa.providerModule" value="hibernate3-bundled" />
        <!--<property name="jboss.as.jpa.managed" value="false"/>-->
        <!-- This will bind the session factory to JNDI as we require -->
        <property name="hibernate.session_factory_name" value="java:/hibernate/XOracleSessionFactory"/>
        <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
        <!-- This is one of the trickiest parts as Hibernate 3.5 does not has a RegionFactory and we must use the one from ehcache to bridge the gap -->
        <property name="hibernate.cache.region.factory_class" value="net.sf.ehcache.hibernate.EhCacheRegionFactory"/>
        <!-- very important to allow same names as in JBoss 4 -->
        <property name="hibernate.cache.region_prefix" value=""/>
        <property name="hibernate.cache.provider_class" value="net.sf.ehcache.hibernate.SingletonEhCacheProvider"/>
        <!-- This will make use of JBoss managed transactions. The factory is already present in JNDI -->
        <property name="hibernate.transaction.factory_class" value="org.hibernate.transaction.JTATransactionFactory"/>
        <property name="hibernate.jdbc.batch_size" value="20"/>
        <property name="hibernate.show_sql" value="false"/>
     	<property name="hibernate.format_sql" value="false"/>
     	<property name="hibernate.cache.use_query_cache" value="true"/>
     	<property name="hibernate.cache.use_second_level_cache" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

I spent a lot of time figuring out some of the options above, especially the ones related to the RegionFactory as JBoss seems to default to org.hibernate.cache.infinispan.JndiInfinispanRegionFactory.

2.3 Tomcat 7

I would have expected this to be very simple and to write maybe a ServletListener which initialized Hibernate and put the SessionFactory into JNDI. However Tomcat JNDI is READ-ONLY. The only solution was to implement an ObjectFactory. However I did not wanted to loose the advantages of autodetection of mappings and classes so I ended again with a hybrid solution.

public class TomcatHibernateFactory implements ObjectFactory {
	private static Logger log = Logger.getLogger(TomcatHibernateFactory.class);
 
	@Override
	public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<!--?,?--> environment) throws Exception {
		SessionFactory sessionFactory;
		try {
			EntityManagerFactory emf = Persistence.createEntityManagerFactory("x");
			HibernateEntityManagerFactory hemf = (HibernateEntityManagerFactory)emf;
			sessionFactory = hemf.getSessionFactory();
		} catch (Throwable e) {
			log.error("TomcatHibernateFactory.getObjectInstance", e);
			throw new NamingException(e.getMessage());
		}
		return sessionFactory;
	}
}

This creates a JPA EntityManagerFactory and converts it to the underlying hibernate implementation to access the SessionFactory. It is then used (defined) in the application META-INF/context.xml (or other context location) as:

<Context>
        <Resource name="hibernate/XOracleSessionFactory" auth="Container"
            type="org.hibernate.SessionFactory"
            factory="com.x.web.TomcatHibernateFactory"/>
</Context>

and then in the WEB-INF/web.xml:

    <resource-ref>
        <res-ref-name>hibernate/XOracleSessionFactory</res-ref-name>
        <res-type>org.hibernate.SessionFactory</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>

The only tiny bit of modification in the code is the name of the JNDI resource used to access the SessionFactory with the java:comp/env prefix. For more info on Tomcat JNDI there is always the official doc.
Also the persistence.xml file has to be modified:

<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_2_0.xsd"
        version="2.0">
  <persistence-unit name="X">
    <description>X</description>
    <!-- this is non-jta-data-source and not a jta-data-source
         avoid: org.hibernate.HibernateException: The chosen transaction strategy requires access to the JTA TransactionManager
     -->
    <non-jta-data-source>java:/comp/env/jdbc/XOracleDS</non-jta-data-source>
    <properties>
    	<!-- Cannot register to JNDI, it's read only 
    	avoid: javax.naming.OperationNotSupportedException: Context is read only
        at org.apache.naming.NamingContext.checkWritable(NamingContext.java:962)
        at org.apache.naming.NamingContext.createSubcontext(NamingContext.java:549)
        at org.apache.naming.NamingContext.createSubcontext(NamingContext.java:575)
        at org.hibernate.util.NamingHelper.bind(NamingHelper.java:92)
        at org.hibernate.impl.SessionFactoryObjectFactory.addInstance(SessionFactoryObjectFactory.java:113)
    	-->
        <!-- <property name="hibernate.session_factory_name" value="java:/hibernate/XOracleSessionFactory"/> -->
        <property name="hibernate.dialect" value="org.hibernate.dialect.Oracle10gDialect"/>
        <property name="hibernate.cache.region.factory_class" value="net.sf.ehcache.hibernate.EhCacheRegionFactory"/>
        <property name="hibernate.cache.region_prefix" value=""/>
        <property name="hibernate.cache.provider_class" value="net.sf.ehcache.hibernate.SingletonEhCacheProvider"/>
        <!-- There is no more TransactionManager 
        avoid: org.hibernate.TransactionException: Could not find UserTransaction in JNDI [java:comp/UserTransaction]
        -->
        <!-- <property name="hibernate.transaction.factory_class" value="org.hibernate.transaction.JTATransactionFactory"/> -->
        <property name="hibernate.jdbc.batch_size" value="20"/>
        <property name="hibernate.show_sql" value="false"/>
     	<property name="hibernate.format_sql" value="false"/>
     	<property name="hibernate.cache.use_query_cache" value="true"/>
     	<property name="hibernate.cache.use_second_level_cache" value="true"/>
    </properties>
 
  </persistence-unit>
</persistence>

Leave a Reply

*