【JavaEE】Spring 事务(1)
1. 为什么要使用事务
比如跟钱相关的两个操作:
这就是一个事务,捆在一起的一组行为,就是事务
但是,如果没有事务呢,则两个操作逻辑上是分开的:
- 第一个操作成功,第二个操作失败,则小马直接亏了100!
2. Spring中事务的实现
Spring中的事务操作主要分为两类:
- 编程式事务(原生方式去写代码操作事务)
- 声明式事务(利用注解,“约定规则”去自动开启和提交事务)
2.1 事务针对哪些操作
事务一般针对的是
事务的概念适用于需要保证一系列操作的原子性和一致性的任何场景
- 而其中,被持久化的数据,被传播的数据…等操作,都具有 “持久性影响” 的作用,所以要通过事务来控制其影响不要太糟糕
- 而一些操作,比如打印,都打印到控制台了,不会回滚的,也没有必要回滚,例如查看执行日志…
- 至于其他的不可见的操作,又没有持久化,是没有影响力的,程序出异常后,这些数据也销毁了~
2.2 MySQL 事务使用
--- 开启事务
start transaction;
--- transaction就是事务的意思
--- 提交事务
commit;
--- 回滚事务
rollback;
三个重要的操作:
- 开启事务
- 提交事务
- 回滚事务
2.3 Spring 编程式事务(手动挡)
与MySQL操作事务类似:
- 开启事务(获取一个事务/创建一个事务并获取)
- 提交事务
- 回滚事务
反正就是,麻烦,难记,不简洁,容易出错(难写)
2.4 Spring 声明式事务(自动挡)
声明式事务的实现很简单
- 只需要在需要的类或者方法上添加 @Transactional 注解 就可以实现了
具体规则/作用范围是:
- 加在类上,内部的所有非静态public方法都相当于加了 @Transactional 注解
- 加在非静态public方法上,这个方法就是一个事务
- 所在的类,必须被五大类注解修饰,这跟其事务的实现有关
- 而且有了五大类注解,Spring开发才能进行呀~
代码实现:
@Service
@Transactional
public class Test {
@Autowired
private Mapper mapper;
public int save(User user) {
mapper.save(user);
}
}
@RequestMapping("/save")
@Transactional
public Object save(User user) {
int result = userService.save(user);
return result;
}
2.5 小疑问(@Transactional注解原理)
2.5.1 @Transactional注解原理
- 这个行为,可能你也意识到了,其实就是AOP,对@Transactional注解下的代码,进行统一的处理
- 当然,对于不同的事务/复杂事务,处理可能不同~
- 这个在执行日志中也能看到,可以平时观察观察~
2.5.2 为什么必须被五大类注解修饰
其实就是因为
@Transactional注解是基于Spring AOP的,而Spring AOP则通过JDK的或者CGLib的动态代理来实现AOP
2.5.3 为什么@Transactional不支持static方法
其实就是因为
无论JDK还是CGlib都无法对静态方法提供代理。原因在于静态方法是类级别的,调用需要知道类信息,而类信息在编译器就已经知道了,并 不支持在运行期的动态绑定
3. 实践
3.1 创建项目
为了方便,我就直接使用之前mybatis项目里写过的代码了
- 因为我们目前侧重学习的点是在事务的实现!
目录结构:
3.2 编写代码
加@Transactional:
3.3 测试
3.4 注意事项
@Transactional 在异常被 try{}catch(){}
捕获的情况下,不会进行事务自动回滚,这也很好理解,因为 try{}catch(){}
后,后面的代码可以继续运行,这个异常是被我们写的 try{}catch(){}
抢走处理了,注解是捕获不到的~
3.4.1 事务不会自动回滚解决方案1:重新抛出
效果:
但是这不太美观,“优雅”,过于暴力
3.4.2 事务不会自动回滚解决方案2:手动回滚
TransactionAspectSupport.currentTransactionStatus()
可以得到当前的事务,然后设置回滚方法setRollbackOnly
就可以实现将当前事务的回滚了
- 跟切面有关=>aop
效果:
这种方式就比较“优雅”了~