SimpleJTA - Design and Implementation

At a high level there are the following components in the SimpleJTA implementation:

These are further described below.

Resource

One of the problems that SimpleJTA must solve is how to re-acquire connection to an XAResource after a system crash. The first requirement is to log the fact that a particular XAResource was used by a transaction. Secondly, some information about the XAResource object must be stored.

There are two possible options:

  1. First option is to store the JNDI key that was used to create the resource. SimpleJTA at present does not support resource registration with JNDI - this will be added in future.
  2. Second option is to store the connection details such as the URL, user and password. This is the approach adopted by SimpleJTA.

The primary reason for the Resource object is to encapsulate the XAResource, and add this extra bit of information that can be used to re-acquire the resource after a system crash.

Another use of the Resource object is to keep an association between the resource and a global transaction. My understanding is that a particular XAResource object (obtained from an XAConnection) can only be associated with one global transaction at any point in time. By association we mean that XAResource.start() has been invoked, and XAResource.end() has not yet been invoked. The Resource wrapper helps to maintain this association. It keeps track of the current global transaction. When XAResource.end is called, this association is removed.

ResourceStatus

The ResourceStatus object is used to maintain status about the Resource for a particular transaction branch. We cannot store branch specific status information in the Resource object itself because a Resource object can be used by multiple transactions. Once a Resource has ended its association with a particular transaction, it is available for use by other transactions. But the transaction branch must remember the Resource so that it can resume it or start it again if necessary. The ResourceStatus object helps to fulfil this requirement.

BranchTransaction

A BranchTransaction is closely associated with a Resource but the relationship is one-to-many. This is shown in the diagram below:

In essence, a BranchTransaction represents a transaction branch within a particular resource manager. The reason there can be multiple Resource objects is that these may be multiple connections to the same underlying Resource Manager, ie, Database. Note, that some DBMS vendors, in particular, Oracle, do not support this. Oracle insists that you create a separate branch for each XAConnection - even if two XAConnection objects point to the same database.

SimpleJTA knows whether two resources point to the same database by using the XAResource.isSameRM() method to compare two resources. To support special cases like Oracle, a flag is used to indicate whether a resource supports joining an existing branch. For Oracle this is always false.

The diagram above shows that the first resource is used to perform transaction completion steps (prepare, commit, rollback, forget). XA specification allows any resource connected to the Resource Manager, even one that has not participated in the transaction, to perform these steps. At present, SimpleJTA always uses the first resource in a Branch to perform transaction completion steps.

The first resource is the one that started the BranchTransaction. All the other resources subsequently joined this branch.

GlobalTransaction

The GlobalTransaction object maintains a list of BranchTransactions. When a new XAConnection object (or one from the pool) is asked to provide a Connection, it is enlisted to the GlobalTransaction. The GlobalTransaction object checks whether the XAResource is pointing to a Resource Manager already involved in a BranchTransaction. In that case, and if the resource supports joining, the resource is asked to join the existing branch. Otherwise a new BranchTransaction is created.

The GlobalTransaction object coordinates the actions for all the BranchTransactions. At commit(), it checks whether only one branch is involved. If so, it invokes One-Phase Commit for the branch. Otherwise, it invokes prepare() and Two-Phase Commit on all the branches.

The GlobalTransaction also updates the transaction log at various steps. The idea is to record the state transitions so that when the system is recovered after a crash, the in-doubt transactions can be recreated.

During recovery, however, GlobalTransaction does not log anything. This is achieved by setting a flag that tells the GlobalTransaction that it is in recovery mode.

During recovery, the GlobalTransaction object is asked to resolve an in-doubt transaction. It does this by looking at the last recorded status and deciding appropriately whether to commit or rollback.

Recovery

SimpleJTA attempts to deal with two types of system crashes. First case is when the application has crashed, but all the database servers are up.

The second possibility is that one or more of the database resources have crashed. In such a situation, SimpleJTA may have lost its transaction logs also, because these are stored in database tables.

The way SimpleJTA handles recovery is this:

First it reconstructs all the transactions that were recorded in its transaction logs.

As it retrieves information about the transactions, it also re-establishes connections to the database resource managers.

Then it invokes XAResource.recover() on each of the database resources to obtain lists of in-doubt transactions. This step is necessary to handle the situation where SimpleJTA cannot rely upon its own transaction logs.

SimpleJTA looks at each of the Xids that have been returned as in-doubt, and identifies the ones that belong to the SimpleJTA transaction manager instance. This is possible because SimpleXidImpl embeds the transaction manager's unique id in the global transaction identifier.

The next step is to compare the list of in-doubt xids with the list of in-doubt transactions that SimpleJTA has reconstructed from its own logs. There are three possibilities:

After comparing the transactions that have been reconstructed from the logs with the xids that were returned by the resource manager, SimpleJTA proceeds to resolve the in-doubt transactions. Global transactions that have the status STATUS_COMMITTING are committed, all other transactions are rolled back.

SimpleXidImpl

The SimpleXidImpl embeds the transaction manager instance id so that it is possible to identify xids that were generated by a particular instance. To ensure uniqueness, SimpleXidImpl uses the transation manager's birth time, its own birth time, a sequence number that is unique for the JVM classloader instance that owns the transaction manager instance, and, as already mentioned, the transaction manager id.

Transaction Logging

At present, SimpleJTA implements its transaction logs using database tables. Two tables are used, one to hold the global transaction details, and another to hold the branch details. The resource recovery data is stored with the branch. Ideally, resources should be stored in a separate table, the current design is not fully normalized.

There are three events that SimpleJTA logs. Firstly, it logs a transaction before attempting to prepare it. This is needed because otherwise SimpleJTA would not be able to recover the resources associated with the transaction. There is a potential to remove this by implementing a separate table for resources, and by registering resources globally, and not as part of particular transactions.

The second point is when the transaction is prepared, and a decision is made to commit the transaction. By recording this decision, SimpleJTA is able to recover from system crashes.

The third event is the decision to rollback a transaction. This is recorded to correctly handle the situation where a transaction was prepared successfully, but the commit failed, and a decision was taken to rollback.


SourceForge.net Logo