本文向大家介绍spring声明式事务使用过程中需要注意的地方。
事务特性
1. 原子性(Atomicity)
事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
2. 一致性(Consistency)
一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
3. 隔离性(Isolation)
可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
4. 持久性(Durability)
一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
spring声明式事务
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
声明式事务使用过程中的注意点
1.必须通过代理类调用目标方法
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public void insertFirst() {
insertSecond();
}
@Transactional
@Override
public void insertSecond() {
User u1 = new User();
u1.setName("Mike");
userMapper.insertSelective(u1);
int a = 1 / 0;
User u2 = new User();
u2.setName("Jerry");
userMapper.insertSelective(u2);
}
}
单元测试执行上面的insertFirst方法,发生异常,发现数据库插入了一条name为“Mike”的记录,说明事务并没有生效。
修改代码,清空数据库数据
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Autowired
private UserService userService;
@Override
public void insertFirst() {
userService.insertSecond();
}
@Transactional
@Override
public void insertSecond() {
User u1 = new User();
u1.setName("Mike");
userMapper.insertSelective(u1);
int a = 1 / 0;
User u2 = new User();
u2.setName("Jerry");
userMapper.insertSelective(u2);
}
}
此时单元测试执行上面的insertFirst方法,发生异常,数据库没有插入数据,事务发生了回滚
可以看到此时userService走了代理,而直接本类调用的话不会走代理,方法不会增强
2.捕获到了异常才会回滚
修改代码
@Transactional
@Override
public void insertSecond() {
try {
User u1 = new User();
u1.setName("Mike");
userMapper.insertSelective(u1);
int a = 1 / 0;
User u2 = new User();
u2.setName("Jerry");
userMapper.insertSelective(u2);
} catch (Exception e) {
e.printStackTrace();
}
}
单元测试执行,发生异常,发现数据库插入了一条name为“Mike”的记录,说明事务并没有生效。
查看TransactionAspectSupport里面的invokeWithinTransaction方法
可以看到捕获到了异常才会进行后续的处理
3.默认情况下,出现RuntimeException或者Error才会触发事务回滚
修改代码,清空数据库数据
@Transactional
@Override
public void insertSecond() throws FileNotFoundException {
User u1 = new User();
u1.setName("Mike");
userMapper.insertSelective(u1);
new FileInputStream("test.txt");
User u2 = new User();
u2.setName("Jerry");
userMapper.insertSelective(u2);
}
单元测试执行,抛出FileNotFoundException异常,发现数据库插入了一条name为“Mike”的记录,说明事务并没有生效。
可以在注解中声明,期望遇到所有的 Exception 都回滚事务@Transactional(rollbackFor = Exception.class)
4.确定事务的传播配置是否合理
修改代码,清空数据
@Transactional
@Override
public void insertFirst() {
User u1 = new User();
u1.setName("Tom");
userMapper.insertSelective(u1);
try {
userService.insertSecond();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional
@Override
public void insertSecond() {
User u1 = new User();
u1.setName("Mike");
userMapper.insertSelective(u1);
int a = 1/0;
User u2 = new User();
u2.setName("Jerry");
userMapper.insertSelective(u2);
}
现在希望子方法insertSecond执行异常后回滚,不影响主方法insertFirst的执行
单元测试执行,抛出org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only异常,主方法发生了回滚
因为在同一个事务里子方法标记事务为回滚了,主方法没有异常,但是事务无法正常提交,所以最终回滚了
修改子方法insertSecond的传播方式,改为@Transactional(propagation = Propagation.REQUIRES_NEW)
再次执行单元测试
可以发现,name为“Tom”的数据正常插入,但是insertSecond事务回滚了,符合预期