Saturday, September 5, 2009

Geronimo - Using Hibernate as your JPA provider

So we started a redev of a JavaEE project, and I decided to use Geronimo as the application server. Geronimo uses OpenEJB for it's EJB container, and I guess this was my biggest motivator. OpenEJB is truly a brilliant project. I also replaced Toplink with Hibernate for the persistence.

The justification of Hibernate was definitely maturity. It's much faster that EclipseLink/Toplink and definitely less buggy. I've had too many problems with Toplink to ever willingly use it again where I have a better option. To get Hibernate integrated into Geronimo wasn't easy though. It's the first time I use Geronimo to this degree. Previously I just ported a small project to Geronimo which had nothing more than a JMS queue and an MDB. To get Hibernate running in Geronimo isn't difficult, though I didn't know how, and thus struggled a bit. So this entry will be my second one describing how to use a specific persistence framework in OpenEJB.

Deploying Hibernate in Geronimo
The basic idea on using Hibernate is too deploy the dependencies and setup a special transaction manager. This explanation was based on Hibernate 3.3. So first get hold of:
  • Hibernate Distribution 3.3.2
  • Hibernate Annotations 3.4.0, and
  • Hibernate EntityManager 3.4.0
Then extract the archives, and retrieve from the extracted directories the following JAR files. I recommend you copy them into a temporary directory for easier maintenance while deploying Hibernate as a JPA provider.
  • Hibernate Distribution
    1. hibernate3.jar
    2. lib/required/antlr-2.7.6.jar
    3. lib/required/commons-collections-3.1.jar
    4. lib/required/dom4j-1.6.1.jar
    5. lib/required/javassist-3.9.0.GA.jar
    6. lib/required/jta-1.1.jar
    7. lib/required/slf4j-api-1.5.8.jar
  • Hibernate Annotations
    1. hibernate-annotations.jar
    2. lib/ejb3-persistence.jar
    3. lib/hibernate-commons-annotations.jar
    4. lib/slf4j-api.jar
  • Hibernate EntityManager
    1. hibernate-entitymanager.jar
Since the current Geronimo (2.1) uses OpenEJB 3.0, you need to create a Hibernate Transaction Manager Lookup class and add it as a dependency to be used wherever we use Hibernate. For this I'll explain how to do it as you would in Netbeans. If you use another development method/IDE, adapt appropriately.
  1. Start a new Java Class Library project and call it GeronimoTransactionManager.
  2. Create a class "org.hibernate.transaction.GeronimoTransactionManagerLookup".
  3. Paste the following code into this new class.
    package org.hibernate.transaction;

    import java.util.Properties;
    import java.util.Set;
    import javax.transaction.TransactionManager;
    import org.apache.geronimo.gbean.AbstractName;
    import org.apache.geronimo.gbean.AbstractNameQuery;
    import org.apache.geronimo.kernel.Kernel;
    import org.apache.geronimo.kernel.KernelRegistry;
    import org.hibernate.HibernateException;
    import org.hibernate.transaction.TransactionManagerLookup;

    public class GeronimoTransactionManagerLookup implements TransactionManagerLookup
    {
    public static final String UserTransactionName = "java:comp/UserTransaction";

    public TransactionManager getTransactionManager(Properties props) throws HibernateException
    {
    try
    {
    Kernel kernel = KernelRegistry.getSingleKernel();
    AbstractNameQuery query = new AbstractNameQuery(TransactionManager.class.getName());
    Set<AbstractName> names = kernel.listGBeans(query);
    if (names.size() != 1)
    {
    throw new IllegalStateException("Expected one transaction manager, not " + names.size());
    }
    AbstractName name = names.iterator().next();
    TransactionManager transMg = (TransactionManager) kernel.getGBean(name);
    return (TransactionManager) transMg;
    }
    catch (Exception e)
    {
    e.printStackTrace();
    System.out.println();
    throw new HibernateException("Geronimo Transaction Manager Lookup Failed", e);
    }
    }

    public String getUserTransactionName()
    {
    return UserTransactionName;
    }
    }
  4. Then under the project's properties, make sure "JDK 5" is selected in the "Source/Binary Format" drop down on the "Sources" property page.
  5. Before closing the project properties dialog, goto Libraries and add all the Hibernate JAR files we listed above to your classpath by clicking the "Add JAR/Folder" button and selecting all of them. If you are using Netbeans you might already have a "Hibernate JPA" library configured in the IDE, so in this case you can instead click the "Add Library" button and select "Hibernate JPA" from the list. We only need the our class to compile against Hibernate, so it doesn't matter if you don't use the exact version we will be deploying into Geronimo. As long as the API is the same, you should be able to safely compile against any Hibernate 3.3 JARs.
  6. Then you need to add one more JAR as a library. This will be the Geronimo kernel jar, which you can find in the Geronimo distribution directory under: repository/org/apache/geronimo/framework/geronimo-kernel/2.1.4/geronimo-kernel-2.1.4.jar
  7. Confirm the properties dialog by pressing the "OK" button.
  8. Now build the project and find the GeronimoTransactionManager.jar file in the project's "dist" directory.
  9. This is the JAR we will use when adding repository resources to Geronimo in the next step, so put it with the rest of the Hibernate JAR files you collected above.
