java事務全解析(四)--成功的案例(自己實現一個線程安全的TransactionManager)

本系列上一篇文章中我們講到,要實現在同一個事務中使用相同的Connection對象,我們可以通過傳遞Connection對象的方式達到共享的目的,但是這種做法是醜陋的。在本篇文章中,我們將引入另外一種機制(ConnectionHolder)來完成事務管理。

 

這是一個關於Java事務處理的系列文章,請通過以下方式下載github源代碼:

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

 

ConnectionHolder的工作機制是:我們將Connection對象放在一個全局公用的地方,然後在不同的操作中都從這個地方取得Connection,從而完成Connection共享的目的,這也是一種ServiceLocator模式,有點像JNDI。定義一個ConnectionHolder類如下:

  1. public class ConnectionHolder  
  2. {  
  3.     private Map<DataSource, Connection> connectionMap = new HashMap<DataSource, Connection>();  
  4.               
  5.     public Connection getConnection(DataSource dataSource) throws SQLException  
  6.     {  
  7.         Connection connection = connectionMap.get(dataSource);  
  8.         if (connection == null || connection.isClosed())  
  9.         {  
  10.             connection = dataSource.getConnection();  
  11.             connectionMap.put(dataSource, connection);  
  12.         }  
  13.               
  14.         return connection;  
  15.     }  
  16.               
  17.     public void removeConnection(DataSource dataSource)  
  18.     {  
  19.         connectionMap.remove(dataSource);  
  20.     }  
  21. }  
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);
    }
}

從ConnectionHolder類中可以看出,我們維護了一個鍵爲DataSource、值爲Connection的Map,這主要用於使ConnectionHolder可以服務多個DataSource。在調用getConnection方法時傳入了一個DataSource對象,如果Map裏面已經存在該DataSource對應的Connection,則直接返回該Connection,否則,調用DataSource的getConnection方法獲得一個新的Connection,再將其加入到Map中,最後返回該Connection。這樣在同一個事務過程中,我們先後從ConnectionHolder中取得的Connection是相同的,除非在中途我們調用了ConnectionHolder的removeConnection方法將當前Connection移除掉或者調用了Connection.close()將Connection關閉,然後在後續的操作中再次調用ConnectionHolder的getConnection方法,此時返回的則是一個新的Connection對象,從而導致事務處理失敗,你應該不會做出這種中途移除或關閉Connection的事情。

 

然而,雖然我們不會自己手動地在中途移除或者關閉Conncetion對象(當然,在事務處理末尾我們應該關閉Conncetion),我們卻無法阻止其他線程這麼做。比如,ConnectionHolder類是可以在多個線程中同時使用的,並且這些線程使用了同一個DataSource,其中一個線程使用完Connection後便將其關閉,而此時另外一個線程正試圖使用這個Connection,問題就出來了。因此,上面的ConnectionHolder不是線程安全的。

 

爲了獲得線程安全的ConnectionHolder類,我們可以引入Java提供的ThreadLocal類,該類保證一個類的實例變量在各個線程中都有一份單獨的拷貝,從而不會影響其他線程中的實例變量。定義一個SingleThreadConnectionHolder類如下:

  1. public class SingleThreadConnectionHolder  
  2. {  
  3.     private static ThreadLocal<ConnectionHolder> localConnectionHolder = new ThreadLocal<ConnectionHolder>();  
  4.              
  5.     public static Connection getConnection(DataSource dataSource) throws SQLException  
  6.     {  
  7.         return getConnectionHolder().getConnection(dataSource);  
  8.     }  
  9.              
  10.     public static void removeConnection(DataSource dataSource)  
  11.     {  
  12.         getConnectionHolder().removeConnection(dataSource);  
  13.     }  
  14.              
  15.     private static ConnectionHolder getConnectionHolder()  
  16.     {  
  17.         ConnectionHolder connectionHolder = localConnectionHolder.get();  
  18.         if (connectionHolder == null)  
  19.         {  
  20.             connectionHolder = new ConnectionHolder();  
  21.             localConnectionHolder.set(connectionHolder);  
  22.         }  
  23.         return connectionHolder;  
  24.     }  
  25.              
  26. }  
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;
    }
           
}

有了一個線程安全的SingleThreadConnectionHolder類,我們便可以在service層和各個DAO中使用該類來獲取Connection對象:

 

Connection connection = SingleThreadConnectionHolder.getConnection(dataSource);

 

 

