【Spring事物三千问】TransactionSynchronizationManager的原理分析

时间:2023-02-05 15:58:00

TransactionSynchronizationManager

TransactionSynchronizationManager 是管理每个线程的DB连接资源和事务同步的核心委托类。

如果事务同步未处于活动状态,则表示当前没有事务,或者事务管理器不支持事务同步。

TransactionSynchronizationManager 中定义了很多 ThreadLocal 变量,来保存事务相关的信息

public abstract class TransactionSynchronizationManager {

    // 保存DB连接资源。
    // 使用 Object 来保存是因为每种平台的DB连接资源对象可能不一样,比如:JDBC,Hibernate,EJB 等使用的 DB 连接对象是不一样的。 
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");

    // 事务同步回调。每个线程可以注册多个事务同步回调
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
    
    // 当前事务的名称  
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name");
    // 当前事务是否只读  
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status");
    // 当前事务的隔离级别  
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level");
    // 事务是否开启  
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
}

不同的平台的DB连接资源对象可能是不一样的,反映在 Spring 源码当中就是调用 TransactionSynchronizationManager#bindResource() 方法时,绑定的 DB 连接资源不同。

TransactionSynchronizationManager 事务同步管理器的核心逻辑

  • 为什么要使用事务同步管理器 TransactionSynchronizationManager?
    答:不同平台的事务管理实现方式不同,DB 连接资源对象也可能不同,所以,通过 TransactionSynchronizationManager 将事务资源相关的信息保存在各个 ThreadLocal 对象中,可以隔离不同平台带来的差异。

将当前 DB 连接资源绑定到当前线程中的代码如下:
【Spring事物三千问】TransactionSynchronizationManager的原理分析

不管是什么平台,Spring 封装的获取 DB 连接资源的编程模型都是统一的:

  1. 从当前线程中获取绑定的 DB 连接资源 --- TransactionSynchronizationManager#getResource()
  2. 如果没有获取到,则从 DataSource 中获取 DB 连接资源
  3. 将获取到的 DB 连接资源绑定到当前线程中 --- TransactionSynchronizationManager#bindResource()

TransactionSynchronization 事务回调扩展

Spring 会通过 AbstractPlatformTransactionManager#processCommit() 去处理事务的提交,在事务提交前后预留了一些扩展点。

处理事务提交的代码如下:
【Spring事物三千问】TransactionSynchronizationManager的原理分析

可以看到,事务提交前后有一些 triggerXxx 方法是 Spring 预留的扩展点。底层是调用当前线程中注册的事务同步回调 TransactionSynchronization 中的方法。

// TransactionSynchronizationUtils#triggerBeforeCommit()  
public static void triggerBeforeCommit(boolean readOnly) {
    for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
        synchronization.beforeCommit(readOnly);
    }
}

事务回调的扩展方法有: triggerBeforeCommit()、triggerBeforeCompletion()、triggerAfterCommit()、triggerAfterCompletion()
参考: org.springframework.transaction.support.TransactionSynchronization

https://www.cnblogs.com/kkkfff/p/13778692.html
https://carlzone.blog.csdn.net/article/details/108659198

慎用事务回调扩展

问题一:triggerXx 方法中操作数据库的话,对原事物是否有影响?

TransactionSynchronization 扩展出的方法中,能不能在方法里面操作数据库,要打个问号??
这里面带来的问题是:如果在 triggerXx 方法中操作数据库,是新开启一个连接,还是在原来的事务连接中进行操作? 如果操作不当,容易引发问题,要慎用! https://www.jianshu.com/p/149de22ca54b

其实,这些预留扩展的 trigger 方法都可以通过其他变通的方式来书写,没必要一定要使用。在博主的工作中,还从未碰到过不使用 trigger 扩展解决不了的场景。

问题二:triggerAfterCommit()、triggerAfterCompletion()

在阅读源码的过程中,可以发现: 在 triggerAfterCommit()、triggerAfterCompletion() 中使用事物的话,不会对上层事物产生任何影响了,因为 spring-tx 对异常的处理没有包含 triggerAfterCommit()、triggerAfterCompletion(), 所以,上层事物不会再处理 triggerAfterCommit()、triggerAfterCompletion() 方法里面抛出的异常。

还有个问题是需要尤其注意的,triggerAfterCommit()、triggerAfterCompletion() 方法中使用事物的话,还有一个风险,内外事物如果使用的是同一个事物的话,那么,事物连接在这个时候可能已经被 连接池回收了,从而导致诡异的问题。

可以参看源码注释: org.springframework.transaction.support.TransactionSynchronization#afterCompletion
TransactionSynchronization#afterCommit 与 TransactionSynchronization#afterCompletion 是类似的。

Invoked after transaction commit/rollback. Can perform resource cleanup after transaction completion.
NOTE: The transaction will have been committed or rolled back already, but the transactional resources might still be active and accessible.
As a consequence, any data access code triggered at this point will still "participate" in the original transaction, 
allowing to perform some cleanup (with no commit following anymore!), unless it explicitly declares that it needs to run in a separate transaction. 
Hence: Use PROPAGATION_REQUIRES_NEW for any transactional operation that is called from here.