之前就在后台的dao层注意到,基本对数据库进行删除和编辑的操作都会加上@Transactional注解(如下),当时只是大概明白是进行事务回滚的。但是也不太清楚具体的执行过程,以前具体使用场景。于是搜索了相关资料来理解并记录一下。
@Modifying
@Transactional
void deleteAllByDistrictId(Long districtId);
@Transactional简单概括
使用这个注解的类或者方法的事务由spring处理,即是方法里面对数据库操作,如果失败则spring负责回滚操作,成功则提交操作。也就是上周学长提到的保证事务的原子性:对数据进行操作的时候,要么全都执行,要么全都不执行。
举个例子: 当前方法需要将数据插入两个数据表:
假如第一张表插入成功了,但是插入第二张的时候失败了,怎么处理?
1.要么就保证两张表都插入成功
2.要么就进行回滚,取消对第一张表的操作。
@Transactional注解就是为了帮助我们管理这些事务。
AOP
查找资料的时候我发现这么一句话:
@Transactional原理是基于spring aop实现的。
那么Aop又是什么呢?
AOP(Aspect-oriented programming):面向切面編程。
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
与之相对是的以前常使用的OOP (Object-oriented programming)面向对象
下面用一个场景来说明一下使用Aop带给我们的好处:
场景:假如说我们已经完成了一个项目,现在想加入日志功能,在save方法中打印日志。
- 如果我们依然使用OOP面向对象,那么我们会这么做: 在save方法代码里,写上打印日志的的代码。有时候写错时甚至会导致原来执行正常的方法执行失败。
如果我们使用了AOP呢?
@Around("execution(* Controller.save(..))") public void print() throws Throwable {
我们就可以使用切面,当Controller.save执行的时候,执行该打印日志的函数。
这样的做法,对原有代码毫无入侵性,这就是AOP的好处了,把和主业务无关的事情,放到代码外面去做。
切面执行时机有五种:
「@Before」 :在目标方法调用前去通知
「@AfterReturning」 :在目标方法返回或异常后调用
「@After」 :在目标方法返回后调用
「@AfterThrowing」 :在目标方法异常后调用
「@Around」 :将目标方法封装起来,自己确定调用时机
在了解AOP是啥之后,我们再来看看实现原理:
AOP 实现主要分为两类:
1.静态 AOP
2.动态 AOP
这里我们主要讲动态 AOP ,因为spring 中 AOP 的实现是就说通过动态代理实现的。
什么是代理呢,就是我再生成一个代理类,去代理Controller的save()方法,代码大概就长这样:
class ControllerProxy {
private Controller controller;
public void save() {
controller.save();
print() // 打印日志
}
}
所以在刚才那张图中,spring实现AOP过程大概是这样的:
(这里根据不同的切面时机过程略有不同,这里是After切面)
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象。
@Transactional
@Transactional原理就是基于spring aop实现的。
根据AOP,我们就可以大概猜测出@Transactional的执行过程,这里以delete方法作为事务例子:
大概了解@Transactional执行逻辑之后,
那么我们来看看@Transactional什么时候会回滚呢?
一个个来说,对于加了@Transactional的方法或类:
Error: 一定会回滚
Excepion: 分为两类
不可查的异常(unchecked exceptions):RuntimeException及其子类和错误(Error)
RuntimeException的子类有很多,不只图片上的,类似的有ArithemticeExcepiton,算术运算异常等。
需要注意的是:
Spring框架的事务基础架构代码将默认地只在抛出error时和unchecked exceptions时才标识事务回滚。
如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。@Transactional(rollbackFor = Exception.class)
- 可查的异常(checked exceptions):Exception下除了RuntimeException外的异常,不会回滚。
例如,SQLException, 数据库连接异常,IOException,编译异常。
对于这种IOException、SQLException等以及用户自定义的Exception异常,
JAVA编译器强制要求我们必需对出现的这些异常进行catch并处理,否则程序就不能编译通过。
所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。
上次因为写了formatter.parse(),编译器就提示必须throws ParseException,并且调用它的函数也需要throws ParseException。
文章到此结束,若有错误还请指正