小心,这12种场景Spring事务会失效!

作者:微信小助手

发布时间:2021-10-21T12:05:51

前言

对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了。

在某些业务场景下,如果一个请求中,需要同时写入多张表的数据。为了保证操作的原子性(要么同时成功,要么同时失败),避免数据不一致的情况,我们一般都会用到spring事务。

确实,spring事务用起来贼爽,就用一个简单的注解:@Transactional,就能轻松搞定事务。我猜大部分小伙伴也是这样用的,而且一直用一直爽。

但如果你使用不当,它也会坑你于无形。

今天我们就一起聊聊,事务失效的一些场景,说不定你已经中招了。不信,让我们一起看看。

图片

一 事务不生效

1.访问权限问题

众所周知,java的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。

但如果我们在开发过程中,把有某些事务方法,定义了错误的访问权限,就会导致事务功能出问题,例如:

@Service
public class UserService {
    
    @Transactional
    private void add(UserModel userModel) {
         saveData(userModel);
         updateData(userModel);
    }
}

我们可以看到add方法的访问权限被定义成了 private,这样会导致事务失效,spring要求被代理方法必须是 public的。

说白了,在 AbstractFallbackTransactionAttributeSource类的 computeTransactionAttribute方法中有个判断,如果目标方法不是public,则 TransactionAttribute返回null,即不支持事务。

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
  // Don't allow no-public methods as required.
  if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    return null;
  }

  // The method may be on an interface, but we need attributes from the target class.
  // If the target class is null, the method will be unchanged.
  Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

  // First try is the method in the target class.
  TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
  if (txAttr != null) {
    return txAttr;
  }

  // Second try is the transaction attribute on the target class.
  txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
  if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
    return txAttr;
  }

  if (specificMethod != method) {
    // Fallback is to look at the original method.
    txAttr = findTransactionAttribute(method);
    if (txAttr != null) {
      return txAttr;
    }
    // Last fallback is the class of the original method.
    txAttr = findTransactionAttribute(method.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
      return txAttr;
    }
  }
  return null;
}

也就是说,如果我们自定义的事务方法(即目标方法),它的访问权限不是 public,而是private、default或protected的话,spring则不会提供事务功能。

2. 方法用final修饰

有时候,某个方法不想被子类重新,这时可以将该方法定义成final的。普通方法这样定义是没问题的,但如果将事务方法定义成final,例如:

@Service
public class UserService {

  @Transactional
  public final void add(UserModel userModel){
      saveData(userModel);
      updateData(userModel);
  }
}

我们可以看到add方法被定义成了 final的,这样会导致事务失效。

为什么?

如果你看过spring事务的源码,可能会知道spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。

但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。

注意:如果某个方法是static的,同样无法通过动态代理,变成事务方法。

3.方法内部调用

有时候我们需要在某个Service类的某个方法中,调用另外一个事务方法,比如:

@Service
public class UserService {

  @Autowired
  private UserMapper userMapper;


  public void add(UserModel userModel) {
      userMapper.insertUser(userModel);
      updateStatus(userModel);
  }

  @Transactional
  public void updateStatus(UserModel userModel) {
      doSameThing();
  }
}

我们看到在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。

由此可见,在同一个类中的方法直接内部调用,会导致事务失效。

那么问题来了,如果有些场景,确实想在同一个类的某个方法中,调用它自己的另外一个方法,该怎么办呢?

3.1 新加一个Service方法

这个方法非常简单,只需要新加一个Service方法,把@Transactional注解加到新Service方法上,把需要事务执行的代码移到新方法中。具体代码如下:

@Servcie
public class ServiceA {
 @Autowired
 prvate ServiceB serviceB;

 public void save(User user) {
       queryData1();
       queryData2();
       serviceB.doSave(user);
 }
}

@Servcie
public class ServiceB {