JDBCTemplate在AutoCommit爲True時手動控制事務

原文發佈於:http://www.gufeng.tech/  穀風的個人主頁

      這是工作中遇到的一個真實問題的處理過程,如果對分析過程不感興趣,可以直接跳到最後看最終方案。

      我們在持久化這一層,並沒有用任何的ORM框架(無論是Hibernate還是MyBatis,亦或是DBUtils),而是採用了在JDBCTemplate基礎上進行了簡單的包裝,同時我們也決定將AutoCommit設置爲True,在sql語句執行完成後立即提交。這樣做相比於@Transactional註解或者通過AOP來控制事務性能更好,也更方便。

      在評估了優劣之後,便開始使用這種方式,隨之我們也遇到了一個真實的問題。這裏我們把涉及公司的信息全部隱藏掉,簡化後的需求是:一個業務要連續執行兩個表的insert操作,必須保證同時生效或失敗。當然採用補償的方式也能達到效果,但是考慮到我們的用戶量不是十分巨大,而且未來一段時間內用戶不會暴增,採用補償有點得不償失,所以決定在特定情況下采用手動控制事務,其餘情況默認AutoCommit爲True。在定了這個目標之後,開始研究如果在JDBCTemplate基礎上實現。

      首先嚐試的是獲得Connection,並設置AutoCommit爲False,代碼如下:

DataSource dataSource = ((JdbcTemplate)namedParameterJdbcTemplate.getJdbcOperations()).getDataSource();
Connection connection = DataSourceUtils.getConnection(dataSource);
connection.setAutoCommit(false);

      設置之後,測試發現並不生效,此時已經知道這麼做是沒有用的。接下來我們進一步分析,看在JDBCTemplate中是如何獲得數據庫連接的,可以通過打斷點的方式查看,每次獲得的connection對象的hashCode不一致。


      我們知道NamedParameterJdbcTemplate這個類持有一個JdbcTemplate的實例,我們從NamedParameterJdbcTemplateupdate方法逐層跟進去,發現最終調用的是JdbcTemplate類的下面方法:

protected int update(final PreparedStatementCreator psc, final PreparedStatementSetter pss)
      throws DataAccessException

      這個方法又調用了下面的方法:

public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
      throws DataAccessException

      在這個execute方法裏我們找到了JdbcTemplate獲得數據庫連接的方式,即:

Connection con = DataSourceUtils.getConnection(getDataSource());

      繼續跟蹤進去,發現最終調用的是DataSourceUtils的下面方法:

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提供的操作是很豐富的,只是平時沒有注意到。在遇到問題時一定不要慌,仔細分析邏輯、閱讀源碼,相信問題一定能夠得到解決。











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章