JBoss.orgCommunity Documentation

Chapter 10. Distributed Transactions (XA)

10.1. Introduction

Immutant encapsulates the distributed transaction support provided by JBoss AS7 within the immutant.xa namespace.

A distributed transaction is one in which multiple types of resources may participate. The most common example of a transactional resource is a relational database, but in Immutant both caching (backed by Infinispan) and messaging (backed by HornetQ) are also automatically transactional. Technically speaking, they provide implementations of the XA protocol, and any back-end data store that does so may participate in an Immutant transaction.

This allows your application to say, tie the success of a SQL database update or the storage of an entry on a replicated data grid to the delivery of a message to a remote queue, i.e. the message is only sent if the database and data grid update successfully. If any single component of an XA transaction fails, all of them rollback.

The immutant.xa namespace contains only two functions:

datasourceCreates a valid XA DataSource for the most common SQL databases
transactionA macro that wraps its body in a transaction unless one is already active

More fine-grained transactional control is available through the immutant.xa.transaction namespace, described below.

10.2. Creating XA DataSources

In order for your database to participate in an XA transaction, an XA DataSource must be created for it. In Immutant, you have two options: either 1) call immutant.xa/datasource from your application, or 2) use the JBoss AS7 configuration facilities. The former is simple and recommended for most deployments, but the latter can be easier to manage when multiple applications share the same DataSource in a clustered environment.

10.2.1. For use with clojure.java.jdbc

The spec you pass to clojure.java.jdbc/with-connection depends on how you create the DataSource. If you call immmutant.xa/datasource, then associate the :datasource key to its result.

Otherwise, set the :name key to the JNDI name you set for the xa-datasource in the JBoss AS7 configuration.

And any library based on clojure.java.jdbc should work just fine with that spec. Using immutant.xa/datasource

To create your own DataSource, you must make the appropriate JDBC driver available. This is easily done with a Leiningen project in project.clj. The following drivers have been tested successfully:

[com.h2database/h2 "1.3.160"]              ; H2
[org.clojars.gukjoon/ojdbc "1.4"]          ; Oracle
[mysql/mysql-connector-java "5.1.22"]      ; MySQL
[postgresql "9.0-801.jdbc4"]               ; Postgres
[net.sourceforge.jtds/jtds "1.2.4"]        ; MS SQL Server

Here's an example creating a DataSource for an in-memory H2 database:

(defonce ds (immutant.xa/datasource "foo" {:adapter "h2" :database "mem:foo"}))
(jdbc/with-connection {:datasource ds}
  (jdbc/create-table :things [:name "varchar(50)"]))

Here's an example creating an Oracle DataSource for an Amazon RDS instance:

;;; rds-create-db-instance myinstance -s 10 -c db.m1.small -e oracle-se -u myuser -p mypassword --db-name mydb
(defonce ds (ixa/datasource "foo" {:adapter "oracle"
                                   :host "myinstance.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com"
                                   :username "myuser"
                                   :password "mypassword"
                                   :database "mydb"}))
(jdbc/with-connection {:datasource ds} ...) Using an AS7-configured DataSource

Once you've configured your XA DataSource in AS7, you simply refer to its JNDI name:

(jdbc/with-connection {:name "java:jboss/datasources/ExampleXADS"}
  (jdbc/create-table :things [:name "varchar(50)"]))

10.3. Defining Transactions

A transaction is easily defined using the immutant.xa/transaction macro:

(ns xa.example
  (:require [immutant.xa :as xa]
            [immutant.cache :as cache]
            [immutant.messaging :as msg]
            [clojure.java.jdbc :as sql]))

 (sql/with-connection {:datasource my-ds}
   (sql/insert-records :things {:name "foo"}))
 (cache/put my-cache :a 1)
 (msg/publish "/queue/test" "success!"))

In the example above, we insert a record into a SQL database, write an entry to a cache and publish a message to a queue, all within the same XA transaction. If the body of xa/transaction runs without tossing an exception, the transaction will be committed. Otherwise it will be rolled back. All or nothing. Simple.

10.3.1. Transaction Scope

When transactional components interact, the state of a transaction when a particular function is invoked isn't always easy to predict. For example, can a function that requires a transaction assume one has been started prior to its invocation? In JEE container-managed persistence, a developer answers these questions using the @TransactionAttribute annotation.

But annotations are gross, right? :)

So instead, the JEE transaction attributes are represented as Clojure macros. In fact, the xa/transaction macro shown above is merely an alias for immutant.xa.transaction/required, which is the implicit attribute used in JEE. There are a total of 6 macros:

requiredExecute within current transaction, if any, otherwise start a new one, execute, commit or rollback.
requires-newSuspend current transaction, if any, start a new one, execute, commit or rollback, and resume the suspended one.
not-supportedSuspend current transaction, if any, and execute without a transaction.
supportsExecute the body whether there's a transaction or not; may lead to unpredictable results
mandatoryToss an exception if there's no active transaction
neverToss an exception if there is an active transaction

These macros give the developer complete declarative control over the transactional semantics of their application as its functional chunks are combined.

Immutant 1.1.4