延迟5个月的回复:编程式事务的传播行为存在并发问题?

最近任务比较少,也没有找到很好的写作题材。突然想起,还有一个承诺没有兑现。今天就来兑现一下对掘友的承诺吧。同时我们再来巩固一下事务传播行为的知识吧。

掘友:编程式事务属性存在并发问题 ?

先得看看一个掘友 5月前提的一个问题,也算是兑现了我的承诺

说一下结论吧,经过自己的测试和看源码,确实存在 掘友说的问题。传播范围确实存在现在安全问题

官方解释 :如果需要使用单例的不同配置,就需要单独创建 transcationTemplate 对象


解决方案

方案 1:为每个线程创建独立的 TransactionTemplate 实例
  • 实现方式

    在 Spring 中,可以通过 prototype 作用域的 Bean 或编程方式为每个线程创建独立的实例:

    java 复制代码
    // 在配置类中定义原型 Bean
    @Bean
    @Scope("prototype")
    public TransactionTemplate transactionTemplate(PlatformTransactionManager txManager) {
        TransactionTemplate template = new TransactionTemplate(txManager);
        // 设置默认配置(可选)
        template.setPropagationBehavior(Propagation.REQUIRED.value());
        return template;
    }
    
    // 在线程中使用时获取新实例
    TransactionTemplate threadLocalTemplate = context.getBean(TransactionTemplate.class);
    threadLocalTemplate.setIsolationLevel(Isolation.READ_COMMITTED.value());
    threadLocalTemplate.execute(...);
  • 优点:完全隔离线程间的配置修改,避免竞争。

  • 缺点:频繁创建实例可能增加内存开销(通常可忽略)。

方案 2:使用 ThreadLocal 绑定配置
  • 实现方式 : 将 TransactionTemplate 的隔离级别与当前线程绑定:

    java 复制代码
    private static final ThreadLocal<TransactionTemplate> threadLocalTemplate = 
        ThreadLocal.withInitial(() -> {
            TransactionTemplate template = new TransactionTemplate(txManager);
            template.setIsolationLevel(Isolation.DEFAULT.value());
            return template;
        });
    
    // 在线程中设置隔离级别
    threadLocalTemplate.get().setIsolationLevel(Isolation.READ_COMMITTED.value());
    threadLocalTemplate.get().execute(...);
  • 优点:配置隔离彻底,无竞争风险。

  • 缺点 :需要手动管理 ThreadLocal 的清理,避免内存泄漏。

方案 3:配置后不再修改
  • 实现方式 :在应用启动时初始化多个 TransactionTemplate 实例,每个实例预配置不同的隔离级别或传播行为,使用时按需选择:

    java 复制代码
    @Bean(name = "readCommittedTemplate")
    public TransactionTemplate readCommittedTemplate(PlatformTransactionManager txManager) {
        TransactionTemplate template = new TransactionTemplate(txManager);
        template.setIsolationLevel(Isolation.READ_COMMITTED.value());
        return template;
    }
    
    @Bean(name = "repeatableReadTemplate")
    public TransactionTemplate repeatableReadTemplate(PlatformTransactionManager txManager) {
        TransactionTemplate template = new TransactionTemplate(txManager);
        template.setIsolationLevel(Isolation.REPEATABLE_READ.value());
        return template;
    }
  • 优点:无运行时修改,线程安全。

  • 缺点:需要预定义所有可能的事务配置组合。


传播机制基础知识

传播机制

Spring的事务规定了7种事务的传播级别,默认的传播机制是REQUIRED

  • REQUIRED,如果不存在事务则开启一个事务,如果存在事务则加入之前的事务,总是只有一个事务在执行
  • REQUIRES_NEW,每次执行新开一个事务,如果当前存在事务,则把当前事务挂起
  • SUPPORTS,有事务则加入事务,没有事务则普通执行
  • NOT_SUPPORTED,有事务则暂停该事务,没有则普通执行
  • MANDATORY,强制有事务,没有事务则报异常
  • NEVER,有事务则报异常
  • NESTED,如果之前有事务,则创建嵌套事务,嵌套事务回滚不影响父事务,反之父事务影响嵌套事务

