乐趣区

关于java:spring事务的这10种坑你稍不注意可能就会踩中

​关注“苏三说技术”,回复:开发手册、工夫治理 有惊喜。

对于从事 java 开发工作的同学来说,spring 的事务必定再相熟不过了。在某些业务场景下,如果同时有多张表的写入操作,为了保障操作的原子性(要么同时胜利,要么同时失败)防止数据不统一的状况,咱们个别都会应用 spring 事务。

没错,spring 事务大多数状况下,能够满足咱们的业务需要。然而明天我要通知大家的是,它有很多坑,稍不留神事务就会生效。

不信,咱们一起看看。

1. 谬误的拜访权限

@Service
public class UserService {
​
    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    private void add(UserModel userModel) {userMapper.insertUser(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;
  }

2. 办法被定义成 final 的

@Service
public class UserService {
​
    @Autowired
    private UserMapper userMapper;
​
    @Transactional
    public final void add(UserModel userModel) {userMapper.insertUser(userModel);
    }
}

咱们能够看到 add 办法被定义成了 final 的,这样会导致 spring aop 生成的代理对象不能复写该办法,而让事务生效。

3. 办法外部调用

@Service
public class UserService {
​
    @Autowired
    private UserMapper userMapper;
​
    @Transactional
    public void add(UserModel userModel) {userMapper.insertUser(userModel);
        updateStatus(userModel);
    }
​
    @Transactional
    public void updateStatus(UserModel userModel) {// doSameThing();
    }
}

咱们看到在事务办法 add 中,间接调用事务办法 updateStatus。从后面介绍的内容能够晓得,updateStatus 办法领有事务的能力是因为 spring aop 生成代理了对象,然而这种办法间接调用了 this 对象的办法,所以 updateStatus 办法不会生成事务。

4. 以后实体没有被 spring 治理

//@Service
public class UserService {
​
    @Autowired
    private UserMapper userMapper;
​
    @Transactional
    public void add(UserModel userModel) {userMapper.insertUser(userModel);
    }    
}

咱们能够看到 UserService 类没有定义 @Service 注解,即没有交给 spring 治理 bean 实例,所以它的 add 办法也不会生成事务。

5. 谬误的 spring 事务流传个性

@Service
public class UserService {
​
    @Autowired
    private UserMapper userMapper;
​
    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel) {userMapper.insertUser(userModel);
    }
​
}

咱们能够看到 add 办法的事务流传个性定义成了 Propagation.NEVER,这种类型的流传个性不反对事务,如果有事务则会抛异样。只有这三种流传个性才会创立新事务:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。

6. 数据库不反对事务

msql8 以前的版本数据库引擎是反对 myslam 和 innerdb 的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成 myslam,这样能够晋升查问效率。然而,要千万记得一件事件,myslam 只反对表锁,并且不反对事务。所以,对这类表的写入操作事务会生效。

7. 本人吞掉了异样

@Slf4j
@Service
public class UserService {
​
    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    public void add(UserModel userModel) {
        try {userMapper.insertUser(userModel);
        } catch (Exception e) {log.error(e.getMessage(), e);
        }
    }
}

这种状况下事务不会回滚,因为开发者本人捕捉了异样,又没有抛出。事务的 AOP 无奈捕捉异样,导致即便呈现了异样,事务也不会回滚。

8. 抛出的异样不正确

@Slf4j
@Service
public class UserService {
​
    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    public void add(UserModel userModel) throws Exception {
        try {userMapper.insertUser(userModel);
        } catch (Exception e) {log.error(e.getMessage(), e);
            throw new Exception(e);
        }
    }
​
}

这种状况下,开发人员本人捕捉了异样,又抛出了异样:Exception,事务也不会回滚。因为 spring 事务,默认状况下只会回滚 RuntimeException(运行时异样)和 Error(谬误),不会回滚 Exception。

9. 多线程调用

@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.doOtherThing();
        }).start();}
}
​
@Service
public class RoleService {
​
    @Transactional
    public void doOtherThing() {System.out.println("保留 role 表数据");
    }
}

咱们能够看到事务办法 add 中,调用了事务办法 doOtherThing,然而事务办法 doOtherThing 是在另外一个线程中调用的,这样会导致两个事务办法不在同一个线程中,获取到的数据库连贯不一样,从而是两个不同的事务。如果想 doOtherThing 办法中抛了异样,add 办法也回滚是不可能的。

如果看过 spring 事务源码的敌人,可能会晓得 spring 的事务是通过数据库连贯来实现的。以后线程中保留了一个 map,key 是数据源,value 是数据库连贯。

private static final ThreadLocal<Map<Object, Object>> resources =

  new NamedThreadLocal<>("Transactional resources");


咱们说的同一个事务,其实是指同一个数据库连贯,只有领有同一个数据库连贯能力同时提交和回滚。如果在不同的线程,拿到的数据库连贯必定是不一样的,所以是不同的事务。

10. 嵌套事务多回滚了 **

public class UserService {
​
    @Autowired
    private UserMapper userMapper;
​
    @Autowired
    private RoleService roleService;
​
    @Transactional
    public void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);
        roleService.doOtherThing();}
}
​
@Service
public class RoleService {
​
    @Transactional(propagation = Propagation.NESTED)
    public void doOtherThing() {System.out.println("保留 role 表数据");
    }
}

这种状况应用了嵌套的内部事务,本来是心愿调用 roleService.doOtherThing 办法时,如果呈现了异样,只回滚 doOtherThing 办法里的内容,不回滚 userMapper.insertUser 里的内容,即回滚保留点。。但事实是,insertUser 也回滚了。

why?

因为 doOtherThing 办法呈现了异样,没有手动捕捉,会持续往上抛,到外层 add 办法的代理办法中捕捉了异样。所以,这种状况是间接回滚了整个事务,不只回滚单个保留点。

怎么样能力只回滚保留点呢?

@Slf4j
@Service
public class UserService {
​
    @Autowired
    private UserMapper userMapper;
​
    @Autowired
    private RoleService roleService;
​
    @Transactional
    public void add(UserModel userModel) throws Exception {
​
        userMapper.insertUser(userModel);
        try {roleService.doOtherThing();
        } catch (Exception e) {log.error(e.getMessage(), e);
        }
    }
​
}

在代码中手动把外部嵌套事务放在 try/catch 中,并且不持续往抛异样。

介绍到这里,你会发现 spring 事务的坑还是挺多的。如果对这一块比拟感兴趣的敌人,请关注一下我的公众账号:苏三说技术,下次我将给大家解说 spring 事务的源码。

退出移动版