一、类未被spring管理

// @Service
public class AServiceImpl implements AService {
 
    @Transactional
    public void updateA(A a) {
        // update a
    }
    
}

将 @Service注解注释后,该类就不受 Spring容器管理,导致事务失效!这是因为 Spring事务是由 AOP机制实现的,AOP机制的本质就是动态代理,从 Spring IOC容器获取 bean时,Spring会为目标类创建代理,从而支持事务。

二、事务方法不是 public

spring 源码:

public abstract class AbstractFallbackTransactionAttributeSource
  implements TransactionAttributeSource, EmbeddedValueResolverAware {

  // 获取事务属性
  @Override
  @Nullable
  public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
      // 部分代码省略

      if (cached != null) {
        // 部分代码省略
      }
      else {
        // 调用 计算事务属性 方法
        // We need to work it out.
        TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
        // 部分代码省略
        return txAttr;
      }
  }

     // 计算事务属性
    @Nullable
    protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
        // 按照配置,不允许使用非公共方法。
        // Don't allow non-public methods, as configured.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        }

        // 部分代码省略
        return null;
    }
}

我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效。从spring源码中可以看出被代理方法必须是public的。在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。

三、方法用final修饰

@Service
public class AService {
    @Transactional
    public final void add(Model model){
        saveData(model);
        int i = 1/0;
    }
}

spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,添加事务功能。

四、方法内部调用事务方法

@Service
public class UserServiceImpl implements UserService {


  @Override
  public void addUser(User user){
    this.addUser(user);
  }

  @Transactional(rollbackFor = Exception.class)
  public void addUser (User user){
    userMapper.save(user);
    int i = 1/0;
  }
}

数据保存成功

为什么事务没有回滚?

事务是通过Spring AOP代理来实现的,而 addUser()内部事务方法其实是this对象调用的,而不是通过AOP代理来调用的,因此事务失效。

五、异常被 catch

@Service
public class UserServiceImpl implements UserService {

    @Transactional
    @Override
    public void addUser(User user) {
       try{
         userMapper.save(user);
         // 模拟异常,数据库的记录应该回滚
         throw new RuntimeException();
       } catch (RuntimeException e) {
         
       }
    }
}

数据保存成功

为什么事务没有回滚?

我们来看看 Spring的官方核心源码:

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
  @Nullable
  protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
                                           final InvocationCallback invocation) throws Throwable {
      // 省略部分代码

      if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
          Object retVal;
          try {
              // This is an around advice: Invoke the next interceptor in the chain.
              // This will normally result in a target object being invoked.
              retVal = invocation.proceedWithInvocation();
          } catch (Throwable ex) {
              // target invocation exception
              // 回滚事务 是在 spring 的catch中处理,也就是说,如果Spring catch不到对应的异常,就不会进入回滚事务的逻辑
              completeTransactionAfterThrowing(txInfo, ex);
              throw ex;
          } finally {
              cleanupTransactionInfo(txInfo);
          }
          // 省略部分代码

          // 提交事务
          commitTransactionAfterReturning(txInfo);
          return retVal;
      }
  }
  /**
   * Handle a throwable, completing the transaction.
   * We may commit or roll back, depending on the configuration.
   * @param txInfo information about the current transaction
   * @param ex throwable encountered
   */
  protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
       // 省略部分代码
      if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
        try {
            // 回滚事务
          txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
        }
        catch (TransactionSystemException ex2) {
          logger.error("Application exception overridden by rollback exception", ex);
          ex2.initApplicationException(ex);
          throw ex2;
        }
        catch (RuntimeException | Error ex2) {
          logger.error("Application exception overridden by rollback exception", ex);
          throw ex2;
        }
      }
        // 省略部分代码
    }
  }
}

通过源码,我们可以很清晰的看出,在 invokeWithinTransaction() 方法中,当 Spring catch到 Throwable异常,就会调用 completeTransactionAfterThrowing()方法进行事务回滚的逻辑。但是,在 UserServiceImpl类的业务代码中直接把异常catch住了,Spring自然就 catch不到异常,因此事务回滚的逻辑就不会执行,事务就失效了。