Now we will deploy the resources to Geronimo. When you add a JAR resource, you do so on the web console under the Services->Repository portlet. You select the JAR file by clicking the 'Browse' button and then enter the 4 details, nl. Group, Artifact, Version and Type. After you've added it, Geronimo will add them to it's "repository" as: group/artifact/version/type/{artifact}-{version}.jar. Internally it's ID will be "group/artifact/version/type". I will use this format when referring to them, so upload each of the following JAR files using this format to determine which values should go into each field.
  • hibernate3.jar - hibernate/core/3.3/jar
  • lib/required/antlr-2.7.6.jar - hibernate/antlr/2.7.6/jar
  • lib/required/commons-collections-3.1.jar - hibernate/commons-collections/3.1/jar
  • lib/required/dom4j-1.6.1.jar - hibernate/dom4j/1.6.1/jar
  • lib/required/javassist-3.9.0.GA.jar - hibernate/javassist/3.9.0.GA/jar
  • lib/required/jta-1.1.jar - hibernate/jta/1.1/jar
  • hibernate-annotations.jar - hibernate/annotations/3.4/jar
  • lib/ejb3-persistence.jar - hibernate/jpa/3.0/jar
  • lib/hibernate-commons-annotations.jar - hibernate/commons-annotations/3.4/jar
  • hibernate-entitymanager.jar - hibernate/entitymanager/3.4/jar
  • GeronimoTransactionManager.jar - hibernate/GeronimoTransactionManager/1.0/jar
Now that you've setup all the dependencies you can create your EJB project. All you need to do is
  1. Create a JDBC pool through the Geronimo console (in this example called: console.dbpool/jdbc_myDatabasePool/1.0/rar)
  2. List the following dependencies in your EJB project's deploy plan (geronimo-ejb.xml):
    • Your JDBC pool
    • All the Hibernate dependencies we added above
    • Geronimo's "slf4j-api" dependency in your deploy plan.In Geronimo 2.1.4 it's called: org.slf4j/slf4j-api/1.4.3/jar
    You can goto the Service->Repository portlet and click on a resource to see it's usage. When building a deploy plan it's the quickest way to add dependencies. Would be great if the Geronimo developers can make a generic dependency builder to generate the deploy plan, like the one they have when building WAR deploy plans. You EJB deploy plan would look something like this:
    <?xml version="1.0" encoding="UTF-8"?>
    <openejb-jar xmlns="http://openejb.apache.org/xml/ns/openejb-jar-2.2"
    xmlns:naming="http://geronimo.apache.org/xml/ns/naming-1.2"
    xmlns:sec="http://geronimo.apache.org/xml/ns/security-2.0"
    xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2">

    <dep:environment>
    <dep:moduleId>
    <dep:groupId>user</dep:groupId>
    <dep:artifactId>MyHibernateEJB</dep:artifactId>
    <dep:version>1.0</dep:version>
    <dep:type>jar</dep:type>
    </dep:moduleId>

    <dep:dependencies>
    <dep:dependency>
    <dep:groupId>console.dbpool</dep:groupId>
    <dep:artifactId>jdbc_myDatabasePool</dep:artifactId>
    <dep:version>1.0</dep:version>
    <dep:type>rar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>core</dep:artifactId>
    <dep:version>3.3</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>annotations</dep:artifactId>
    <dep:version>3.4</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>antlr</dep:artifactId>
    <dep:version>2.7.6</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>commons-annotations</dep:artifactId>
    <dep:version>3.4</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>commons-collections</dep:artifactId>
    <dep:version>3.1</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>dom4j</dep:artifactId>
    <dep:version>1.6.1</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>entitymanager</dep:artifactId>
    <dep:version>3.4</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>javassist</dep:artifactId>
    <dep:version>3.9.0.GA</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>jpa</dep:artifactId>
    <dep:version>3.0</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>jta</dep:artifactId>
    <dep:version>1.1</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>hibernate</dep:groupId>
    <dep:artifactId>GeronimoTransactionManager</dep:artifactId>
    <dep:version>1.0</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    <dep:dependency>
    <dep:groupId>org.slf4j</dep:groupId>
    <dep:artifactId>slf4j-api</dep:artifactId>
    <dep:version>1.4.3</dep:version>
    <dep:type>jar</dep:type>
    </dep:dependency>
    </dep:dependencies>
    </dep:environment>
    </openejb-jar>
  3. Specify the transaction manager in your persistence.xml. Here is an example. Note how the JTA data source is specified by only using the artifact ID of the pool you created, with any underscore (_) characters replaced with a "/". This is how Geronimo maps repository resource's module IDs to JNDI names.
    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="1.0" 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_1_0.xsd">
    <persistence-unit name="MyHibernateEJB-PU" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/myDatabasePool</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
    <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    <property name="hibernate.transaction.manager_lookup_class"
    value="org.hibernate.transaction.GeronimoTransactionManagerLookup"/>
    </properties>
    </persistence-unit>
    </persistence>
  4. And deploy your EJB.
All should be done now, and you can use Hibernate like you would any other persistence framework.

2 comments:

fakalit said...

thanks for the awesome article. just one question tough; when i implement TransactionManagerLookup interface, i am expected to provide a method called getTransactionIdentifier. i'm guessing that the geronimo 2.2 kernel change is responsible, but since i don't know what these are all about, i have no idea how to implement it. any suggestions for me to go after?

Quintin said...

I recall running into the same problem with Geronimo 2.2. It accepts a parameter of type "Transaction" and you can simply return this same parameter. So something like the following should work:

public Object getTransactionIdentifier(Transaction tx)
{
return tx;
}

It's a new method added to the Hibernate API, used mainly for lookups/caching purposes, and since these lookups should generally be very unique a reference to the transaction itself works perfectly well.

Using an older version of Hibernate is another way of getting around it. According to the documentation, it was mainly added for use in servers they refer to as "Unfriendly JEE containers" like WebSphere. Exactly for what I can't say, and neither can I recall in which version of Hibernate it was added. It sounds very specific so I guess it's only intended for WebSphere.