(Quick Reference)

5 Tutorial - Reference Documentation

Authors: Burt Beckwith

Version: 1.0

Table of Contents

5 Tutorial

First create a test application:

$ grails create-app atomikostest
$ cd atomikostest

and install the atomikos plugin:

$ grails install-plugin atomikos

5.1 JDBC

Convert the development H2 datasource to XA by adding an xaConfig attribute:

environments {
   development {
      dataSource {
         dbCreate = 'update'
         url = 'jdbc:h2:db/devDb;MVCC=TRUE'
         xaConfig = [
            driverClassName: 'org.h2.jdbcx.JdbcDataSource',
            driverProperties: [
               URL: 'jdbc:h2:db/devDb;MVCC=TRUE',
               user: 'sa',
               password: ''],
            minPoolSize: 1,
            maxPoolSize: 50
         ]
      }
   }
   …
}

Note that the datasource has also been changed to file-based so it persists between runs. This isn't required but makes it easier to look at the database when the application isn't running.

Create a second datasource for a MySQL database:

dataSource_mysql {
   dbCreate = 'update'
   url = 'jdbc:mysql://localhost/xatest'
   driverClassName = 'com.mysql.jdbc.Driver'
   username = 'xatest'
   password = 'xatest'
   xaConfig = [
      driverClassName: 'com.mysql.jdbc.jdbc2.optional.MysqlXADataSource',
      driverProperties: [
         URL: 'jdbc:mysql://localhost/xatest',
         user: 'xatest',
         password: 'xatest',
         autoReconnect: true,
         autoReconnectForConnectionPools: true,
         autoReconnectForPools: true],
      minPoolSize: 1,
      maxPoolSize: 50
   ]
}

The name "dataSource_mysql" is just a suggestion; use whatever you want after "dataSource_". This datasource definition assumes that you have a MySQL database named "xatest" with a user "xatest" that has been granted access using the password "xatest". Change those values as needed.

Domain classes

Create a domain class that will use the default H2 database:

$ grails create-domain-class H2Thing

and change the code so it looks like this:

package atomikostest

class H2Thing { String name }

Create a domain class that will use the MySQL database:

$ grails create-domain-class MySQLThing

and change the code so it looks like this:

package atomikostest

class MySQLThing { String name

static mapping = { datasource 'mysql' } }

Dependencies

To use MySQL you'll need the JDBC driver, so add the mavenLocal() and mavenCentral() repositories and the mysql-connector dependency to BuildConfig.groovy:

repositories {
   inherits true
   grailsPlugins()
   grailsHome()
   grailsCentral()

mavenLocal() mavenCentral() }

dependencies { runtime 'mysql:mysql-connector-java:5.1.16' }

schema-export

You can verify that the domain classes are in separate datasources by running the schema-export script for each. Run the script for the default datasource:

$ grails schema-export

and you should only see a "create table" statement for the "h2thing" table in target/ddl.sql. Run it again for the other datasource:

$ grails schema-export --datasource=mysql

and you should only see a "create table" statement for the "mysqlthing" table in target/ddl.sql.

5.2 JDBC Testing

To test two-phase commit with the two databases, create a transactional service:

$ grails create-service XaTest

package atomikostest

class XaTestService {

void failAfterH2() { assert new MySQLThing(name: 'test MySQL').save(flush: true) assert new H2Thing(name: 'test H2').save(flush: true) throw new RuntimeException('forcing an auto rollback in failAfterH2') }

void failAfterMySQL() { assert new H2Thing(name: 'test H2').save(flush: true) assert new MySQLThing(name: 'test MySQL').save(flush: true) throw new RuntimeException('forcing an auto rollback in failAfterH2') }

void succeedBoth() { assert new H2Thing(name: 'test H2').save(flush: true) assert new MySQLThing(name: 'test MySQL').save(flush: true) } }

To verify that an unchecked runtime exception will automatically roll back even flushed instances, there are two methods that create and flush valid instances and then throw an exception, failAfterH2() and failAfterMySQL(). succeedBoth() doesn't throw an exception, so it should successfully create both. You could create a controller to test these, but it's simpler to use the Grails console:

$ grails console

First try failAfterH2():

import atomikostest.H2Thing
import atomikostest.MySQLThing

int h2ThingCount = H2Thing.count() int mySQLCount = MySQLThing.count()

def xaTestService = ctx.xaTestService

try { xaTestService.failAfterH2() println "uh oh, no exception?" } catch (e) { println "caught exception: $e.message" }

assert h2ThingCount == H2Thing.count() assert mySQLCount == MySQLThing.count()

and then the same for failAfterMySQL():

