In the previous article in this series we said that to implement the same connection object in the same transaction, we could achieve the purpose of sharing by passing the connection object, but it was ugly. In this article, we will introduce another mechanism (Connectionholder) to complete transaction management.
Connectionholder work mechanism is: We put connection objects in a global common place, and then in different operations from this place to obtain connection, so as to accomplish 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); }}
As can be seen from the Connectionholder class, 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 passed in when the Getconnection method is called, and if the datasource corresponding connection already exists in the map, the connection is returned directly, otherwise, Call DataSource's Getconnection method to get a new connection, add it to the map, and then return to the connection. So in the same transaction process, we have obtained from the Connectionholder connection is the same, Unless we call the Removeconnection method of Connectionholder to remove the current connection or call Connection.close () in the middle of the way, the connection is closed. Then call Connectionholder's Getconnection method again in a subsequent operation, and return a new connection object, causing the transaction to fail, and you should not have done this halfway to remove or close connection.
However, although we do not manually remove or close the Conncetion object in the middle of the process (of course, we should close conncetion at the end of the transaction), we cannot prevent other threads from doing so. For example, the Connectionholder class can be used simultaneously in multiple threads, and these threads use the same datasource, where one thread closes the connection after it is used, and another line is impersonating attempts to use this connection , the problem comes out. Therefore, the above connectionholder are not thread-safe.
To get the thread-safe Connectionholder class, we can introduce the Java-provided threadlocal class, which guarantees that an instance variable of a class has a separate copy in each thread, so that it does not affect 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 in individual DAO to get the Connection object:
Connection Connection = singlethreadconnectionholder.getconnection (DataSource);
Of course, at this point we need to pass in a DataSource, this datasource can exist as an instance variable of the DAO class, so we don't have to pass the Connection object directly to DAO's method as in the previous article. Here you may ask, since you can use DataSource as an instance variable, why can't you use connection as an instance variable in the previous article, so that you don't create an ugly API? The reason for this is that the connection object as an instance variable also poses a thread-safety problem, and when multiple threads are using the same DAO class, one thread shuts down connection and the other is in use. This is the same problem as the Connectionholder thread safety issue mentioned above.
About bank DAO and insurance DAO class source code is not listed here, they and the previous article just get Connection object method is not the same, you can refer to the GitHub source.
Next, let's take a look at the TransactionManager class, in the last few articles, we are all directly writing and transaction-related code in the service class, and the better way is to declare a Transactionmanger class to centrally manage the transaction-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.getconnec tion (dataSource); }}
As you can see, the TransactionManager object also maintains a DataSource instance variable and is also singlethreadconnectionholder to get the connection object. We then 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 (datasour CE); 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 Transactionmanger and two DAO classes use Singlethreadconnectionholder to get connection, they use the same connection object throughout the transaction process, and the transaction succeeds. We can also see that the withdraw and deposit methods in two DAO do not accept business-agnostic objects, eliminate API pollution, and use TransactionManager to manage transactions, making service layer code simple.
In the next article, we'll talk about using template mode to complete the transaction.
Full parsing of Java transaction (four)--successful case (self-implementation of a thread-safe transactionmanager)