Connecting a JDBC DataSource to the JTA Transaction Service

Git branch: track5

Creating a transaction aware DataSource is complicated through a mismatch between the JTA specification and the actual XAResource implementation of most JDBC vendors. In the specification there is a clear decoupling between XAConnection and XAResource. Implicitly the spec assumes that the XAConnection can safely be returned to the pool while the XA transaction is still active. The final commit processing is executed by the XAResource:

title XA transaction with one resource (specification intent)

Application->UserTransaction: begin
UserTransaction->TransactionManager: begin
TransactionManager->*Transaction:new
Application->DataSource: getConnection
DataSource->TransactionManager: getTransaction
DataSource->Pool: borrow
Pool->XADataSource: getXAConnection
XADataSource->*XAConnection: new
DataSource->XAConnection: addConnectionEventListener
DataSource->XAConnection: getXAResource
XAConnection->*XAResource: new
DataSource->Transaction: enlistResource
Transaction->XAResource: start
DataSource->XAConnection: getConnection
XAConnection->*Connection: new
Application->Connection: prepareStatement
Connection->*PreparedStatement: new
Application->PreparedStatement: executeUpdate
Application->PreparedStatement: close
Application->Connection: close
Connection->XAConnection: connectionClosed
XAConnection->DataSource: connectionClosed
DataSource->Transaction: deListResource
Transaction->XAResource: end
DataSource->Pool: release
Application->UserTransaction: commit
UserTransaction->TransactionManager: commit
TransactionManager->Transaction: commit
Transaction->XAResource: commit

However most JDBC drivers do not support this schema and will probably rollback on connection.close(). Therefore a transactional dataSource will keep the connection open and only close it after the transaction has been completed. Consequently the life span of the transaction and the connection lease are interleaved:

title XA transaction with one resource (implementation)

Application->UserTransaction: begin
UserTransaction->TransactionManager: begin
TransactionManager->*Transaction:new
Application->DataSource: getConnection
DataSource->TransactionManager: getTransaction
DataSource->Pool: borrow
Pool->XADataSource: getXAConnection
XADataSource->*XAConnection: new
DataSource->XAConnection: addConnectionEventListener
DataSource->XAConnection: getXAResource
XAConnection->*XAResource: new
DataSource->Transaction: enlistResource
Transaction->XAResource: start
DataSource->XAConnection: getConnection
XAConnection->*Connection: new
DataSource->*ConnectionCloser: new
DataSource->Transaction: registerSynchronization
DataSource->*ConnectionWrapper: wrap
Application->ConnectionWrapper: prepareStatement
ConnectionWrapper->Connection: prepareStatement
Connection->*PreparedStatement: new
Application->PreparedStatement: executeUpdate
Application->PreparedStatement: close
Application->ConnectionWrapper: close
Application->UserTransaction: commit
UserTransaction->TransactionManager: commit
TransactionManager->Transaction: commit
Transaction->XAResource: end
Transaction->XAResource: commit
Transaction->ConnectionCloser: afterCompletion
ConnectionCloser->Connection: close
Connection->XADataSource: connectionClosed
XADataSource->DataSource: connectionClosed
DataSource->Pool: release

Things are further complicated if the applications does multiple getConnection() to the same DataSource in one transaction. To avoid starting a transaction branch for each getConnection, an implementation can reuse the connection handle by associating it with the transaction. Often this is implemented with a ThreadLocal in the DataSource, but if one need to support suspending the transaction in one thread and resuming it in an other, one can use the TransactionSynchronizationRegistry service:

title XA transaction with two actions - one resource (implementation)

Application->UserTransaction: begin
UserTransaction->TransactionManager: begin
TransactionManager->*Transaction:new
Application->DataSource: getConnection
DataSource->TransactionManager: getTransaction
DataSource->Pool: borrow
Pool->XADataSource: getXAConnection
XADataSource->*XAConnection: new
DataSource->XAConnection: addConnectionEventListener
DataSource->XAConnection: getXAResource
XAConnection->*XAResource: new
DataSource->Transaction: enlistResource
Transaction->XAResource: start
DataSource->XAConnection: getConnection
XAConnection->*Connection: new
DataSource->TransactionSynchronizationRegistry: putResource
DataSource->*ConnectionCloser: new
DataSource->Transaction: registerSynchronization
DataSource->*ConnectionWrapper: wrap
Application->ConnectionWrapper: prepareStatement
ConnectionWrapper->Connection: prepareStatement
Connection->*PreparedStatement: new
Application->PreparedStatement: executeUpdate
Application->PreparedStatement: close
Application->ConnectionWrapper: close
Application->DataSource: getConnection
DataSource->TransactionSynchronizationRegistry: getResource
DataSource->*ConnectionWrapper2: wrap
Application->ConnectionWrapper2: prepareStatement
ConnectionWrapper2->Connection: prepareStatement
Connection->*PreparedStatement2: new
Application->PreparedStatement2: executeUpdate
Application->PreparedStatement2: close
Application->ConnectionWrapper2: close
Application->UserTransaction: commit
UserTransaction->TransactionManager: commit
TransactionManager->Transaction: commit
Transaction->XAResource: end
Transaction->XAResource: commit
Transaction->ConnectionCloser: afterCompletion
ConnectionCloser->Connection: close
Connection->XADataSource: connectionClosed
XADataSource->DataSource: connectionClosed
DataSource->Pool: release