66_Spring事务:先使用AOP来模拟一下@Transactional的功能
开篇
大家好,到目前为止呢,整个AOP章节的内容就结束了,那么从这节开始,我们就正式进入事务部分的学习了,说到事务呢,那就不得不提@Transactional注解了,因为在平时的工作中,我们主要就是依赖于@Transactional注解来实现事务管理的。
所以事务这块呢,我们主要就是来分析这个@Transactional注解,我们会来分析,为什么简单的加个@Transactional注解就可以支持事务了?这个@Transactional注解的背后,到底隐藏了哪些玄机?
不过呢,本节作为事务的第一讲,不会涉及到事务源码,内容还是比较简单的,大家不用紧张,我们会平滑的过度到事务源码的。
那么这节讲什么呢?
是这样的,因为我们不是刚讲过AOP蛮,所以趁着大家AOP的知识还没忘,我们这节就基于AOP,来模拟一下@Transactional注解的功能,本节的主要内容如下:
- 首先我们需要虚拟出来一个需要事务的业务场景,比如提交订单这个业务场景
- 接着我们会基于AOP来模拟@Transactional注解的功能,说白了就是会搞一个事务切面来做些增强
- 最后我们会分情况来看下模拟事务的效果,其实就是正常执行完毕是什么效果,发生异常时又是什么效果
来虚拟一个需要事务的业务场景
好了,那现在就开始吧,既然要模仿@Transactional注解的功能,那么我们就必须先要圈定一个业务场景,因为脱离业务场景来讲技术,那就是耍流氓
那这样,我们的业务场景就选用提交订单吧,因为大家平常点外卖和网购都会涉及到提交订单,相对熟悉点
好,确定完场景后,那么第一步,当然是要先定义一个提交订单的接口了,如下图:
可以看到,上边呢,其实就是定义了一个提交订单的接口
有了接口,那么必然要有实现类啊,实现类代码如下:
我们知道,订单通常包括订单表和订单明细表,所以在提交订单的接口中,就会同时向订单表和订单明细表中插入数据,就像上图一样,我们这里为了简化,就直接通过打印的形式表示了,大家知道意思就好。
那么大家都知道,在操作数据库的时候,如果你的接口中,有多个增删改操作,那么就需要把它们放到一个事务中,这样才可以保证,这些操作要么一起成功,那么一起失败。
如果不添加事务的话,就会造成数据不一致,比如在提交订单的场景中,如果不添加事务的话,那么就可能会发生订单表数据插入成功了,但是订单明细表数据却插入失败了,那这样数据就有问题了,所以同一个接口中多个增删改操作添加事务通常都是必须的!
基于AOP来模拟@Transactional注解的功能
我们知道,平时我们工作时,在这个场景下,我们就直接使用@Transactional注解来实现事务了。
不过这里呢,我们暂时不用@Transactional注解,而是使用我们刚学过的AOP,也就是说,我们可以基于AOP先来模拟下@Transactional的效果。
那具体该怎么做呢?
我们知道,AOP的本质呢,其实就是动态代理,而动态代理说白了就是一种代理模式,通过代理模式我们可以额外添加一些增强逻辑。
因此我们可以利用代理类中的增强逻辑来实现@Transactional注解的效果,那么这个是什么意思呢?
其实也很简单,我们可以这样做,大家来看下边这张图:
通过上边的图,我们可以看到,客户端调用代理类方法时,代理类首先会开启一个事务,然后再执行目标类的方法,当目标类方法执行结束之后,再来看下此时是否发生异常,如果发生了异常,那么就直接回滚事务,而如果没有发生异常,那么就提交事务。
而上图中的开启事务、提交事务和回滚事务操作,其实都是AOP中的增强逻辑,这样不就实现了事务的基本逻辑了吗?是不是感觉很简单?
其实啊,我们平时使用事务时,也是这样的逻辑,其实就是正常执行完毕后提交事务,而如果有异常就回滚事务,和我们之前的使用经验是吻合的,完全没毛病
好,俗话说:“说起来简单做起来难”,那上图中的逻辑该怎么来落地实现呢?
其实啊,这里还真的是“说起来简单,做起来也简单”,大家想一想,其实现在要做的,不就是在目标类的前后加一些增强逻辑蛮,那么AOP人家是提供了五种增强的,我们直接在AOP的五种增强中,来添加这些增强逻辑就可以了。
我们可以这样搞,我们看下边这张图:
可以看到,上图呢,其实定义了一个事务切面,而切面中核心就是@Around增强,而@Around增强之前我们讲过,主要就是通过 point.proceed()这行代码来调用目标方法的,而在调用目标方法的前后,我们都可以自由地添加一些增强逻辑。
比如上边的代码,就在point.proceed()
这行代码之前,打印了一行日志,说白了这里是用来模拟开启事务操作的,开启完事务后,接着就调用point.proceed()调用目标方法了
我们注意到point.proceed()这行代码做了try catch处理,一旦point.proceed()执行发生异常的话,那么就会执行catch中的逻辑,这里同样是打印了一行日志,说白了就是用来模拟回滚事务的操作,回滚完事务后,会直接将这个异常抛出。
而如果point.proceed()正常执行完了目标方法,那么也会执行一行日志,说白了就是用来模拟提交事务的操作。
怎么样?上边的代码是不是非常简单?
说白了就是对调用目标方法的操作加了try catch的处理,同时在执行目标方法前后做一些事务增强,也就是执行目标方法前开启事务,目标方法正常执行完毕之后,提交事务,其实非常简单。
分情况来看下模拟事务的效果
好了,核心代码就是上边这些,那么最后效果到底如何呢?我们下边一起来看下吧
验证效果的测试类代码如下:
可以看到,上边的代码,其实就是从IOC容器中获取到了AOP代理对象,然后直接调用了代理对象的submitOrder()方法。
而最后的打印结果如下:
通过上图,可以看到,在执行目标方法submitOrder之前开启了事务,而在目标方法submitOrder正常执行结果之后提交了事务,可以看到,结果是符合预期的,其实就像我们平时使用@Transactional注解的效果一样。
那么上边这个是正常执行结束的效果,那万一目标方法执行一半发生了异常呢?此时该怎么办?
这个简单,我们来模拟一下就可以了,我们看下边的代码,如下图:
通过上图,可以看到,此时我们在目标类中,添加了一行代码来计算1/0的结果,而我们都知道除数是不能为0的,所以这行代码就是用来模拟发生异常的情况,
此时执行结果如下:
通过上图,我们可以看到,同样是在执行目标方法submitOrder之前开启了事务,不一样的是,在执行目标方法时,当插入订单的操作执行完毕,准备执行订单明细的插入时,发生了异常。
那么这里可以看到,发生异常的情况下,是会回滚事务的,并且发生的异常也被抛了出来,这个是符合预期的,其实和我们之前使用@Transactional注解的逻辑,完全是一样的!
总结
好了,今天的知识点,我们就讲到这里了,我们来总结一下吧。
本节作为我们事务的第一讲,我们没有来分析事务源码,而是基于AOP来模拟了@Transactional注解的功能。
说白了,就是使用AOP的@Around增强,对目标方法做了try catch处理,并且在目标方法前后做了增强,也就是开启事务和提交事务。
并且我们还分别看了未发生异常和发生异常两种情况的运行结果,其实和我们平常使用@Transactional注解的效果是一样的!
那么我们这里呢,毕竟只是简单模拟了一下@Transactional注解的效果,而@Transactional注解的原理肯定要复杂的多,从下节开始,我们就会开始分析@Transactional注解的源码了,这节只是一个开胃菜,主要就是来帮助大家可以平滑的过度到事务源码中,好了,那这节就到这里了,我们下节见。