Full parsing of Java Transaction Processing (4) -- successful cases (implementing a thread-safe TransactionManager by yourself)

Source: Internet
Author: User

In the previous article of this series, we mentioned that to use the same Connection object in the same transaction, we can share the Connection object by passing the Connection object, however, this approach is ugly. In this article, we will introduce another mechanism (ConnectionHolder) to complete transaction management.

 

This is a series of articles about Java transaction processing. Please download the github source code in the following ways:

Git clone https://github.com/davenkin/java_transaction_workshop.git

 

The working mechanism of ConnectionHolder is: we put the Connection object in a global public place, and then get the Connection from this place in different operations to achieve the purpose of Connection Sharing, this is also a ServiceLocator mode, a bit like JNDI. Define a ConnectionHolder class as follows:

package davenkin.step3_connection_holder;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;import java.util.HashMap;import java.util.Map;public class ConnectionHolder{    private Map<DataSource, Connection> connectionMap = new HashMap<DataSource, Connection>();    public Connection getConnection(DataSource dataSource) throws SQLException    {        Connection connection = connectionMap.get(dataSource);        if (connection == null || connection.isClosed())        {            connection = dataSource.getConnection();            connectionMap.put(dataSource, connection);        }        return connection;    }    public void removeConnection(DataSource dataSource)    {        connectionMap.remove(dataSource);    }}

 

From the ConnectionHolder class, we can see that we maintain a Map with a key of DataSource and a value of Connection, which is mainly used to enable ConnectionHolder to serve multiple DataSource. A DataSource object is introduced when the getConnection method is called. If the Connection corresponding to the DataSource already exists in the Map, the Connection is directly returned. Otherwise, call the getConnection method of DataSource to obtain a new Connection, add it to the Map, and return the Connection. In this way, the Connection obtained from ConnectionHolder is the same in the same transaction process, unless we call the removeConnection method of ConnectionHolder in the middle to remove the current Connection or call the Connection. close () closes the Connection, and then calls the getConnection method of ConnectionHolder again in subsequent operations. At this time, a new Connection object is returned, leading to transaction failure, you should not remove or close the Connection halfway.

 

However, although we do not manually remove or close the Conncetion object in the middle (of course, we should disable Conncetion at the end of the transaction processing), we cannot prevent other threads from doing so. For example, the ConnectionHolder class can be used in multiple threads at the same time, and these threads use the same DataSource. One of the threads closes the Connection after the Connection is used, at this time, another thread is trying to use this Connection, and the problem arises. Therefore, the above ConnectionHolder is NOT thread-safe.

 

To obtain the thread-safe ConnectionHolder class, we can introduce the ThreadLocal class provided by Java, which ensures that the instance variables of a class have a separate copy in each thread, this will not affect the instance variables in other threads. Define a SingleThreadConnectionHolder class as follows:

package davenkin.step3_connection_holder;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;public class SingleThreadConnectionHolder{    private static ThreadLocal<ConnectionHolder> localConnectionHolder = new ThreadLocal<ConnectionHolder>();    public static Connection getConnection(DataSource dataSource) throws SQLException    {        return getConnectionHolder().getConnection(dataSource);    }    public static void removeConnection(DataSource dataSource)    {        getConnectionHolder().removeConnection(dataSource);    }    private static ConnectionHolder getConnectionHolder()    {        ConnectionHolder connectionHolder = localConnectionHolder.get();        if (connectionHolder == null)        {            connectionHolder = new ConnectionHolder();            localConnectionHolder.set(connectionHolder);        }        return connectionHolder;    }}

 

With a thread-safe SingleThreadConnectionHolder class, we can use this class in the service layer and each DAO to obtain the Connection object:

        Connection connection = SingleThreadConnectionHolder.getConnection(dataSource);

 

Of course, at this time, we need to pass in a DataSource, which can be used as an instance variable of the DAO class, so we do not need to pass the Connection object directly to the DAO method as in the previous article. Here you may want to ask, since DataSource can be used as an instance variable, in the previous article, why can't we use Connection as an instance variable? Isn't this an ugly API? The reason is: using the Connection object as an instance variable will also cause thread security problems. When multiple threads use the same DAO class at the same time, one thread closes the Connection and the other is in use, this problem is the same as the thread security problem of ConnectionHolder mentioned above.

The source code of the Bank DAO and Insurance DAO classes is not listed here. They are different from the method used to obtain the Connection object in the previous article. You can refer to the source code of github.

Next, let's take a look at the TransactionManager class. In the last few articles, we all directly write the code related to transaction processing in the service class, the better way is to declare a TransactionManger class to centrally manage transaction processing-related work:

package davenkin.step3_connection_holder;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;public class TransactionManager{    private DataSource dataSource;    public TransactionManager(DataSource dataSource)    {        this.dataSource = dataSource;    }    public final void start() throws SQLException    {        Connection connection = getConnection();        connection.setAutoCommit(false);    }    public final void commit() throws SQLException    {        Connection connection = getConnection();        connection.commit();    }    public final void rollback()    {        Connection connection = null;        try        {            connection = getConnection();            connection.rollback();        } catch (SQLException e)        {            throw new RuntimeException("Couldn't rollback on connection[" + connection + "].", e);        }    }    public final void close()    {        Connection connection = null;        try        {            connection = getConnection();            connection.setAutoCommit(true);            connection.setReadOnly(false);            connection.close();            SingleThreadConnectionHolder.removeConnection(dataSource);        } catch (SQLException e)        {            throw new RuntimeException("Couldn't close connection[" + connection + "].", e);        }    }    private Connection getConnection() throws SQLException    {        return SingleThreadConnectionHolder.getConnection(dataSource);    }}

 

It can be seen that the TransactionManager object also maintains a DataSource instance variable and obtains the Connection object through SingleThreadConnectionHolder. Then we use the TransactionManager in the service class:

package davenkin.step3_connection_holder;import davenkin.BankService;import javax.sql.DataSource;public class ConnectionHolderBankService implements BankService{    private TransactionManager transactionManager;    private ConnectionHolderBankDao connectionHolderBankDao;    private ConnectionHolderInsuranceDao connectionHolderInsuranceDao;    public ConnectionHolderBankService(DataSource dataSource)    {        transactionManager = new TransactionManager(dataSource);        connectionHolderBankDao = new ConnectionHolderBankDao(dataSource);        connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource);    }    public void transfer(int fromId, int toId, int amount)    {        try        {            transactionManager.start();            connectionHolderBankDao.withdraw(fromId, amount);            connectionHolderInsuranceDao.deposit(toId, amount);            transactionManager.commit();        } catch (Exception e)        {            transactionManager.rollback();        } finally        {            transactionManager.close();        }    }}

 

In ConnectionHolderBankService, we use TransactionManager to manage transactions. Because both TransactionManger and DAO classes use SingleThreadConnectionHolder to obtain the Connection, they use the same Connection object throughout the transaction process, the transaction is successfully processed. We can also see that the withdraw and deposit methods of the two DAO do not accept objects unrelated to the business, eliminating API pollution. In addition, TransactionManager is used to manage transactions, the Service-Layer Code is also simplified.

 

In the next article, we will talk about using the Template mode to complete transaction processing.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.