import atomikostest.H2Thing
import atomikostest.MySQLThing

int h2ThingCount = H2Thing.count() int mySQLCount = MySQLThing.count()

def xaTestService = ctx.xaTestService

try { xaTestService.failAfterMySQL() println "uh oh, no exception?" } catch (e) { println "caught exception: $e.message" }

assert h2ThingCount == H2Thing.count() assert mySQLCount == MySQLThing.count()

Finally, execute the success case to see that the instances do get created:

import atomikostest.H2Thing
import atomikostest.MySQLThing

int h2ThingCount = H2Thing.count() int mySQLCount = MySQLThing.count()

def xaTestService = ctx.xaTestService

xaTestService.succeedBoth()

assert h2ThingCount + 1 == H2Thing.count() assert mySQLCount + 1 == MySQLThing.count()

5.3 JMS

Now let's add JMS to the mix. Install the jms plugin:

$ grails install-plugin jms

and add ActiveMQ jars as dependencies in grails-app/conf/BuildConfig.groovy since the JMS plugin doesn't provide a JMS implementation:

dependencies {
   runtime 'mysql:mysql-connector-java:5.1.16'

compile('org.apache.activemq:activemq-core:5.5.0') { transitive = false } runtime('org.apache.activemq:kahadb:5.5.0') { transitive = false } }

Register a connection factory and a queue for testing in grails-app/conf/spring/resources.groovy:

import org.apache.activemq.ActiveMQXAConnectionFactory
import org.apache.activemq.command.ActiveMQQueue

beans = {

jmsConnectionFactory(ActiveMQXAConnectionFactory) { brokerURL = 'vm://localhost' }

jtaQueue(ActiveMQQueue) { physicalName = 'jtaQueue' } }

MessageTestService

Create a service that will act as our listener:

$ grails create-service MessageTest

package atomikostest

class MessageTestService {

static exposes = ['jms'] static destination = 'jtaQueue'

// NOT THREAD SAFE - just for testing def mostRecentMessage

void onMessage(Map message) { mostRecentMessage = message println "\n new message: $message\n" }

void onMessage(String message) { mostRecentMessage = message println "\n new message: $message\n" } }

Add some additional test methods to XaTestService:

void failAfterJms() {

def thing = new MySQLThing(name: 'test JDBC').save(flush: true) assert thing

def message = [thingId: thing.id, thingName: thing.name] jmsService.send 'jtaQueue', message

throw new RuntimeException('forcing an auto rollback in failAfterJms') }

void failAfterJdbc() { jmsService.send 'jtaQueue', "you won't get this"

assert new MySQLThing(name: 'test JDBC').save(flush: true)

throw new RuntimeException('forcing an auto rollback in failAfterJdbc') }

void succeedJmsAndJdbc() { def thing = new MySQLThing(name: 'test JDBC').save(flush: true) assert thing

def message = [thingId: thing.id, thingName: thing.name] jmsService.send 'jtaQueue', message }

Note that we only test MySQL here to demonstrate that JDBC and JMS work together, but H2 would work fine also.

Testing in the console

First try failAfterJms():

import atomikostest.MySQLThing

int mySQLCount = MySQLThing.count()

def xaTestService = ctx.xaTestService def messageTestService = ctx.messageTestService messageTestService.mostRecentMessage = null

try { xaTestService.failAfterJms() println "uh oh, no exception?" } catch (e) { println "caught exception: $e.message" }

assert mySQLCount == MySQLThing.count() sleep 200 assert messageTestService.mostRecentMessage == null

and then the same for failAfterJdbc():

import atomikostest.MySQLThing

int mySQLCount = MySQLThing.count()

def xaTestService = ctx.xaTestService def messageTestService = ctx.messageTestService messageTestService.mostRecentMessage = null

try { xaTestService.failAfterJdbc() println "uh oh, no exception?" } catch (e) { println "caught exception: $e.message" }

assert mySQLCount == MySQLThing.count() sleep 200 assert messageTestService.mostRecentMessage == null

Finally, execute the success case to see that the domain class instance does get created and the JMS message gets sent:

import atomikostest.MySQLThing

int mySQLCount = MySQLThing.count()

def xaTestService = ctx.xaTestService def messageTestService = ctx.messageTestService messageTestService.mostRecentMessage = null

xaTestService.succeedJmsAndJdbc()

assert mySQLCount + 1 == MySQLThing.count() sleep 200 assert messageTestService.mostRecentMessage instanceof Map

Note that the mostRecentMessage assertions can occasionally fail since JMS messages are sent and received asynchronously. The sleep() call should be sufficient but if things are running slowly you can get a false failure.