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

这是工作中遇到的一个真实问题的处理过程,如果对分析过程不感兴趣,可以直接跳到最后看最终方案。

我们在持久化这一层,并没有用任何的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的实例,我们从NamedParameterJdbcTemplate的update方法逐层跟进去,发现最终调用的是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提供的操作是很丰富的,只是平时没有注意到。在遇到问题时一定不要慌,仔细分析逻辑、阅读源码,相信问题一定能够得到解决。

时间: 2024-12-29 12:38:52

JDBCTemplate在AutoCommit为True时手动控制事务的相关文章

springboot之手动控制事务

一.事务的重要性,相信在实际开发过程中,都有很深的了解了.但是存在一个问题我们经常在开发的时候一般情况下都是用的注解的方式来进行事务的控制,说白了基于spring的7种事务控制方式来进行事务的之间的协调. 二.spring的7中事务传播行为 Propagation.REQUIRED 代表当前方法支持当前的事务,且与调用者处于同一事务上下文中,回滚统一回滚(如果当前方法是被其他方法调用的时候,且调用者本身即有事务),如果没有事务,则自己新建事务, Propagation.SUPPORTS 代表当前

修改XPMenu让ToolButton在Down=True时正确显示

XPMenu是一个不错的程序界面效果控件,但它也存在不少不足之处.我最近又对它作了一点修改. 原因是我在程序里有一个ToolButton,其Style=tbsButton,当Down=True时,XPMenu绘制的效果效果跟Down=False时一样,也就是说根本看不出它是按下的.当把Style改为tbsCheck后,却能显示效果,但是底色很深. 这个按钮来我是用来表示某个面板是否可以显示的,我希望它像OfficeXP的工具按钮那样,当工具条显示时,在按钮上画个边框即可,而不是以很深的底色显示.

cxgrid GridMode 等于 True 时的一些问题。

When using grid mode, the data controller loads a fixed number of dataset records into memory. The number of records to be loaded depends on the GridModeBufferCount property value. A user is permitted to perform data-related operations on the loaded

只有在配置文件或 Page 指令中将 enableSessionState 设置为 true 时,才能使用会话状态。还请确保在应用程序配置的 // 节中包括 System.Web.SessionSta

首先搞清楚我们的目的,我的目的是验证用户是否登录,也就是Session["userName"]!=null 就ok了 开始的时候我是这么写的,结果报错,提示如上面标题的错误,查了半天资料都没搞清楚什么问题 public class BasePage:System.Web.UI.Page { public BasePage() { this.IsLogin(); } public void IsLogin() { if (Session["DateTime"] ==

UIButton的selected设为TRUE时在按下时显示自定义的背景图

在UIButton的selected设为TRUE后,需要在按钮高亮时,显示自定义的背景图. 经研究hightLighted和selected这两个状态是可以重叠的,就是button可以同时处于selectec和highlighted两个状态下. 从UIControlState的定义也可以看出: typedef NS_OPTIONS(NSUInteger, UIControlState) { UIControlStateNormal       = 0, UIControlStateHighlig

Popup 解决StayOpen=true时,置顶的问题

/// <summary> /// 解决StayOpen=true时,永远置顶问题的Popup控件 /// </summary> public class EasiNotePopup : Popup { public static readonly DependencyProperty IsTopmostProperty = DependencyProperty.Register("IsTopmost", typeof(bool), typeof(EasiNot

MySQL可重复读采坑记录-对事务B进行更新时,事务A提交的更新会不会影响到事务B

之前线上出现数据重复插入的问题,通过对问题进行排查发现该问题和MySQL的默认隔离级别-Repeatable Read(可重读)有关系,可重复读确保同一事务的多个实例在并发读取数据时,会看到同样的数据行.现在通过实验,对问题进行下分析. 1.在终端A开启事务A,查询一下. START TRANSACTION; select spt.id,spt.audit_status,spt.is_deleted from stat_point_task spt limit 5; 结果如下: 2.在终端B开启

当TextBox的ReadOnly属性为true时,Postback后无法读取TextBox之值?

摘要:当TextBox的ReadOnly属性为true时,无法读取Text之值? 文章移至http://petekcchen.com/2009/07/cannot-read-textbox-value-after-postback-when-readonly-is-true.html 原文:大专栏  当TextBox的ReadOnly属性为true时,Postback后无法读取TextBox之值? 原文地址:https://www.cnblogs.com/petewell/p/11516571.

手动控制事务

1.事务简单介绍: 1)事务(Transaction)是并发控制的单位,是用户定义的一个操作序列. 这些操作要么都做,要么都不做,是一个不可切割的工作单位.通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,以便server保持数据的完整性. 2)事务一般是以BEGIN TRANSACTION開始,以COMMIT或ROLLBACK结束. COMMIT表示提交,即提交事务的全部操作.详细地说就是将事务中全部对数据库的更新写回到磁盘上的物理数据库中去,事务正常结束. ROLLBACK表示