六、表不支持事务

Spring 事务是业务层的事务,其底层还是依赖于数据库本身的事务支持。比如 MySQL 数据库,MyISAM 引擎不支持事务而 InnoDB 引擎支持事务。所以,开发中如果需要使用事务,一定要确保你选择的数据库支持事务。

七、rollbackFor属性配置错误

@Service
public class UserServiceImpl implements UserService {

  @Override
  public void addUser(User user){
    // 调用内部方法
    this.doAddUser(user);
  }

  @Transactional(rollbackFor = Error.class)
  public void addUserInfo (User user){
    userMapper.save(user);
    // 模拟异常,数据库的记录应该回滚
    throw new RuntimeException();
  }
}

数据保存成功

为什么事务没有回滚?

我们依旧来看看 Spring的官方核心源码:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
  /**
   * Defines zero (0) or more exception classes, which must be subclasses of Throwable, indicating which exception types must cause a transaction rollback.
   * By default, a transaction will be rolled back on RuntimeException and Error but not on checked exceptions (business exceptions). See org.springframework.transaction.interceptor.DefaultTransactionAttribute.rollbackOn(Throwable) for a detailed explanation.
   * This is the preferred way to construct a rollback rule (in contrast to rollbackForClassName), matching the exception type, its subclasses, and its nested classes. See the class-level javadocs for further details on rollback rule semantics and warnings regarding possible unintentional matches.
   * @return
   */
  //  rollbackFor的异常必须是 Throwable的子类
  Class<? extends Throwable>[] rollbackFor() default {};
}

通过 Transactional注解源码,我们可以发现 rollbackFor属性指定的异常必须是 Throwable及其子类,并且在默认情况下,Spring对 RuntimeException 和 Error 两种异常会自动回滚事务,也就是说,如果业务抛出来的异常是 RuntimeException 和 Error类型,可以不需要通过 rollbackFor属性指定,Spring 默认会识别处理。代码中抛出的异常是 Exception(throw new Exception()),而 Exception 和 Error没有任何关系,也就是说,事务需要捕获到 Error才会回滚,可是抛出一个和 Error不相关的 Exception异常,因此事务自然无效,不能回滚。

八、错误的事物传播特性

@Service
public class UserService {
    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel) {
        saveData(userModel);
        updateData(userModel);
    }
}

spring目前支持7种传播特性:

  • REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。

  • SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。

  • MANDATORY 如果当前上下文中存在事务,否则抛出异常。

  • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

  • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。

  • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。

  • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。

目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。

九、多线程调用

@Slf4j
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        new Thread(() -> {
            roleService.save();
        }).start();
    }
}

@Service
public class RoleService {
    @Transactional
    public void save() {
        // 保存数据
    }
}

我们可以看到事务方法add中,调用了事务方法save,但是事务方法save是在另外一个线程中调用的。这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果save方法中抛了异常,add方法也回滚是不可能的。如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。

private static final ThreadLocal<Map<Object, Object>> resources =new NamedThreadLocal<>("Transactional resources");

我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

十、未开启事务

事务没有生效,原因有很多,但没有开启事务,这个原因极其容易被忽略。

如果你使用的是springboot项目,那么springboot通过DataSourceTransactionManagerAutoConfiguration类,已经默默的帮你开启了事务。

你所要做的事情很简单,只需要配置spring.datasource相关参数即可。

但如果你使用的还是传统的spring项目,则需要在applicationContext.xml文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。

具体配置如下信息:

<!-- 配置事务管理器 --> 
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> <property name="dataSource" ref="dataSource"></property> 
</bean> 
<tx:advice id="advice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/></tx:attributes> 
</tx:advice> 
<!-- 用切点把事务切进去 --> 
<aop:config> <aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/> <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
</aop:config> 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