( 三十 )、 Spring 事务失效场景
( 三十 )、 Spring 事务失效场景
1、简介
在某些业务场景下,如果一个请求中,需要同时写入多张表的数据。为了保证操作的原子性(要么同时成功,要么同时失败),避免数据不一致的情况,我们一般都会用到事务。Spring 就用一个简单的注解:@Transactional 就能解决事务问题。但是使用不当也会使事务失效。下面我们一一说明。
2、失效场景介绍:
2.1、方法上未添加 @Transactional 注解,不会回滚异常:
@Service
public class TestTransactionServiceImpl implements ITransactionTestService {
@Autowired
private IUserDao userDao;
@Override
public Integer testA() {
int i = userDao.saveUser(new MyUser("张三", 23));
System.out.println(1 / 0);
return i;
}
}
如果方法上面没有添加 @Transactional 注解, 方法在执行过程中, 不会回滚异常:
2.2、Controller 调用 A 方法, 在A 中调用 B 方法, A 方法 未添加 注解 @Transactional,在 B上添加注解依然会失效
@Service
public class TestTransactionServiceImpl implements ITransactionTestService {
@Autowired
private IUserDao userDao;
@Override
public Integer testA() {
Integer i = testB();
System.out.println(1 / 0);
return i;
}
@Transactional
public Integer testB(){
int i = userDao.saveUser(new MyUser("张三", 23));
return i;
}
}
如果在 方法A 上添加了注解, 调用同类中的B方法, B 未添加注解,这种情况会回滚, 如下:
@Service
public class TestTransactionServiceImpl implements ITransactionTestService {
@Autowired
private IUserDao userDao;
@Override
@Transactional
public Integer testA() {
Integer i = this.testB();
System.out.println(1 / 0);
return i;
}
public Integer testB(){
int i = userDao.saveUser(new MyUser("张三", 23));
return i;
}
}
如果在 方法A 上添加了注解, 调用同类中的B方法, B 未添加注解,并且私有化B, 这种情况会回滚, 如下:
@Service
public class TestTransactionServiceImpl implements ITransactionTestService {
@Autowired
private IUserDao userDao;
@Override
@Transactional
public Integer testA() {
Integer i = this.testB();
return i;
}
private Integer testB(){
int i = userDao.saveUser(new MyUser("张三", 23));
System.out.println(1 / 0);
return i;
}
}
2.3、多线程调用导致事务失效
@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表数据");
}
}
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。
2.4、表不支持事务
数据库引擎是 myisam, 是不支持事务的。
2.5、错误的传播特性
其实,我们在使用@Transactional
注解时,是可以指定propagation
参数的。
该参数的作用是指定事务的传播特性,spring目前支持7种传播特性:
REQUIRED
如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。SUPPORTS
如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。MANDATORY
如果当前上下文中存在事务,否则抛出异常。REQUIRES_NEW
每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。NOT_SUPPORTED
如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。NEVER
如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。NESTED
如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
如果我们在手动设置propagation参数的时候,把传播特性设置错了,比如:
@Service
public class UserService {
@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
2.6、自己吞了异常
事务不会回滚,最常见的问题是:开发者在代码中手动try...catch了异常。比如:
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
2.7、手动抛了别的异常
即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚
public class UserService {
@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}
上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。
因为spring事务,默认情况下只会回滚 RuntimeException
(运行时异常)和Error
(错误),对于普通的Exception(非运行时异常),它不会回滚。