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