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 MapjndiMapping = null; @Overridepublic void afterPropertiesSet() throws Exception {for (def addToJndi : jndiMapping.entrySet()) {template.bind(addToJndi.getKey(), addToJndi.getValue());}}public void setJndiMapping(MapjndiMapping) { 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 sourceif( 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 databaseif( 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
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:
@Repositorypublic class HibernateRecipeDao extends HibernateSimpleDaoimplements RecipeDao { @Autowiredpublic HibernateRecipeDao(@Qualifier('recipeSessionFactory') SessionFactory sessionFactory) {domainClass = Recipe.classthis.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:
Post a Comment