Tuesday, December 31, 2013


XA Transactions with Spring and Hibernate

Working for my current client, the need for XA transactions has finally arisen. We’re restructuring a good chunk of the infrastructure here, and as a part of that, we’re working on a new application to handle the import of data from an SCM (Supply Chain Management) System. This RESTful app handles requests from an intermediary to take various types of objects from the new SCM system, and import them into existing (though altered) databases. The thought is (or … ummm… was) that as many as 9 different database would have to be accessed and manipulated to handle all the data that will eventually be imported. Rather than deal with 9 different transaction managers, why not use XA transactions and deal with just a single transaction manager? This would also remove the need to create views in sql server that provide access to data data required by one database, that exists in another.
We implemented the XA transaction logic in our Spring configuration some time ago; it’s long past time to document this in this blog, though, because much of what we ran into, we couldn’t find in any single place. We found lots of pointers on how to do this part of configuration XA transactions, or that part, but no all-in-one place where we could really look at the full configuration and put the pieces together. One of the more complete articles that we used is this JavaWorld article, though it didn’t entirely serve our purposes.
What this post documents is the end configuration of what we needed, combining Spring,HibernateJBossJTA, and Apache connection pooling, in a Groovy application that uses both Microsoft SQL Server in non-development environment (QA, Production), and an h2database in development.
This post is going include just the code snippets that we really need, with explanations as to why they are needed, rather than providing a full Spring data context file. I’ll provide what I hope is enough that, reading this, you can take these snippets and put together a working XA configuration to suit your needs (and serve as a reference for me, as well).
To begin, we define the base datasource, from which all our other datasources derive. Most of these values could be configurable, but we don’t really have the need, so we’ve hard-coded appropriate values. The one part that is different from our non-XA implementation is that we hard-code the driverClassName as being the JBossJTA driver (com.arjuna.ats.jdbc.TransactionalDriver). This driver is used regardless of the type of database that we use. In the non-XA case, the driver would change depending upon the type of database being used (we use h2 during development, and sql server in our other environments). Underlying the JBossJTA driver is the choice of datasource, which will be database specific. More on that later.
The other thing to note here is that we’re using a datasource of org.apache.tomcat.jdbc.pool.XADataSource; this is a tomcat-provided Apache XA-capable data source, which enables the newer apache connection pool. In a non-XA case, just use the appropriate DataSource from the Apache Tomcat jdbc pool. Earlier, my team was using  org.apache.commons.dbcp.BasicDataSource, which is an older, slower connection pool from Apache. The JBossJTA driver is documented here.
If you think of the JTA driver as a layer over the non-XA datasource, that’s helpful (at least, it helped me to think of it that way).
 
 
 
 
 
 
 
 
 
 
We define specific data sources in the usual manner:
 
 
 
 
 
 
 
 
 
 
Note the url value; we must always use jdbc:arjuna to enable the XA transations. If the “arjuna” part is not present, non-XA transactions are used. It took a while to realize that one of our initial configurations had “arjuna” missing from the URL, and an inspection of the log indicated that XA transactions were NOT being used. If “arjuna” is present in the URL, then the jboss XA transactions are used, and the last part of the URL is used as a JNDI key, to obtain a datasource.
Which brings us to this: one key thing about the Jboss JTA driver is that it uses JNDI to look up data sources. This section sets up a JNDI context, and creates 2 entries within it; the entries are set by invoking the static getDataSource method on the DataSourceBuilder, passing the database URL (source for that later).
 
    
     
     
     
    
 
The JndiExporter is a neat little tidbit that was posted on stackoverflow; for reference, see this post at StackOverflow. This is the JndiExporter as we are using it:
public class JndiExporter implements InitializingBean {
  private final JndiTemplate template = new JndiTemplate();
  private Map jndiMapping = null;
  @Override
  public void afterPropertiesSet() throws Exception {
    for (def addToJndi : jndiMapping.entrySet()) {
      template.bind(addToJndi.getKey(), addToJndi.getValue());
    }
  }
  public void setJndiMapping(Map jndiMapping) {
    this.jndiMapping = jndiMapping;
  }
}
The DataSourceBuilder is the next piece of the puzzle. Recall that this is used to build the data sources which are inserted into the JNDI service. Here it is:
class DataSourceBuilder {
static public XADataSource getDataSource(String url) throws SQLException {
def source
if( url.startsWith("jdbc:h2:") ) {
source = new org.h2.jdbcx.JdbcDataSource()
source.setURL(url)
} else if( url.startsWith("jdbc:jtds:") ) {
source = new net.sourceforge.jtds.jdbcx.JtdsDataSource()
// Create URI for everything after jtds:
URI uri = URI.create( url.substring(10) ) // Strip everything up to just beyond the jtds:
source.setServerName(uri.getHost())
source.setPortNumber(uri.getPort())
def database = uri.getPath()
// removing leading slash on database
if( database.startsWith('/') ) {
database = database.substring(1)
}
source.setDatabaseName(database)
source.setServerType(net.sourceforge.jtds.jdbc.Driver.SQLSERVER)
}
return source
}
}
Basically, when a database url needs a datasource associated with it, this method is invoked, passing the URL. We inspect the URL and return an appropriate XA capable datasource. In the case of h2 (our development environment), this is just the usual org.h2.jdbcs.JdbcDataSource, as it is XA capable. For our other environments (sql server
is the only other one we use here), we use jtds and make sure we return an instance of the XA-capable JtdsDataSource.

What surprises me here is that the JBoss JTA requires the usage of Jndi for looking up data sources; you can’t directly specify the datasource in any other manner … you are required to use the jndi lookup.
The next thing is defining the session factories:
validate
${erp.scm.hibernate.dialect}
15
false
1000
true
true
1
false
false
com.company.scm.domain.model.erp
com.company.scm.domain.model.supplychain
There’s really nothing unusual about the session factories; we just define a session factory for each separate set of domain classes that live in their own database (in other words, we’re still defining a session factory per database).
And we have some boiler plate for the JBoss JTA transaction manager:
  class="com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple"/>
  class="com.arjuna.ats.internal.jta.transaction.arjunacore.UserTransactionImple"/>
 
   
 
 
   
 
And yeah, it’s really “UserTransactionImple” … with the trailing “e”.
As far as domain classes go, there’s really no difference in how they are constructed in the non-XA transaction case. As an example:
@Repository
public class HibernateRecipeDao extends HibernateSimpleDao implements RecipeDao {
@Autowired
public HibernateRecipeDao(@Qualifier('recipeSessionFactory') SessionFactory sessionFactory) {
domainClass = Recipe.class
this.sessionFactory = sessionFactory
}
}
The HibernateRecipeDao an autowired constructor taking the session factory (we use a base class, HibernateSimpleDao, that encapsulates a lot of the standard boiler plate); we have to use the @Qualifier just to make sure the correct session factory is wired in.
Once you have this in place for your database management, you’re good to go. With XA transactions enabled in your databases, you can access multiple databases from within a single transaction and everything will be just fine. If you want, you could use this XA transaction setup with non-XA transactions on the databases; the only catch there is that you will throw an exception if you try to access more than one database within a single transaction. We’ve actually run this way for a while, as our database administrators researched the XA transaction capabilities of SQL Server. Fortunately, we haven’t had to access multiple databases in a single transaction yet (there were views available that cross the database barrier for us; we just knew that down the road we’d be accessing multiple databases in single transactions), though we could see the need coming.
In the end, it’s not that much different from using non-XA data sources; a little more work, a few more dots to connect, but once they are connected, there’s not really much that is different, conceptually.



No comments: