@Transitional注解用于完成声明式事务操作,默认当发生RuntimeException时会对当前事务操作进行回滚,但其使用不当就会造成失效;(注:若未使用该注解,或者未使用手动事务,则在进行数据库更新操作之后才抛出异常的话,该更新操作是不会被回滚的,即Spring默认没有异常回滚操作)
场景一:抛出异常非RuntimeException
@Transitional
public void insert() throws Exception {
// insert...
throw new Exception();
}
解决方案
手动设置事务回滚捕获的异常
@Transactional(rollBackFor = Exception.class)
public void insert() throws Exception {
// insert...
throw new Exception();
}
场景二:方法类中自调用
被@Transactional注解的方法在类中自调用,是通过this对象进行的,而不是通过Cglib的代理对象调用,因此并没有被Spring事务处理器管理,从而导致事务失效。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
publicvoid test1() {
insertUser();
}
@Transactional(rollbackFor = Exception.class)
public void insertUser() {
User user = new User();
userMapper.insert(user);
}
}
解决方案:
1、将@Transactional注解到外层方法
2、如果业务避不开自调用,那么就自己注入自己,再进行调用;
方案一:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(rollbackFor = Exception.class)
publicvoid test1() {
insertUser();
}
public void insertUser() {
User user = new User();
userMapper.insert(user);
}
}
方案二:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserService userService;
publicvoid test1() {
userService.insertUser();
}
@Transactional(rollbackFor = Exception.class)
public void insertUser() {
User user = new User();
userMapper.insert(user);
}
}
场景三:异常被捕获
异常被捕获后,没用继续抛出,导致Spring事务管理器没有捕捉到异常,其认为是正常业务逻辑,因而不会回滚。
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(rollbackFor = Exception.class)
public void test1() {
try {
insertUser();
} catch (Exception e) {
System.out.println("测试解决异常");
}
}
public void insertUser() throws Exception {
User user = new User();
user.setId(UUID.randomUUID().toString().replaceAll("-", ""));
user.setName(UUID.randomUUID().toString().replaceAll("-", ""));
userMapper.insert(user);
throw new Exception("test Exception");
}
}
解决方案
捕获解决后继续抛出。
@Transactional(rollbackFor = Exception.class)
public void test1() throws Exception {
try {
insertUser();
} catch (Exception e) {
System.out.println("测试解决异常");
throw e;
}
}
场景四:上游方法未使用事务,但是报错
上游方法在调用下游方法时,上游方法未使用事务处理,而是在下游使用的事务,如果上游方法在调用事务方法之后报错了,则不会使下游事务方法回滚。
@Service
public class TestService {
@Autowired
private UserService userService;
public void test() {
userService.test1();
int i = 1 / 0; // 不会回滚
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private TestService testService;
public void test1() {
insertUser();
}
public void insertUser() {
User user = new User();
user.setId(UUID.randomUUID().toString().replaceAll("-", ""));
user.setName(UUID.randomUUID().toString().replaceAll("-", ""));
userMapper.insert(user);
}
}
解决方案
上游方法增加事务注解
@Service
public class TestService {
@Autowired
private UserService userService;
@Transactional
public void test() {
userService.test1();
int i = 1 / 0;
}
}
其他场景
1、数据库引擎不支持事务,例如Mysql的MyIsam
2、@Transactional注解方法所在类未被Spring容器管理(未使用@Service等注解,取而代之使用new对象调用方法);
3、事务方法中存在异步业务,异步处理部分发生异常,不会使得主方法回滚。
多线程事务处理可以参考文章:Spring在多线程环境下如何确保事务一致性https://blog.csdn.net/m0_53157173/article/details/127423286
4、更多场景可查看:啪!啪!@Transactional 注解的12种失效场景,这坑我踩个遍-腾讯云开发者社区-腾讯云 (tencent.com)
某些事务失效还与Spring事务传播行为有关,这里不再展开,感兴趣小伙伴可以结合案例自行研究