Using Hibernate with JBossThis guide is intended for developers who use JBoss as the target platform for their J2EE applications. The first method used here is derived from Konstantin Pribluda's Hibernate demo package in the forum - http://sourceforge.net/forum/message.php?msg_id=1801820 - and can be used on JBoss 3.0.x and 3.2 with Hibernate 1.2.2, and without XDoclet (which most developers use though). The second discussion outlines the use of the service with JBoss 3.2.X, Hibernate 2.1 and XDoclet.
Steps to deploy a Hibernate 1.X or 2.0 service in JBoss
1. Prepare your JDBC DataSource for use with Hibernate.Please refer to JBoss docs, or the examples included in the JBoss distribution.
2. Copy the necessary jars to ${JBOSS_HOME}/server/default/lib.The following jars are required (maybe redundant?), which can be found in ${HIBERNATE_HOME}/lib:
- cglib.jar
- commons-collections.jar
- commons-logging.jar
- commons-lang.jar
- jcs.jar
- odmg.jar
- and of course, hibernate.jar :)
If you are using Hibernate 2, you will also need to copy dom4j.jar too. Due to some mysterious ClassLoader problems in JBoss, these jars have to be copied to ${JBOSS_HOME}/server/default/lib. And in addition, you may need to use the latest versions of the commons-*.jar if JBoss failed to start up after copying (the commons-*.jar in Hibernate's distribution seems to cause conflict with JBoss itself... maybe it means not all the commons-*.jar are necessary? :\).
3. Prepare the directory structure of the service archive.The Hibernate service will be deployed as a service archive (SAR), which will include all the entity objects, mapping XML files, and the JBoss service XML file itself. Prepare a directory structure like this:
org/mytest/objects/MasterAccount.class
org/mytest/objects/SubAccount.class
org/mytest/objects/Password.class
mappings/MasterAccount.hbm.xml
mappings/SubAccount.hbm.xml
mappings/Password.hbm.xml
META-INF/
4. Prepare the service XML file.Fire up your favorite text editor and type in the Hibernate service MBean configuration, like the one below:
<server>
<mbean code="cirrus.hibernate.jmx.HibernateService" name="jboss.jca:service=HibernateFactory,
name=HibernateFactory">
<depends>jboss.jca:service=RARDeployer</depends>
<!-- Make it deploy ONLY after DataSource had been started -->
<depends>jboss.jca:service=LocalTxCM,name=DataSource</depends>
<attribute name="MapResources">mappings/MasterAccount.hbm.xml,
mappings/SubAccount.hbm.xml,
mappings/Password.hbm.xml</attribute>
<attribute name="JndiName">java:/hibernate/HibernateFactory</attribute>
<attribute name="Datasource">java:/jdbc/DataSource</attribute>
<attribute name="Dialect">cirrus.hibernate.sql.MySQLDialect</attribute>
<attribute name="TransactionStrategy">cirrus.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">
cirrus.hibernate.transaction.JBossTransactionManagerLookup
</attribute>
<attribute name="UseOuterJoin">false</attribute>
<attribute name="ShowSql">false</attribute>
<attribute name="UserTransactionName">java:/UserTransaction</attribute>
</mbean>
</server>
The following classpaths will work with hibernate 2.x
<server>
<mbean code="net.sf.hibernate.jmx.HibernateService" name="jboss.jca:service=HibernateFactory,
name=HibernateFactory">
<depends>jboss.jca:service=RARDeployer</depends>
<depends>jboss.jca:service=LocalTxCM,name=MySqlDS</depends>
<!-- Make it deploy ONLY after DataSource had been started -->
<attribute name="MapResources">mappings/Attribute.hbm.xml</attribute>
<attribute name="JndiName">java:/hibernate/HibernateFactory</attribute>
<attribute name="Datasource">java:/MySqlDS</attribute>
<attribute name="Dialect">net.sf.hibernate.dialect.MySQLDialect</attribute>
<attribute name="TransactionStrategy">net.sf.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">net.sf.hibernate.transaction.JBossTransactionManagerLookup</attribute>
<attribute name="UseOuterJoin">false</attribute>
<attribute name="ShowSql">false</attribute>
<attribute name="UserTransactionName">UserTransaction</attribute>
</mbean>
</server>
Of course, you will want to change the attributes above.
- MapResources: a list of the mapping files used, separated by commas.
- JndiName: the JNDI bound name of the Hibernate service.
- Datasource: the JDBC data source this Hibernate service will use.
- Dialect: the SQL dialect the Hibernate service will use. please refer to http://hibernate.bluemars.net/../reference/html/session-configuration.html to see the list of available dialects.
- UseOuterJoin: whether Hibernate should use outer join fetching.
- ShowSql: prints out the SQL (actually a PreparedStatement) generated by Hibernate when set to true.
Save this file as jboss-service.xml and put it under META-INF of your directory structure.
5. Pack the directory up as a JAR with .sar as extension.Issue a jar command to jar the above contents to a service archive, which ends with .sar. It is not required to use sar as its extension, just for some sort of conformance... :) You may also want to do this job with an Ant target.
6. Copy the archive to ${JBOSS_HOME}/server/default/deploy.You will then see the Hibernate service initialized. You may need to check with your mappings and/or jboss-service.xml in case any exception is thrown.
Using the JBoss MBean with Hibernate 2.1, JBoss 3.2.X and XDoclet
1. Prepare your JDBC DataSource for use with Hibernate.Please refer to JBoss docs, or the examples included in the JBoss distribution.
2. Set up for XDocletSee the excellent tutorial http://www.hibernate.org/72.html The only differences are: Use XDoclet 1.2beta4. Then you don't have to do "Calling XDoclet from your Ant build script" Step 4 because beta4 has been fixed for Hibernate 2. Your hibernatedoclet Ant task should look as follows:
<!-- =================================================================== -->
<!-- generates the hibernate HBM.XML files and SAR descriptor -->
<!-- =================================================================== -->
<target name="generate-Hibernate"
description="Generates Hibernate class descriptor files."
depends="compile">
<!-- copy additional resources for the Hibernate XDoclet task to the mergedir -->
<copy todir="${build.resources}/sar/hibernate">
<fileset dir="${src.dir}">
<include name="**/hibernate/hibernate-properties-*.xml"/>
</fileset>
<fileset dir="${resources}/sar/hibernate">
<include name="jboss-service-custom.xdt"/>
</fileset>
</copy>
<!-- Execute the hibernatedoclet task -->
<hibernatedoclet
destdir="${build.resources}/sar/hibernate"
excludedtags="@version,@author,@todo,@see,@desc"
addedtags="@xdoclet-generated at ${TODAY}@copyright yourCompany,@author yourCompany,@version ${version}"
force="${xdoclet.force}"
mergedir="${build.resources}/sar/hibernate"
verbose="false">
<fileset dir="${src.dir}">
<include name="**/hibernate/*.java"/>
</fileset>
<hibernate version="2.0"/>
<jbossservice
destdir="${build.resources}/sar/hibernate"
serviceName="Hibernate"
jndiName="${hibernate.jndi.name}"
dataSource="${hibernate.datasource.name}"
dialect="${hibernate.dialect}"
useOuterJoin="true"
transactionManagerStrategy="net.sf.hibernate.transaction.JBossTransactionManagerLookup"
transactionStrategy="net.sf.hibernate.transaction.JTATransactionFactory"
userTransactionName="UserTransaction"
/>
</hibernatedoclet>
</target>
Running this target gets you all the hbm.xml s and a jboss-service.xml in the destdir, as follows:
jboss-service.xml
com/yourcompany/package1/YourClass1.hbm.xml
com/yourcompany/package1/YourClass2.hbm.xml
com/yourcompany/package1/YourClass3.hbm.xml
com/yourcompany/package1/subpackage2/YourClass4.hbm.xml
...
The jboss-service.xml looks like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE server>
<!-- Generated file - Do not edit! -->
<server>
<mbean code="net.sf.hibernate.jmx.HibernateService" name="jboss.jca:service=Hibernate">
<depends>jboss.jca:service=RARDeployer</depends>
<attribute name="MapResources">com/yourcompany/package1/YourClass1.hbm.xml,
com/yourcompany/package1/YourClass2.hbm.xml,
com/yourcompany/package1/YourClass3.hbm.xml,
com/yourcompany/package1/subpackage1/YourClass4.hbm.xml
</attribute>
<attribute name="JndiName">java:/HibernateFactory</attribute>
<attribute name="Datasource">java:/myDS</attribute>
<attribute name="Dialect">net.sf.hibernate.dialect.PostgreSQLDialect</attribute>
<attribute name="UseOuterJoin">true</attribute>
<attribute name="ShowSql">false</attribute>
<attribute name="UserTransactionName">UserTransaction</attribute>
<attribute name="TransactionStrategy">net.sf.hibernate.transaction.JTATransactionFactory</attribute>
<attribute name="TransactionManagerLookupStrategy">net.sf.hibernate.transaction.JBossTransactionManagerLookup</attribute>
</mbean>
</server>
3. Package the Hibernate SARAgain, an Ant task:
<!-- =================================================================== -->
<!-- Hibernate SAR build -->
<!-- =================================================================== -->
<target depends="generate-Hibernate" name="hibernate-sar">
<jar destfile="${build.lib}/hibernateStartup.sar">
<!-- Get the generated hbm.xml files -->
<fileset dir="${build.resources}/sar/hibernate">
<include name="**/*.hbm.xml"/>
</fileset>
<!--
<fileset dir="${lib.dir}">
<include name="cglib2.jar"/>
<include name="commons-collections.jar"/>
<include name="commons-logging.jar"/>
<include name="dom4j.jar"/>
<include name="hibernate2.jar"/>
<include name="odmg.jar"/>
<include name="ehcache.jar"/>
</fileset>
-->
<metainf dir="${build.resources}/sar/hibernate">
<include name="jboss-service.xml"/>
</metainf>
</jar>
<antcall target="hibernate-schemaexport" />
</target>
A couple of things to note:
- I do not have my Hibernate Java class files in the SAR. In my case, the class files are used by the rest of my code including a WAR, so I have them in a separate jar that is in my EAR classpath. In my experience with JBoss, code in a WAR cannot find classes in a SAR.
- See the next section for an explanation of why the fileset with the jars is commented out.
4. Hibernate Support jars and Final packagingAs of Hibernate 2.1 rc1, this is the set of jars from the Hibernate distribution that you need:
hibernate2.jar
cglib2.jar
commons-collections.jar
commons-logging.jar
dom4j.jar
odmg.jar
ehcache.jar
All other jars that Hibernate needs are supplied by the base JBoss default deployment. As of 2.1 rc1, ehcache is the required and the only caching scheme you can use. The final release of 2.1 will allow you to change the caching scheme via: <attribute name="CacheProvider">net.sf.ehcache.hibernate.Provider</attribute> in the jboss-service.xml. See below for the implications for using XDoclet. Each caching scheme requires a different set of supporting jars. Check out lib/libs-readme.txt in the Hibernate distribution about the requirements of the different schemes. In the build of the SAR above, the fileset tag with the jars is commented out because I am still experimenting with the packaging. Currently, those jars go in my EAR and are referred to in my EAR application.xml (see below). If you want to deploy the Hibernate SAR on its own outside of an EAR, including the above Hibernate related JARs in the SAR should work. Dropping this SAR into the server/default/deploy directory does a hot deploy. In an EAR, I have:
hibernateStartup.sar (as built above)
myHibernateClasses.jar
hibernate2.jar
cglib2.jar
commons-collections.jar
commons-logging.jar
dom4j.jar
odmg.jar
ehcache.jar
meta-inf/application.xml
meta-inf/jboss-app.xml
My application.xml looks like:
<application>
<display-name>Company server</display-name>
<description>Company server</description>
<!-- uses session beans to get to Hibernate POJOs -->
<module>
<web>
<web-uri>CompanyUI.war</web-uri>
<context-root>/company</context-root>
</web>
</module>
<!-- Just session beans accessing hibernate -->
<module>
<ejb>companyEJB.jar</ejb>
</module>
<module>
<java>myHibernateClasses.jar</java>
</module>
<module>
<java>hibernate2.jar</java>
</module>
<module>
<java>cglib2.jar</java>
</module>
<module>
<java>commons-logging.jar</java>
</module>
<module>
<java>commons-collections.jar</java>
</module>
<module>
<java>dom4j.jar</java>
</module>
<module>
<java>odmg.jar</java>
</module>
<module>
<java>ehcache.jar</java>
</module>
</application>
My jboss-app.xml looks like:
<?xml version="1.0"?>
<jboss-app>
<loader-repository>company:loader=company.ear</loader-repository>
<module>
<service>hibernateStartup.sar</service>
</module>
</jboss-app>
5. Additional attributes for the SAR jboss-service.xml
- MapResources
- JndiName
- UserName
- Password
- Datasource
- Dialect
- UseOuterJoin
- ShowSql
- UserTransactionName
- TransactionStrategy
- TransactionManagerLookupStrategy
UserTransactionName, TransactionStrategy and TransactionManagerLookupStrategy appear to be required under JBoss if you are using Hibernate transactions. Based on looking at the code coming out for 2.1 final (but not 2.1 rc1, which still has only the original set of attributes from Hibernate 2), the following new attributes can appear in the jboss-service.xml:
- MaxFetchDepth
- JdbcFetchSize
- CacheProvider
- UseQueryCache
- QuerySubstitutions
- DefaultSchema
See the documentation elsewhere about hibernate.properties and hibernate.cfg.xml for details.
6. Generating a complete jboss-service.xml from XDoclet for Hibernate 2.1If you check back in the section in the hibernatedoclet Ant task, you will see that the jbossservice tag has a limited set of attributes. With Hibernate 2.1, the set of attributes that can be given in the jboss-service.xml for the MBean is expanded, and there will be no out of the box method for setting them via the hibernatedoclet task in XDoclet 1.2beta4. All is not lost! You can include a merge point for XDoclet, which is a file that is included as is by XDoclet into the jboss-service.xml. In the mergedir directory specified in the hibernatedoclet task, put a file named jboss-service-custom.xdt. This should contain the text you want to include into the jboss-service.xml that you can't with hibernatedoclet, ala:
<depends>jboss.jca:service=LocalTxCM,name=MySqlDS</depends>
<attribute name="CacheProvider">net.sf.ehcache.hibernate.Provider</attribute>
There is a bug in XDoclet 1.2beta3 and 1.2beta4, which means the contents of jboss-service-custom.xdt are included in the wrong place. See the comments below for the fix.
Commentary
Question: What are the advantages to this as opposed to getting a pooled connection and relying on JBoss for transaction handling?Using Object-Relation-Mapping make application layer seperated from the Database access layer. Compare with the tradition Object-Storage in EJB, it allow better control on the data in the relational database. Also connection pooling could be done inside Hibernate.
Question: Why will I want to use this method to integrate Hibernate service into JBoss?Of course, you can use the traditional "hibernate.cfg.xml" or "hibernate.properties" method which will also work seamlessly in JBoss. However, using the JMX integration method, the Hibernate service is deployed as a JMX MBean, which you can modify its properties through the JMX console. That is, you can change the JDBC data source the service uses, include/ exclude mapping files included in the .sar, enable/ disable SQL verbose to the console, etc. while the server is running. This was (supposed to be) why the Hibernate JBoss MBean was written.
Not using the MBeanIf you are going to try to get JBoss working without using the service, the libraries listed above are a good place to start. The classloader errors that JBoss gives are not necessarily for the actual class that is missing, JBoss merely reports the top-level class it was trying to load if there is a failure anywhere inside the transitive closure of all the libraries Hibernate needs. It can be very confusing and take a while to debug. So save yourself the hassle of trying to figure out what the minimum set of libraries that are needed from the distro -- this is the list. The other thing to consider in regards to the JBoss classloader is that other strange errors can be introduced if you have utility code (static Session recovery methods, for instance) that is shared by both the web and EJB tiers of your app. If the code gets too complex, you can find yourself in the weeds trying to figure out why your webapp is suddenly throwing NoClassDefFoundError in a very strange location of the application that you had not touched for many days or weeks. This is often a sign of a new dependency within the common code that is causing the object graph to get much much larger (think about adding one person to your friendster.com account ;) and suddenly everything stops. So by using the service, you can isolate the dependencies better between the tiers and save yourself a visit from Mr. Murphy two days after you forgot all the classloader details that you had studied a month ago. So, basically, use the MBean!
|