枚举代码在org.springframework.transaction.annotation.propagation.class 中

常见开启事务的方式,就是通过注解@Transaction 和 编程式事务 TransactionTemplate,这两个实现方式设置事务的隔离级别的方式当然也不一样了。代码如下:

注解开启事务

java 复制代码
//开启事务 设置隔离级别:REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void testTransaction(){
    deleteOne();
    updateTwo();
}

编程式事务

scss 复制代码
@Resource
private TransactionTemplate transactionTemplate;
@Override
public void testTransaction(){
   
    //开启事务 设置隔离级别:REQUIRES_NEVER
    transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);

    transactionTemplate.execute((TransactionCallback<Void>) status -> {
      try {
         deleteOne();
         updateTwo();
        return null;
      } catch (Exception e) {
        log.error("---------error---编程式事务隔离级别:{}",e);
        status.setRollbackOnly();
        throw e;
      }
    });

}

注意

❗ 注解式事务,隔离级别是设置到方法的注解上的,是线程安全的。

❗ 但是编程式事务的隔离级别是settransactionTemplate 的属性上的,如果我们项目共用一个transactionTemplate岂不是又线程安全问题

❗ 经过我测试确实是存在线程安全问题

java 复制代码
private int propagationBehavior = PROPAGATION_REQUIRED;
//set的时候也是直接 set 到属性上,也没有任何线程安全的控制。
public final void setPropagationBehavior(int propagationBehavior) {
    if (!constants.getValues(PREFIX_PROPAGATION).contains(propagationBehavior)) {
       throw new IllegalArgumentException("Only values of propagation constants allowed");
    }
    this.propagationBehavior = propagationBehavior;
}

只要transactionTemplateset 的属性,我们都要保证不影响其他线程,如要修改,请重新创建实例。

事务传播机制处理核心代码

Spring的事务传播行为由Propagation枚举定义,其核心实现位于AbstractPlatformTransactionManager及其子类(如DataSourceTransactionManager)。关键逻辑在获取事务事务管理器创建事务时,根据当前是否存在事务决定行为:

获取事务

核心代码AbstractPlatformTransactionManager.java 中的 getTransaction() 方法:

java 复制代码
//AbstractPlatformTransactionManager.java
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
       throws TransactionException {

    // 1. 使用默认配,当事务定时为空时
    // Use defaults if no transaction definition given. 
    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
    // 2. 获取当前事务    
    Object transaction = doGetTransaction();
    boolean debugEnabled = logger.isDebugEnabled();
    // 3.存在现有事务 执行handleExistingTransaction 方法
    if (isExistingTransaction(transaction)) {
       // 存在现有事务,检查事务的传播行为 
       // Existing transaction found -> check propagation behavior to find out how to behave.
       return handleExistingTransaction(def, transaction, debugEnabled);
    }
    // 4.1 不存在事务的处理逻辑
    // 检车事务是否超时
    // Check definition settings for new transaction.
    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
       throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
    }
    // 4.2 事务隔离级别为:MANDATORY,强制有事务,没有事务则报异常
    // No existing transaction found -> check propagation behavior to find out how to proceed.
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
       throw new IllegalTransactionStateException(
             "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    // 4.3 REQUIRED、REQUIRES_NEW、NESTED 处理逻辑
    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
          def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
          def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
       SuspendedResourcesHolder suspendedResources = suspend(null);
       if (debugEnabled) {
          logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
       }
       try {
          return startTransaction(def, transaction, debugEnabled, suspendedResources);
       }
       catch (RuntimeException | Error ex) {
          resume(null, suspendedResources);
          throw ex;
       }
    }
    // 4.4 ISOLATION_DEFAULT 处理逻辑
    else {
       // Create "empty" transaction: no actual transaction, but potentially synchronization.
       if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
          logger.warn("Custom isolation level specified but no actual transaction initiated; " +
                "isolation level will effectively be ignored: " + def);
       }
       boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
       return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
}

事务已经存在

上面一节说了存在现有事务就执行 handleExistingTransaction,下面看看handlExistingTransaction方法吧

