Happy Easter: 25% off for 3 months on all monthly plans. Woohoo! Get your discount code here (and spread the word, while you are at it!).

How to get started with XA/distributed transactions on the JVM

May 02, 2014 - Marco Behler

(Note: You can find fully working code examples for this article on our Github account)

Your task is to find out how to commit to two datasources at the same time. The ususal example: You are operating a fairly huge web shop, with a couple of dependent backend systems. At the same time as a new incoming order gets saved to the database , you also want to send a message to other systems (read: JMS queue), like inventory management.

But you are not entirely sure how to get started? Or have doubts about integration complexity and performance? We will get you up and running with a minimal, but fully working quickstart project in no time, that should answer most questions for you.

What tools do I need?

We are going to be very minimalistic. No J2EE application container, instead we’re going to use some Spring. To get XA transactions to work with Spring, we also need a JTA Transaction Manager. Now, there are a couple of alternatives, like JBossTS or Atomikos, but we are big fans of Brett Woolridge’s and friends’ work, so we are going to go with Bitronix this time. And as the database does not really matter for now, we’re going with the fantastic H2. Oh, and a bit of Maven does not hurt if you want to get the code example up and running.</p>

Enough talk, let's see some business!

Ok, what are going to do is this: We want to have method which registers users, but with a catch. The user should get written to one database, but for some legacy reasons, we must save some details of the user to another database. (So in this example we do not have database+jms queue, but database+database, but it really does not matter.)

Bitronix comes with a PoolingDataSource which we have to use for XA transactions. Let's do it!

@Bean(destroyMethod = "close", name = "dataSourceOne")
    public DataSource dataSourceOne() {
        PoolingDataSource ds = new PoolingDataSource();
        // a h2 in-memory database...make sure to use the XADatasources for other databases
        ds.setClassName("org.h2.jdbcx.JdbcDataSource");
        ds.setUniqueName("ds1");
        ds.setMaxPoolSize(10);
        Properties props = new Properties();
        props.put("url", "jdbc:h2:mem:ds1");
        props.put("user", "sa");
        props.put("password", "");
        ds.setDriverProperties(props);
        ds.init();
        return ds;
    }
    @Bean(destroyMethod = "close", name = "dataSourceTwo")
    public DataSource dataSourceTwo() {
      //...same as above, only different h2 connection string...
    }

The PoolingDataSource is configured to have an underlying H2 datasource, which in this example opens up an in-memory database. Easy, huh?

Now all that is left do is: Enable declarative @Transactional management and for that we still need a transaction manager setup:

@Bean(destroyMethod = "shutdown")
    public BitronixTransactionManager bitronixManager() {
        return TransactionManagerServices.getTransactionManager();
    }
    @Bean
    public JtaTransactionManager jtaTransactionManager() {
        JtaTransactionManager jta = new JtaTransactionManager();
        jta.setTransactionManager(bitronixManager());
        jta.setUserTransaction(bitronixManager());
        return jta;
    }

  Bitronix is of course configurable, but we will go with the defaults here. All we need is a BitronixTransactionManager, which then is wrapped in Spring's JtaTransactionManager. Oh, and don't forget to annotate your ApplicationContext class with @EnableTransactionManagement.

Now, let's bring it all together!

public class UserRegistryXA {
  //...
    @Autowired
    @Qualifier(value = "dataSourceOne")
    private DataSource ds1;
    @Autowired
    @Qualifier(value = "dataSourceTwo")
    private DataSource ds2;
    @Transactional

    public void registerUser(final String name, final Integer limit) {
         // let's commit the user in one database....and yes, that create table statement should not be here, but is for simplicity ; )
        new JdbcTemplate(ds1).execute("create table if not exists user (id bigint auto_increment primary key, name varchar)");
        new SimpleJdbcInsert(ds1).withTableName("user").execute(new HashMap<String, Object>() {
            put("name", name);
        });
        // let's assume we want to save this user's atm withdrawal limits to another database...
        // (and yes, there is no relationship or foreign key in this example ; )
        new JdbcTemplate(ds2).execute("create table if not exists atm_widthdrawal_limit (id bigint auto_increment primary key, amount int )");
        new SimpleJdbcInsert(ds2).withTableName("atm_widthdrawal_limit").execute(new HashMap<String, Object>() {
            put("amount", limit);
        });
       // ....
    }


//...
}

And voila - you are completely setup with distributed transactions. Now when you register a user, and for whatever reason saving the user works, but the limit saving fails, then the whole transaction rolls back, including your user! Feel free to change the underlying database(s) and experiment by adding a JMS queue.

Yes, let me play around with it myself!

### get the code
git clone https://github.com/marcobehler/marcobehler-blog.git
cd marcobehler-blog/marcobehler-blog-xa/
### to run a test which executes an XA transaction
mvn clean test
### to actually see how it all it setup
less src/main/java/marcobehler/blog/jta/Application.java
less src/main/java/marcobehler/blog/jta/UserRegistryXA.java

Play with the code example yourself! Enjoy!

Want to get notified of new articles & screencasts?

Get your weekly articles & screencast list, straight to your inbox every Wednesday. We promise never to spam.

comments