JDBCTemplate在AutoCommit为True时手动控制事务

原文发布于: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的实例,咱们从NamedParameterJdbcTemplateupdate方法逐层跟进去,发现最终调用的是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提供的操做是很丰富的,只是平时没有注意到。在遇到问题时必定不要慌,仔细分析逻辑、阅读源码,相信问题必定可以获得解决。

相关文章
相关标签/搜索