原文發佈於:http://www.gufeng.tech/ 穀風的我的主頁java
這是工做中遇到的一個真實問題的處理過程,若是對分析過程不感興趣,能夠直接跳到最後看最終方案。sql
咱們在持久化這一層,並無用任何的ORM框架(不管是Hibernate仍是MyBatis,亦或是DBUtils),而是採用了在JDBCTemplate基礎上進行了簡單的包裝,同時咱們也決定將AutoCommit設置爲True,在sql語句執行完成後當即提交。這樣作相比於@Transactional註解或者經過AOP來控制事務性能更好,也更方便。數據庫
在評估了優劣以後,便開始使用這種方式,隨之咱們也遇到了一個真實的問題。這裏咱們把涉及公司的信息所有隱藏掉,簡化後的需求是:一個業務要連續執行兩個表的insert操做,必須保證同時生效或失敗。固然採用補償的方式也能達到效果,可是考慮到咱們的用戶量不是十分巨大,並且將來一段時間內用戶不會暴增,採用補償有點得不償失,因此決定在特定狀況下采用手動控制事務,其他狀況默認AutoCommit爲True。在定了這個目標以後,開始研究若是在JDBCTemplate基礎上實現。框架
首先嚐試的是得到Connection,並設置AutoCommit爲False,代碼以下:ide
DataSource dataSource = ((JdbcTemplate)namedParameterJdbcTemplate.getJdbcOperations()).getDataSource(); Connection connection = DataSourceUtils.getConnection(dataSource); connection.setAutoCommit(false);
設置以後,測試發現並不生效,此時已經知道這麼作是沒有用的。接下來咱們進一步分析,看在JDBCTemplate中是如何得到數據庫鏈接的,能夠經過打斷點的方式查看,每次得到的connection對象的hashCode不一致。性能
咱們知道NamedParameterJdbcTemplate這個類持有一個JdbcTemplate的實例,咱們從NamedParameterJdbcTemplate的update方法逐層跟進去,發現最終調用的是JdbcTemplate類的下面方法:測試
protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss) throws DataAccessException
這個方法又調用了下面的方法:spa
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException
在這個execute方法裏咱們找到了JdbcTemplate得到數據庫鏈接的方式,即:線程
Connection con = DataSourceUtils.getConnection(getDataSource());
繼續跟蹤進去,發現最終調用的是DataSourceUtils的下面方法:
debug
public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); 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(); } // Else we either got no holder or an empty thread-bound holder here. logger.debug("Fetching JDBC Connection from DataSource"); Connection con = dataSource.getConnection(); if (TransactionSynchronizationManager.isSynchronizationActive()) { logger.debug("Registering transaction synchronization for JDBC Connection"); // Use same Connection for further JDBC actions within the transaction. // Thread-bound object will get removed by synchronization at transaction completion. ConnectionHolder holderToUse = conHolder; if (holderToUse == null) { holderToUse = new ConnectionHolder(con); } else { holderToUse.setConnection(con); } holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization( new ConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } return con; }
解釋一下上面的代碼:每次得到數據庫鏈接時,都是首先判斷TransactionSynchronizationManager裏面是否包含了ConnectionHolder,若是包含了則直返回,若是未包含,則首先從DataSource中得到一個Connection,而後分兩種狀況進行處理:
一 當TransactionSynchronizationManager.isSynchronizationActive()爲True時,則初始化ConnectionHolder,並調用TransactionSynchronizationManager.bindResource(dataSource, holderToUse);完成綁定。關於綁定的範圍,咱們看一下TransacionSynchronizationManager代碼中變量的定義,就能知道了。
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<Map<Object, Object>>("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<String>("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<Boolean>("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<Integer>("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<Boolean>("Actual transaction active");
都是ThreadLocal的,也就是生效範圍是當前線程內。
二 不處理ConnectionHolder,直接返回connection。
看到這裏,你們必定知道該怎麼處理了,接下來給出咱們最終的修改代碼(省略掉了catch中的所有):
TransactionSynchronizationManager.initSynchronization(); DataSource dataSource = ((JdbcTemplate)jdbcTemplate.getJdbcOperations()).getDataSource(); Connection connection = DataSourceUtils.getConnection(dataSource); try { connection.setAutoCommit(false); //須要操做數據庫的兩個insert,或者提供回調給業務開發人員 connection.commit(); } catch (SQLException e) { } finally { try { TransactionSynchronizationManager.clearSynchronization(); } catch (IllegalStateException e) { } try { connection.setAutoCommit(true); } catch (SQLException e) { } }
最後總結一下,Spring的JDBCTemplate提供的操做是很豐富的,只是平時沒有注意到。在遇到問題時必定不要慌,仔細分析邏輯、閱讀源碼,相信問題必定可以獲得解決。