Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

Integrating BTM with Hibernate

Hibernate can be integrated straight with any JTA transaction manager. These instructions have been verified against BTM 1.2 and Hibernate 3.2.6.

The biggest added value (omitting the fact that you can use Hibernate and two databases) is Hibernate's Current Session context management with JTA. You do not have to take care about opening nor closing Session as Hibernate will automatically bind them to the JTA transaction's lifecycle. You just have to make sure JTA transactions are properly started and ended.

Info
titleJPA, Hibernate and BTM

The example discussed here uses the Hibernate API but the JPA API could be used as well. You just need to use the EntityManager but the same configuration applies.

Contents

Table of Contents
maxLevel3
minLevel2

JTA datasources

Hibernate cannot directly create a BTM PoolingDataSource. You will have to create them yourself (either via the API or the Resource Loader) and make sure they are bound to a JNDI server.

Setting up a JNDI server

You have to bind the datasources to some JNDI server. You can use any one you wish, including the one embedded in your application server.

In the case you want to run Hibernate in a standalone application, you can use Tomcat's JNDI server as it is very easy to use it in a standalone J2SE application. Just create a jndi.properties file at the root of your classpath. It should only contain this line:

Code Block
java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory

Then add these two jar files to your classpath:

Code Block
naming-factory.jar
naming-resources.jar

They are available in any Tomcat 5.5 distribution, just look into the TOMCAT_HOME/common/lib folder.

This will configure Tomcat's JNDI server as the default implementation. It is a simple intra-VM server that implements the most important JNDI features. You can now just create a InitialContext with the no-args constructor to have access to it.

API way: Creating the datasources

As you can expect, you will need to create one PoolingDataSource per database. Say that you want to use two Embedded Derby databases, and configure them via the BTM API. Here is what your code would look like:

Code Block
PoolingDataSource ds1 = new PoolingDataSource();
ds1.setUniqueName("jdbc/testDS1");
ds1.setClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
ds1.setMaxPoolSize(3);
ds1.getDriverProperties().put("databaseName", "users1");
ds1.init();

PoolingDataSource ds2 = new PoolingDataSource();
ds2.setUniqueName("jdbc/testDS2");
ds2.setClassName("org.apache.derby.jdbc.EmbeddedXADataSource");
ds2.setMaxPoolSize(3);
ds2.getDriverProperties().put("databaseName", "users2");
ds2.init();
Tip
titleDatasource's unique name and JNDI location correspondence

It is usually a good idea to set a datasource's unique name to the same value as its JNDI location although this is not mandatory.

Finally, here is the code to bind the datasources:

Code Block
Context ctx = new InitialContext();
ctx.createSubcontext("jdbc");
ctx.rebind("jdbc/testDS1", ds1);
ctx.rebind("jdbc/testDS2", ds2);
ctx.close();

Resource Loader way: Creating the datasources

You can use BTM's Resource Loader instead of the BTM API. It is usually a good idea when you want to create a fully standalone application as you can get rid of the datasources creation, JNDI binding and shutdown code.

Create a datasources.properties file in the current directory containing these properties:

Code Block
bitronix.tm.resource.bind=true

resource.ds1.className=org.apache.derby.jdbc.EmbeddedXADataSource
resource.ds1.uniqueName=jdbc/testDS1
resource.ds1.maxPoolSize=3
resource.ds1.driverProperties.databaseName=users1

resource.ds2.className=org.apache.derby.jdbc.EmbeddedXADataSource
resource.ds2.uniqueName=jdbc/testDS2
resource.ds2.maxPoolSize=3
resource.ds2.driverProperties.databaseName=users2
Tip
titleResource Loader JNDI binding

When you configure the Resource Loader to bind datasources to JNDI (by setting the bitronix.tm.resource.bind property to true) it will bind them using the unique name as the JNDI location.

In your application code, you will have to configure BTM to use the resource loader:

Code Block
TransactionManagerServices.getConfiguration().setResourceConfigurationFilename("./datasources.properties");
userTransaction = TransactionManagerServices.getTransactionManager();

This has the exact same behavior as creating the PoolingDataSource objects and binding them to JNDI yourself. It is just more convenient.

Hibernate Session factories

You need to configure exactly one SessionFactory per datasource.

Datasource JNDI location

You have to tell Hibernate where to get the BTM datasource via JNDI. Add a connection.datasource property and set its value to the JNDI location of your datasource:

Code Block
xml
xml
<property name="connection.datasource">jdbc/testDS1</property>

Current session context

You have to set current_session_context_class to jta.

Code Block
xml
xml
<property name="current_session_context_class">jta</property>

Transaction factory class

You have to set transaction.factory_class to org.hibernate.transaction.JTATransactionFactory.

Code Block
xml
xml
<property name="transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>

Transaction manager lookup class

You have to set transaction.manager_lookup_class to an implementation of TransactionManagerLookup that you have to create yourself. Here is an example implementation if this interface:

Code Block
package bitronix.examples.hibernate;

import java.util.Properties;
import javax.transaction.TransactionManager;
import org.hibernate.HibernateException;
import org.hibernate.transaction.TransactionManagerLookup;
import bitronix.tm.TransactionManagerServices;

public class BitronixTransactionManagerLookup implements TransactionManagerLookup {

    public TransactionManager getTransactionManager(Properties props) throws HibernateException {
        return TransactionManagerServices.getTransactionManager();
    }

    public String getUserTransactionName() {
        return "UserTransaction";
    }

}

Then add this property:

Code Block
xml
xml
<property name="transaction.manager_lookup_class">bitronix.examples.hibernate.BitronixTransactionManagerLookup</property>

SessionFactory XML configuration files

Here is what the hibernate_testDS1.cfg.xml file will look like for the first datasource. Some other mandatory properties also have to be added, like the dialect, cache.provider_class and of course the required object mappings.

Code Block
XML
XML
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>
        <property name="connection.datasource">jdbc/testDS1</property>
        <property name="current_session_context_class">jta</property>
        <property name="transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
        <property name="transaction.manager_lookup_class">bitronix.examples.hibernate.BitronixTransactionManagerLookup</property>

        <property name="dialect">org.hibernate.dialect.DerbyDialect</property>
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
        <property name="show_sql">true</property>

        <mapping resource="bitronix/examples/hibernate/entities/User.hbm.xml"/>
    </session-factory>

</hibernate-configuration>

And here is the hibernate_testDS2.cfg.xml for the second datasource:

Code Block
XML
XML
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>
        <property name="connection.datasource">jdbc/testDS2</property>
        <property name="current_session_context_class">jta</property>
        <property name="transaction.factory_class">org.hibernate.transaction.JTATransactionFactory</property>
        <property name="transaction.manager_lookup_class">bitronix.examples.hibernate.BitronixTransactionManagerLookup</property>

        <property name="dialect">org.hibernate.dialect.DerbyDialect</property>
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
        <property name="show_sql">true</property>

        <mapping resource="bitronix/examples/hibernate/entities/User.hbm.xml"/>
    </session-factory>

</hibernate-configuration>

End result

Now that Hibernate and BTM are properly configured, you can simply use the JTA and Hibernate APIs in your application.

Application code

Here is what your code will look like when you want to update the content of both databases atomically:

Code Block
for (int i=0; i<10 ;i++) {
    System.out.println("Iteration #" + (i+1));
    userTransaction.setTransactionTimeout(60);
    userTransaction.begin();
    
    try {
        System.out.println("*** DB1 ***");
        persistUser(sf1, "user");
        listUsers(sf1);
    
        System.out.println("*** DB2 ***");
        persistUser(sf2, "user");
        listUsers(sf2);
    
        userTransaction.commit();
    }
    catch (Exception ex) {
        ex.printStackTrace();
        userTransaction.rollback();
    }
}

Say that persistUser() creates a new user, in no way will a user be created in one database and not in the other.

Download

You can download a sample runnable application putting these explanations in practice. It contains all the code that has been skipped for clarity in this page. Both the API and Resource Loader ways are implemented so you can try both and see which one you prefer.

You can download this demo here: HibernateBTM12.zip.

There is an ant build.xml file included as well as a the necessary batch and shell scripts required to run the application from Windows or Unix.

Before you run the application, you have to create the Derby database. Just run the included derby-create.sh or derby-create.bat script to do so, it will create two directories called users1 and users2. Then you can start the demo by either running run_api.sh or run_api.bat for the API version, run_rl.sh or run_rl.bat for the Resource Loader version.

Here is the list of JAR files with version required to run this demo. They're all included in the downloadable ZIP file.

JAR name

Version

btm-1.2.jar

BTM 1.2

geronimo-spec-jta-1.0.1B-rc4.jar

BTM 1.2

slf4j-api-1.4.3.jar

SLF4J 1.4.3

slf4j-jdk14-1.4.3.jar

SLF4J 1.4.3

derby-10.3.1.4.jar

Derby 10.3.1.4

derbytools-10.3.1.4.jar

Derby 10.3.1.4

antlr-2.7.6.jar

Hibernate 3.2.6

asm.jar

Hibernate 3.2.6

cglib-2.1.3.jar

Hibernate 3.2.6

commons-collections-2.1.1.jar

Hibernate 3.2.6

commons-logging-1.0.4.jar

Hibernate 3.2.6

dom4j-1.6.1.jar

Hibernate 3.2.6

hibernate3.jar

Hibernate 3.2.6

naming-factory.jar

Tomcat 5.5.23

naming-resources.jar

Tomcat 5.5.23