當然,此時我們需要傳入一個DataSource,這個DataSource可以作爲DAO類的實例變量存在,所以我們不用像上一篇文章那樣將Connection對象直接傳給DAO的方法。這裏你可能要問,既然可以將DataSource作爲實例變量,那麼在上一篇文章中,爲什麼不可以將Connection也作爲實例變量呢,這樣不就不會造成醜陋的API了嗎?原因在於:將Connection對象作爲實例變量同樣會帶來線程安全問題,當多個線程同時使用同一個DAO類時,一個線程關閉了Connection而另一個正在使用,這樣的問題和上面講到的ConnectionHolder的線程安全問題一樣。

 

關於Bank DAO和Insurance DAO類的源代碼這裏就不列出了,他們和上篇文章只是獲得Connection對象的方法不一樣而已,你可以參考github源代碼

 

接下來,我們再來看看TransactionManager類,在上幾篇文章中,我們都是在service類中直接寫和事務處理相關的代碼,而更好的方式是聲明一個TransactionManger類將事務處理相關工作集中管理:

  1. public class TransactionManager  
  2. {  
  3.     private DataSource dataSource;  
  4.           
  5.     public TransactionManager(DataSource dataSource)  
  6.     {  
  7.         this.dataSource = dataSource;  
  8.     }  
  9.           
  10.     public final void start() throws SQLException  
  11.     {  
  12.         Connection connection = getConnection();  
  13.         connection.setAutoCommit(false);  
  14.     }  
  15.           
  16.     public final void commit() throws SQLException  
  17.     {  
  18.         Connection connection = getConnection();  
  19.         connection.commit();  
  20.     }  
  21.           
  22.     public final void rollback()  
  23.     {  
  24.         Connection connection = null;  
  25.         try  
  26.         {  
  27.             connection = getConnection();  
  28.             connection.rollback();  
  29.           
  30.         } catch (SQLException e)  
  31.         {  
  32.             throw new RuntimeException("Couldn't rollback on connection[" + connection + "].", e);  
  33.         }  
  34.     }  
  35.           
  36.     public final void close()  
  37.     {  
  38.         Connection connection = null;  
  39.         try  
  40.         {  
  41.             connection = getConnection();  
  42.             connection.setAutoCommit(true);  
  43.             connection.setReadOnly(false);  
  44.             connection.close();  
  45.             SingleThreadConnectionHolder.removeConnection(dataSource);  
  46.         } catch (SQLException e)  
  47.         {  
  48.             throw new RuntimeException("Couldn't close connection[" + connection + "].", e);  
  49.         }  
  50.     }  
  51.           
  52.     private Connection getConnection() throws SQLException  
  53.     {  
  54.         return SingleThreadConnectionHolder.getConnection(dataSource);  
  55.     }  
  56. }  
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);
    }
}

可以看出,TransactionManager對象也維護了一個DataSource實例變量,並且也是通過SingleThreadConnectionHolder來獲取Connection對象的。然後我們在service類中使用該TransactionManager

  1. public class ConnectionHolderBankService implements BankService  
  2. {  
  3.     private TransactionManager transactionManager;  
  4.     private ConnectionHolderBankDao connectionHolderBankDao;  
  5.     private ConnectionHolderInsuranceDao connectionHolderInsuranceDao;  
  6.          
  7.     public ConnectionHolderBankService(DataSource dataSource)  
  8.     {  
  9.         transactionManager = new TransactionManager(dataSource);  
  10.         connectionHolderBankDao = new ConnectionHolderBankDao(dataSource);  
  11.         connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource);  
  12.          
  13.     }  
  14.          
  15.     public void transfer(int fromId, int toId, int amount)  
  16.     {  
  17.         try  
  18.         {  
  19.             transactionManager.start();  
  20.             connectionHolderBankDao.withdraw(fromId, amount);  
  21.             connectionHolderInsuranceDao.deposit(toId, amount);  
  22.             transactionManager.commit();  
  23.         } catch (Exception e)  
  24.         {  
  25.             transactionManager.rollback();  
  26.         } finally  
  27.         {  
  28.             transactionManager.close();  
  29.         }  
  30.     }  
  31. }  
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();
        }
    }
}

在ConnectionHolderBankService中,我們使用TransactionManager來管理事務,由於TransactionManger和兩個DAO類都是使用SingleThreadConnectionHolder來獲取Connection,故他們在整個事務處理過程中使用了相同的Connection對象,事務處理成功。我們也可以看到,在兩個DAO的withdraw和deposit方法沒有接受和業務無關的對象,消除了API污染;另外,使用TransactionManager來管理事務,使Service層代碼也變簡潔了。

 

下一篇文章中,我們將講到使用Template模式來完成事務處理。

轉載地址:http://www.davenkin.me/post/2013-02-23/40049986447



發佈了31 篇原創文章 · 獲贊 15 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章