spring事務管理器設計思想(一)


在最近做的一個項目里面,涉及到多數據源的操作,比較特殊的是,這多個數據庫的表結構完全相同,由於我們使用的ibatis框架作為持久化層,為了防止每一個數據源都配置一套規則,所以重新實現了數據源,根據線程變量中指定的數據庫連接名稱來獲取實際的數據源。

一個簡單的實現如下:

public class ProxyDataSource implements DataSource {
/** 數據源池配置 */
private Map<String, DataSource> dataSourcePoolConfig;

public Connection getConnection() throws SQLException {
        return createDataSource().getConnection();
}
private synchronized DataSource createDataSource() {
        String dbName = DataSourceContextHolder.getDbName();
        return dataSourcePoolConfig.get(dbName);
}

 

每次調用spring事務管理器之前,設置DataSourceContextHolder.set(“dbName”) 

事務提交之后在調用 DataSourceContextHolder.clear() 方法即可

 但是這樣設計實際使用過程中也會遇到一些典型的問題,這就是在仔細了解spring中持久化層的設計之后,才能明白所產生的問題的原因。下面主要總結一下spring 持久化的設計。

 

Jdbc基本的編程模型

由於任何持久化層的封裝實際上都是對java.sql.Connection等相關對象的操作,一個典型的數據操作的流程如下:

但在我們實際使用spring和ibatis的時候,都沒有感覺到上面的流程,其實spring已經對外已經屏蔽了上述的操作,讓我們更關注業務邏輯功能,但是我們有必要了解其實現,以便能夠更好運用和定位問題。

 

開啟事務:

在開啟事務的時候,我們需要初始化事務上下文信息,以便在業務完成之后,需要知道事務的狀態,以便進行后續的處理,這個上下文信息可以保存在 ThreadLocal里面,包括是否已經開啟事務,事務的超時時間,隔離級別,傳播級別,是否設置為回滾。這個信息對應用來說是透明的,但是提供給使用者編程接口,以便告知業務結束的時候是提交事務還是回滾事務。

 

獲取連接

首先來看看spring如何獲取數據庫連接的,對於正常情況來看,獲取連接直接調用DataSource.getConnection()就可以了,我們在自己實現的時候也肯定會這么做,但是需要考慮兩種情況(這里面先不引入事務的傳播屬性):

1 還沒有獲取過連接,這是第一次獲取連接

2 已經獲取過連接,不是第一次獲取連接,可以復用連接

解決獲取數據庫連接的關鍵問題就是如何判斷是否已經可用的連接,而不需要開啟新的數據庫連接,同時由於數據庫連接需要給后續的業務操作復用,如何保持這個連接,並且透明的傳遞給后續流程。對於一個簡單的實現就是使用線程上下文變量ThrealLocal來解決以上兩個問題。

具體的實現是:在獲取數據庫連接的時候,判斷當前線程線程變量里面是否已經存在相關連接,如果不存在,就創新一個新的連接,如果存在,就直接獲取其對應的連接。在第一次獲取到數據庫連接的時候,我們還需要做一些特殊處理,就是設置自動提交為false。在業務活動結束的時候在進行提交或者回滾。這個時候就是要調用connection.setAutoCommit(false)方法。

 

執行sql

這一部分和業務邏輯相關,通過對外提供一些編程接口,可以讓業務決定業務完成之后如何處理事務,比較簡單的就是設置事務狀態。

 

提交事務:

在開啟事務的時候,事務上下文信息已經保存在線程變量里面了,可以根據事務上下文的信息,來決定是否是提交還是回滾。其實就是調用數據庫連接Connection.commit 和 Connection.rollback 方法。然后需要清空線程變量中的事務上下文信息。相當於結束了當前的事務。

  

關閉連接:

關閉連接相對比較簡單,由於當前線程變量保存了連接信息,只需要獲取連接之后,調用connection.close方法即可,接着清空線程變量的數據庫連接信息。

 上面幾個流程是一個簡單的事務處理流程,在spring中都有對應的實現,見TransactionTemplate.execute方法。Spring定義了一個TransactionSynchronizationManager對象,里面保存了各種線程變量信息,

 

//保存了數據源和其對應連接的映射,value是一個Map結構,其中key為datasource,value為其打開的連接

private static final ThreadLocal resources

//這個暫時用不到,不解釋

private static final ThreadLocal synchronizations

//當前事務的名字

private static final ThreadLocal currentTransactionName

//是否是只讀事務以及事務的隔離級別(這個一般我們都用不到,都是默認界別)

private static final ThreadLocal currentTransactionReadOnly

private static final ThreadLocal currentTransactionIsolationLevel

//代表是否是一個實際的事務活動,這個后面將)

private static final ThreadLocal actualTransactionActive

 

在獲取連接的時候,可見DataSourceUtils.doGetConnection()方法,就是從調用TransactionSynchronizationManager.getResource(dataSource)獲取連接信息,如果為空,就直接從調用dataSource.getConnection()創建新的連接,后面在調用

TransactionSynchronizationManager.bindResource(dataSource,conn)綁定數據源到線程變量,以便后續的線程在使用。

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

                   if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {

                            conHolder.requested();

                            if (!conHolder.hasConnection()) {

                                     logger.debug("Fetching resumed JDBC Connection from DataSource");

                                     conHolder.setConnection(dataSource.getConnection());

                            }

                            return conHolder.getConnection();

                   }

        

                   logger.debug("Fetching JDBC Connection from DataSource");

                   Connection con = dataSource.getConnection();

在提交事務的時候,見 DataSourceTransactionManager.doCommit方法,其實就是獲取事務狀態信息以及連接信息,調用conn.commmit方法,比較簡單。

 

但是實際上,spring事務管理遠遠比上述復雜,我們沒有考慮以下幾種情況:

1 如果當前操作不需要事務支持,也就是每次執行一次,就自動進行提交。如何在同一個架構里面兼容這兩種情況。比如就是簡單的query操作。

2 一個業務活動跨越多個事務,每個事務的傳播級別配置不一樣。后面會拿一個例子來說明

 

對於第一個問題,比較好解決,首先就是根據線程變量里面獲取數據源對應的連接,如果有連接,就復用。如果沒有,就創建連接。在判斷當前是否存在活動的事務上下文,如果存在事務信息,設置conn.setAutoCommit(false),然后設置線程上下文,綁定對應的數據源。如果不存在事務信息,就直接返回連接給應用。

這樣就會帶來一個新的問題,就是連接如何進行關閉。根據最開始的分析,在存在事務上下文的情況下,直接從獲取線程獲取對應的數據庫連接,然后關閉。在關閉的也需要也進行判斷一下即可。在spring里面,在事務中獲取連接和關閉連接有一些特殊的處理,主要還是和其jdbc以及orm框架設計兼容。在jdbcTemplate,IbatiTemplate每執行一次sql操作,就需要獲取conn,執行sql,關閉conn。如果不存在事務上下文,這樣做沒有任何問題,獲取一次連接,使用完成,然后就是比。但是如果存在事務上下文,每次獲取的conn並不一定是真實的物理連接,所以關閉的時候,也不能直接關閉這數據庫連接。Spring的中定義一個ConnectionHandle對象,這個對象持有一個數據庫連接對象,以及該連接上的引用次數(retain屬性)。每次復用一次就retain++ 操作,沒關閉一次,就執行retain-- 操作,在retain 為0的時候,說明沒有任何連接,就可以進行真實的關閉了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM