Java交易處理全解析(四)—— 成功的案例(自己實現一個安全執行緒的TransactionManager)

來源:互聯網
上載者:User

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

 

這是一個關於Java交易處理的系列文章,請通過以下方式下載github原始碼:

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

 

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

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);    }}

 

從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類如下:

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;    }}

 

有了一個安全執行緒的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類將交易處理相關工作集中管理:

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);    }}

 

可以看出,TransactionManager對象也維護了一個DataSource執行個體變數,並且也是通過SingleThreadConnectionHolder來擷取Connection對象的。然後我們在service類中使用該TransactionManager:

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();        }    }}

 

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

 

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

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.