java 复制代码
// 处理有事务的逻辑:
private TransactionStatus handleExistingTransaction(
       TransactionDefinition definition, Object transaction, boolean debugEnabled)
       throws TransactionException {
     
    //1. NEVER 直接抛异常
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
       throw new IllegalTransactionStateException(
             "Existing transaction found for transaction marked with propagation 'never'");
    }
    // 2.NOT_SUPPORTED 处理逻辑
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
       if (debugEnabled) {
          logger.debug("Suspending current transaction");
       }
       Object suspendedResources = suspend(transaction);
       boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
       return prepareTransactionStatus(
             definition, null, false, newSynchronization, debugEnabled, suspendedResources);
    }
    // REQUIRES_NEW 调用 suspend 然后 调用 startTransaction 开启事务
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
       if (debugEnabled) {
          logger.debug("Suspending current transaction, creating new transaction with name [" +
                definition.getName() + "]");
       }
       SuspendedResourcesHolder suspendedResources = suspend(transaction);
       try {
          return startTransaction(definition, transaction, debugEnabled, suspendedResources);
       }
       catch (RuntimeException | Error beginEx) {
          resumeAfterBeginException(transaction, suspendedResources, beginEx);
          throw beginEx;
       }
    }
    // NESTED 处理逻辑
    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
       if (!isNestedTransactionAllowed()) {
          throw new NestedTransactionNotSupportedException(
                "Transaction manager does not allow nested transactions by default - " +
                "specify 'nestedTransactionAllowed' property with value 'true'");
       }
       if (debugEnabled) {
          logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
       }
       if (useSavepointForNestedTransaction()) {
          // Create savepoint within existing Spring-managed transaction,
          // through the SavepointManager API implemented by TransactionStatus.
          // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
          DefaultTransactionStatus status =
                prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
          status.createAndHoldSavepoint();
          return status;
       }
       else {
          // Nested transaction through nested begin and commit/rollback calls.
          // Usually only for JTA: Spring synchronization might get activated here
          // in case of a pre-existing JTA transaction.
          return startTransaction(definition, transaction, debugEnabled, null);
       }
    }
    
    // 有事务的情况SUPPORTS、REQUIRED 处理方式相同
    // Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
    if (debugEnabled) {
       logger.debug("Participating in existing transaction");
    }
    if (isValidateExistingTransaction()) {
       if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
          Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
          if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
             Constants isoConstants = DefaultTransactionDefinition.constants;
             throw new IllegalTransactionStateException("Participating transaction with definition [" +
                   definition + "] specifies isolation level which is incompatible with existing transaction: " +
                   (currentIsolationLevel != null ?
                         isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
                         "(unknown)"));
          }
       }
       if (!definition.isReadOnly()) {
          if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
             throw new IllegalTransactionStateException("Participating transaction with definition [" +
                   definition + "] is not marked as read-only but existing transaction is");
          }
       }
    }
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

这里就不继续深入分析了。

总结

简单分析了一下,处理事物传播范围的部分源码。同时给出了,在编程式事务中,如需修改transanctionTemplate属性问题应该怎么实现,才能避免属性的并发问题。

相关推荐
you来有去23 分钟前
记录一下在k3s快速创建gitlab
java·kubernetes·gitlab
雷神乐乐42 分钟前
分布式主键生成服务
java·服务器·微服务·主键生成·数据库乐观锁
magic 2451 小时前
深入理解Java多线程编程:从基础到高级应用
java·开发语言·线程
刘小炮吖i1 小时前
【面试】Java 之 String 系列 -- String 为什么不可变?
java·开发语言·面试
小杨4041 小时前
springboot框架四个基础核心三(actuator)
spring boot·后端·架构
sinat_319868071 小时前
Spring Boot2.0之十 使用自定义注解、Json序列化器实现自动转换字典类型字段
spring boot·后端·json
吴晓斌kobe1 小时前
Java中的缓存技术:Guava Cache vs Caffeine vs Redis
java·redis·缓存·guava·caffeine
seabirdssss1 小时前
华为机试牛客刷题之HJ75 公共子串计算
java·算法·华为
葡萄_成熟时_2 小时前
JavaWeb后端基础(2)
java·网络·web