JTA Deep Adventure - Principle and Implementation

TA Deep Adventure - Principle and Implementation
In J2EE applications, transaction is an indispensable component model, which guarantees the ACID (that is, atomic, consistent, isolated, durable) properties of user operations. For applications that only operate on a single data source, transaction management can be implemented through the local resource interface; for large-scale applications that span data sources (such as multiple databases, or databases and JMS), the global transaction JTA (Java Transaction API) must be used. JTA provides distributed transaction services for the J2EE platform, which isolates transactions from underlying resources and implements a transparent transaction management method. This article will deeply explore the architecture of JTA, and introduce its implementation mechanism through detailed code.



Transaction processing using JTA
What is transaction processing
Transaction is an indispensable component model in computer applications, which ensures the atomicity, consistency (Consistency), isolation (Isolation) and durability (Durabilily) of user operations. The most classic example of a transaction is credit card transfer: transfer 500 RMB in user A's account to user B's account, the operation process is as follows
1. Reduce the amount in A's account by 500
2. Transfer the amount in B's account The two operations of adding 500
must ensure the transaction properties of ACID: either all succeed or all fail; if there is no transaction guarantee, the user's account amount may have problems:
if the first operation succeeds and the second fails, then the user The amount in A's account will be reduced by 500 yuan without any increase in user B's account (missing); also if the first step is wrong and the second step is successful, then user A's account amount remains unchanged and user B's The account will be increased by 500 yuan (created out of thin air). Any of the above errors can create serious data inconsistencies, and the absence of transactions is unacceptable for a stable production system.
J2EE transaction processing
1. Local transactions: closely dependent on the underlying resource manager (such as database connections), transaction processing is limited to the current transaction resources. This transaction processing method does not depend on the application server, so the deployment is flexible but cannot support distributed transactions with multiple data sources. An example of using a local transaction in a database connection is as follows:
Listing 1. Local transaction processing instance
public void transferAccount() {
Connection conn = null;
Statement stmt = null;
try{
conn = getDataSource().getConnection();
// will automatically commit Set to false,
//If set to true, the database will consider each data update as a transaction and commit it automatically
conn.setAutoCommit(false);

stmt = conn.createStatement();
// Reduce the amount in the A account 500
stmt.execute("\
             update t_account set amount = amount - 500 where account_id = 'A'");
// Increase the amount in B account by 500
stmt.execute("\
             update t_account set amount = amount + 500 where account_id = 'B'");

// Commit the transaction
     conn.commit();
// Transaction commit: the two-step operation of the transfer is successful at the same time
} catch(SQLException sqle){
try{
// An exception occurs, rollback the operation in this transaction
                conn.rollback() ;
// Transaction rollback: the two-step transfer operation is completely undone
                 stmt.close();
                 conn.close();
}catch(Exception ignore){

}
sqle.printStackTrace();
}
}
2. Distributed transaction processing: Java Transaction Programming Interface (JTA: Java Transaction API) and Java Transaction Service (JTS; Java Transaction Service) provide distributed transaction services for the J2EE platform. Distributed transaction (Distributed Transaction) includes transaction manager (Transaction Manager) and one or more resource managers (Resource Manager) that support the XA protocol. We can think of the resource manager as any type of persistent data store; the transaction manager is responsible for the coordination and control of all transaction participating units. JTA transactions effectively shield the underlying transaction resources, so that applications can participate in transaction processing in a transparent way; however, compared with local transactions, the system overhead of the XA protocol is large, and it should be carefully considered in the system development process whether it is indeed necessary to distribute affairs. If you really need distributed transactions to coordinate multiple transaction resources, you should implement and configure transaction resources that support the XA protocol, such as JMS, JDBC database connection pools, and so on. An example of transaction processing using JTA is as follows (note: connA and connB are connections from different databases)
Listing 2. JTA transaction processing
public void transferAccount() {

UserTransaction userTx = null;
Connection connA = null;
Statement stmtA = null;

Connection connB = null;
Statement stmtB = null;
   
try{
       // Get the Transaction management object
userTx = (UserTransaction)getContext().lookup("\
       java:comp/UserTransaction");
// Get database connection from database A
connA = getDataSourceA().getConnection();

// Get database connection from database B
connB = getDataSourceB().getConnection();
     
                        // start transaction
userTx.begin();

// decrease the amount in account A by 500
stmtA = connA.createStatement();
stmtA.execute("
            update t_account set amount = amount - 500 where account_id = 'A'");

// Increase the amount in account B by 500
stmtB = connB.createStatement();
stmtB.execute("\
             update t_account set amount = amount + 500 where account_id = 'B'");

// Commit transaction
userTx. commit();
// Transaction submission: The two-step operation of the transfer is successful at the same time (data in database A and database B are updated at the same time)
} catch(SQLException sqle){

try{
         // An exception occurs, rollback the operation in this transaction
                  userTx.rollback (                  ) .
_
_ _ _ Exception ignore){ } sqle.printStackTrace(); } catch(Exception ne){ e.printStackTrace(); } } back to top JTA implementation principle















Many developers will be interested in the inner workings of JTA: I write code without any code that interacts with transactional resources (such as database connections), but my operations (database updates) are actually wrapped in transactions , then how does JTA achieve this transparency? To understand the implementation principle of JTA, we first need to understand its architecture: it includes a transaction manager (Transaction Manager) and one or more resource managers (Resource Manager) that support the XA protocol. We can regard the resource manager as any type of The persistent data storage of the transaction manager is responsible for the coordination and control of all transaction participating units. Depending on the object-oriented, we can understand JTA's transaction manager and resource manager as two aspects: the developer-oriented usage interface (transaction manager) and the service provider-oriented implementation interface (resource manager). The main part of the development interface is the UserTransaction object referenced in the above example, developers implement distributed transactions in the information system through this interface; and the implementation interface is used to standardize the transactions provided by providers (such as database connection providers). Service, which stipulates the resource management function of the transaction, so that JTA can perform collaborative communication between heterogeneous transaction resources. Taking databases as an example, IBM provides database drivers for implementing distributed transactions, and Oracle also provides database drivers for implementing distributed transactions. When using DB2 and Oracle database connections at the same time, JTA can connect according to the agreed interface. Coordinator two transaction resources to achieve distributed transactions. It is the different implementations based on the unified specification that enable JTA to coordinate and control the transaction resources of different databases or JMS vendors. Its architecture is shown in the following figure:
Figure 1. JTA Architecture
JTA Architecture Diagram

Developers use the developer interface to implement the application's support for global transactions; each provider (database, JMS, etc.) provides transaction resource management functions according to the specifications of the provider interface; the transaction manager ( TransactionManager ) will apply the distributed transaction Use maps to actual transaction resources and coordinate and control between transaction resources. Below, this article will introduce the three main interfaces including UserTransaction, Transaction and TransactionManager and the methods they define.
The interface for developers is UserTransaction (the usage method is shown in the above example), and developers usually only use this interface to implement JTA transaction management, which defines the following methods:
begin()- start a distributed transaction, (in the background TransactionManager will Create a Transaction transaction object and associate this object with the current thread through ThreadLocale)
commit()- commit the transaction (in the background TransactionManager will take out the transaction object from the current thread and commit the transaction represented by this object)
rollback()- return Roll transaction (in the background TransactionManager will take out the transaction object from the current thread and roll back the transaction represented by this object)
getStatus() - Returns the status of the distributed transaction associated with the current thread (the Status object defines all transaction statuses , interested readers can refer to the API documentation)
setRollbackOnly()- identifies that the distributed transaction associated with the current thread will be rolled back The provider
-oriented implementation interface mainly involves two objects, TransactionManager and Transaction
Transaction represents a transaction in the physical sense. When the developer calls the UserTransaction.begin() method, the TransactionManager will create a Transaction transaction object (marking the start of the transaction) and associate this object with the current thread through ThreadLocale. The commit(), rollback(), getStatus() and other methods in the UserTransaction interface will be delegated to the corresponding methods of the Transaction class for execution. The Transaction interface defines the following methods:
commit()- Coordinates different transaction resources to complete the transaction commit
rollback()- Coordinates different transaction resources to complete the transaction rollback
setRollbackOnly()- Identifies the distributed transaction associated with the current thread will be rolled back
getStatus() - returns the status of the distributed transaction associated with the current thread
enListResource(XAResource xaRes, int flag) - adds the transaction resource to the current transaction (in the above example, when operating on database A it is The transaction resource represented will be associated with the current transaction, and similarly, the transaction resource represented by it will also be associated with the current transaction when operating on database B)
delistResource(XAResource xaRes, int flag) - removes the transaction resource from the current transaction. delete in transaction
registerSynchronization(Synchronization sync)- callback interface, ORM tools such as Hibernate have their own transaction control mechanism to ensure the transaction, but they also need a callback mechanism to be notified when the transaction is completed to trigger some processing work, such as clearing the cache, etc. . This involves the callback interface registerSynchronization of Transaction. The tool can inject the callback program into the transaction through this interface. When the transaction is successfully submitted, the callback program will be activated.
TransactionManager itself does not undertake the actual transaction processing function, it acts more as a bridge between the user interface and the implementation interface. The methods defined in TransactionManager are listed below, and you can see that most of the transaction methods in this interface are the same as UserTransaction and Transaction. When the developer calls the UserTransaction.begin() method, the TransactionManager will create a Transaction transaction object (marking the start of the transaction) and associate this object with the current thread through ThreadLocale; similarly UserTransaction.commit() will call TransactionManager.commit() , the method will take out the transaction object Transaction from the current thread and commit the transaction represented by this object, that is, call Transaction.commit()
begin()- start transaction
commit()- commit transaction
rollback()- rollback transaction
getStatus() - Returns the current transaction state
setRollbackOnly()
getTransaction() - Returns the transaction associated with the current thread
setTransactionTimeout(int seconds)- set the transaction timeout time
resume(Transaction tobj)- continue the transaction associated with the current thread
suspend()- suspend the transaction associated with the current thread
In process of system development, there will be operations that need to temporarily exclude transaction resources. At this point, you need to call the suspend() method to suspend the current transaction: any operations done after this method will not be included in the transaction, and resume() is called after the non-transactional operation is completed to continue the transaction (Note : To perform this operation, you need to obtain the TransactionManager object, which is different in different J2EE application servers.)
The following will introduce the JTA implementation principle to readers through specific codes. The following figure lists the Java classes involved in the sample implementation, where UserTransactionImpl implements the UserTransaction interface, TransactionManagerImpl implements the TransactionManager interface, and TransactionImpl implements the Transaction interface
Figure 2. JTA implementation class diagram

Transaction class diagram
Listing 3. Start transaction - UserTransactionImpl implements UserTransaction
public void begin() throws NotSupportedException, SystemException {
   // Delegate the operation of starting a transaction to TransactionManagerImpl
   TransactionManagerImpl.singleton().begin();
     }
Listing 4. Start transaction - TransactionManagerImpl implements TransactionManager
// Here transactionHolder is used to associate the transaction object represented by Transaction to the thread
private static ThreadLocal<TransactionImpl> transactionHolder
        = new ThreadLocal<TransactionImpl>();

//TransacationMananger must maintain a Global object, so use single instance mode to implement
private static TransactionManagerImpl singleton = new TransactionManagerImpl();

private TransactionManagerImpl(){

}

public static TransactionManagerImpl singleton(){
return singleton;
}

public void begin() throws NotSupportedException, SystemException {
//XidImpl implementation The Xid interface is used to uniquely identify a transaction
XidImpl xid = new XidImpl();
// Create a transaction object and associate the object with the thread
TransactionImpl tx = new TransactionImpl(xid);

transactionHolder.set(tx);
}
Now we can understand the reason why the begin method is not defined on the Transaction interface: the Transaction object itself represents a transaction, which is indicated when it is created The transaction has already started, so there is no need to define an additional begin() method.
Listing 5. Commit transaction - UserTransactionImpl implenments UserTransaction
     public void commit() throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, SecurityException,
IllegalStateException, SystemException {

// Check if it is a Roll back only transaction, if it is a rollback transaction
        if(rollBackOnly){
     rollback() ;

     return;
       } else {
    // Delegate transaction commit to TransactionManagerImpl
    TransactionManagerImpl.singleton().commit();
       }
}
Listing 6. Commit transaction - TransactionManagerImpl implenments TransactionManager
public void commit() throws RollbackException, HeuristicMixedException,
    HeuristicRollbackException, SecurityException,
    IllegalStateException, SystemException {

     // Get the transaction associated with the current transaction and submit it via its commit method
     TransactionImpl tx = transactionHolder.get( );
     tx.commit();
         }
Similarly, methods such as rollback, getStatus, and setRollbackOnly are implemented in the same way as commit(). The UserTransaction object does not have any control over the transaction, and all transaction methods are passed to the actual transaction resource, namely the Transaction object, through the TransactionManager.
The above example demonstrates the processing of a JTA transaction, the following will show you how transaction resources (database connections, JMS) are transparently added to a JTA transaction. The first thing to be clear is that the database source ( DataSource ) obtained in the JTA transaction code must support distributed transactions. In the following code example, although all database operations are included in the JTA transaction, because MySql's database connection is obtained locally, any updates to MySql will not be automatically included in the global transaction.
Listing 7. JTA transaction processing
public void transferAccount() {

UserTransaction userTx = null;
Connection mySqlConnection = null;
Statement mySqlStat = null;

Connection connB = null;
Statement stmtB = null;
   
try{
        // get the Transaction management object
userTx =
            (UserTransaction) getContext().lookup("java:comp/UserTransaction");
// get mySql database connection
locally mySqlConnection = DriverManager.getConnection("localhost:1111");

// Get the database connection from database B, getDataSourceB returns the data source of the application server
connB = getDataSourceB().getConnection();
     
                        // start the transaction
userTx.begin();

// reduce the amount in the A account by 500
//mySqlConnection It is a database connection obtained locally and will not be included in the global transaction
mySqlStat = mySqlConnection.createStatement();
mySqlStat.execute("
             update t_account set amount = amount - 500 where account_id = 'A'");

//connB is The database connection from the application server will be included in the global transaction
stmtB = connB.createStatement();
stmtB.execute("
             update t_account set amount = amount + 500 where account_id = 'B'");

// transaction commit: The operation of connB is submitted, the operation of mySqlConnection will not be submitted
userTx.commit();

} catch(SQLException sqle){
// Handling exception code
} catch(Exception ne){
e.printStackTrace();
}
}
Why must a database connection obtained from a transaction-enabled data source support distributed transactions? In fact, the data source that supports transactions is different from the ordinary data source, it implements the additional XADataSource interface. We can simply understand XADataSource as an ordinary data source (inheriting java.sql.PooledConnection), but it adds the getXAResource method to support distributed transactions. In addition, the database connection returned by the XADataSource is also different from the ordinary connection. This connection implements the XAConnection interface in addition to all the functions defined by java.sql.Connection. We can understand XAConnection as an ordinary database connection, it supports all database operations of the JDBC specification, the difference is that XAConnection adds support for distributed transactions. Through the following class diagram, readers can understand the relationship between these interfaces:
Figure 3. Transaction resource class diagram

Transaction class diagram The database connection obtained by the
application from the data source that supports distributed transactions is the implementation of the XAConnection interface, and the The session (Statement) created by this database connection also adds functionality to support distributed transactions, as shown in the following code:
Listing 8. JTA transaction resource handling
public void transferAccount() {

UserTransaction userTx = null;

Connection conn = null;
Statement stmt = null;
   
try{
        // Get the Transaction management object
userTx = (UserTransaction)getContext().lookup("
java:comp/UserTransaction");

// Get the database connection from the database, getDataSourceB returns the data that supports distributed transactions source
conn = getDataSourceB().getConnection();
                        // session stmt has been enhanced to support distributed transactions
stmt = conn.createStatement();

                        // start transaction
userTx.begin();
             stmt.execute("update t_account ... where account_id = 'A'");
userTx.commit();

} catch(SQLException sqle){
// handle exception code
} catch(Exception ne){
e.printStackTrace();
}
}
Let's take a look at the code implementation of the Session (Statement) part created by the XAConnection database connection (different JTA providers have different implementations, the code example here is just to show you how transaction resources are automatically added to the transaction). We take the execute method of the session object as an example. By adding a call to the associateWithTransactionIfNecessary method at the beginning of the method, we can ensure that during the JTA transaction, the operation of any database connection will be transparently added to the transaction.
Listing 9. Automatically associate transaction resources to transaction objects - XAStatement implements Statement
public void execute(String sql) {
                // For every database operation check if the database connection this session is on has been added to the transaction
associateWithTransactionIfNecessary();

try {
                      // Code for handling database operations
      ....

} catch(SQLException sqle){
// Code for handling exceptions
} catch(Exception ne){
e.printStackTrace();
}
}

public void associateWithTransactionIfNecessary(){
    
// Get TransactionManager
TransactionManager tm = getTransactionManager();

                Transaction tx = tm.getTransaction();
        // Check whether the current thread has a distributed transaction
        if(tx != null){
// In a distributed transaction, use the tx object to determine whether the current data connection is already included in the transaction,
// if not then add this connection to the transaction
Connection conn = this.getConnection();
//tx.hasCurrentResource, xaConn.getDataSource() is not a standard JTA
                        // interface method, for A custom method added to implement distributed transactions
if(!tx.hasCurrentResource(conn)){
     XAConnection xaConn = (XAConnection)conn;
     XADataSource xaSource = xaConn.getDataSource();

     // Call the interface method of Transaction to store database transaction resources Join the current transaction
     tx.enListResource(xaSource.getXAResource(), 1);
         }
         }
        }
XAResource and Xid: XAResource is a Java implementation of the Distributed Transaction Processing: The XA Specification standard. It is an abstraction of the underlying transaction resources and defines the protocol between the transaction manager and the resource manager in the process of distributed transaction processing. A resource provider (eg JDBC driver, JMS) will provide an implementation of this interface. Using this interface, developers can implement distributed transaction processing through their own programming, but these are usually implemented by the application server (the server's own implementation is more efficient and stable) To illustrate, we will give an example of how it is used.
Before using distributed transactions, in order to distinguish transactions without confusion, a Xid class must be implemented to identify transactions. Xid can be thought of as an identifier for transactions. Every time a new transaction is created, a Xid is assigned to the transaction. , Xid contains three elements: formatID, gtrid (global transaction identifier) ​​and bqual (branch modifier identifier). formatID is usually zero, which means you will use the OSI CCR (Open Systems Interconnection Commitment, Concurrency and Recovery standard) for naming; if you want to use another format, formatID should be greater than zero, and a value of -1 means Xid is invalid .
gtrid and bqual contain 64-byte binary codes to identify global transactions and branch transactions, respectively. The only requirement is that gtrid and bqual must be globally unique.
The XAResource interface mainly defines the following methods:
commit()- commits the transaction
isSameRM(XAResource xares)- checks whether the current XAResource and the parameters are the same transaction resource
prepare()- notify the resource manager to prepare the commit work of the transaction
rollback()- notify the resource manager to roll back the transaction
When the transaction is submitted, the Transaction object will collect all the XAResource resources contained in the current transaction, and then call the submit method of the resource, As shown in the following code:
Listing 10. Commit transaction - TransactionImpl implements Transaction
public void commit() throws RollbackException, HeuristicMixedException,
HeuristicRollbackException, SecurityException,
IllegalStateException, SystemException {

// Get all transaction resources in the current transaction
        List<XAResource> list = getAllEnlistedResouces( );

// Notify all transaction resource managers to prepare to commit the transaction
                        // For production-level implementations, additional processing is required here to handle exceptions that occur during the preparation of some resources
for(XAResource xa : list){
xa. prepare();
}

// All transactional resources, commit the transaction
for(XAResource xa : list){
xa.commit();
}
   }
Back to top
Conclusion
Through the above introduction, I believe that the reader has some understanding of the principle of JTA. The sample code in this article is a hypothetical implementation under ideal conditions. A complete and mature JTA transaction implementation needs to consider and deal with many details, such as performance (multi-threaded concurrent submission of transactions when submitting transactions), fault tolerance (network, system exceptions), etc. It also takes a long time to mature. accumulation. Interested readers can read some open source JTA implementations for further in-depth study. References


The official website for learning Java " Java Transaction API Specification " provides detailed JTA information, including version, specification, etc. Interested readers can refer to this article " Java Transaction API Overview " (Programming China), which describes the XA resources in detail. Using the principle, interested readers can refer to learning " JOTM " is an open source JTA implementation, readers can refer to its source code to learn more about the developerWorks Java technology area : There are hundreds of articles about various aspects of Java programming. Discussion Participate in forum discussions . Join the developerWorks Chinese community . View developer-driven blogs, forums, groups, and wikis, and connect with other developerWorks users. Reprinted:










http://www.ibm.com/developerworks/cn/java/j-lo-jta/

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327033772&siteId=291194637