前言:在我们前面学习的案例中,其中执行删除操作时,会产生bug,当我们删除部门时,实际上部门下的员工信息应该也消失了,但是查询之后,我们发现有异常时,仅仅是删除了部分,员工数据还存在,造成是偏差,这时我们就可以把这两个事件捆绑成一个事务,即使出现异常时,也能执行回滚事务,保持数据的一致。
Spring事务管理
注解:@Transactional
位置:业务(service)层的方法上、类上、接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务
我们可以在yml文件中配置事务管理日志
logging: level: org.springframework.jdbc.support.JdbcTransactionManager: DEBUG

++格式如上,我们要注意,事务管理是spring框架下的,要注意位置,还有就是格式,空一个空格还是两个空格,都是值得注意的。++
然而并不是所有异常之后都能执行事务回滚的,只有运行时异常才可以。这时我们就要学习下面的知识了。
事务属性-回滚
rollbackFor
默认情况下,只有出现 RuntimeException才回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务。

事务属性-传播行为
propagation
事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。

在一个案例中,默认的是REQUIRED,有事务时,会默认加入到那个事务,这时出现异常的时候,这两个都会回滚,但是我们可以在前面加上_NEW让他自己创建一个事务,不会跟随第一个事务回滚而不执行。
场景
REQUIRED:大部分情况下都是用该传播行为即可。
REQUIRES NEW:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与A否,都需要保证日志记录能够记录成功。
AOP概述
AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。
实现:
++动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。++
引入AOP依赖:
<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Component:将当前类交给IOC容器管理
@Aspect:当前类不是普通类,而是AOP类。
@Around:当前共性的功能应该执行在哪些方法上,指定范围。(切入点表达式)
我们前面提及的事务管理,其实就是底层就是根据AOP来实现的 。在原始方法开始之前开启事务,原始方法运行之后提交/回滚事务。
AOP就像一个万能插件🔌:
-
把你的业务代码想象成插座
-
AOP就是各种插头(日志、事务、安全等)
-
不用修改插座,想用什么功能就插什么插头
-
随时可以拔掉 或更换,不影响插座本身
✨ 最形象的理解
原始代码 :一棵光秃秃的树🌲
AOP之后:一棵挂满彩灯的圣诞树🎄✨
你只需要种树(写业务代码),
AOP会自动帮你:
-
挂彩灯🎄(加日志)
-
绕星星🌟(加事务)
-
放礼物🎁(加权限)
-
而且不用修改树本身!
这就是AOP的魅力:横切关注点,纵向解耦 🎯
AOP核心概念
连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
目标对象:Target,通知所应用的对象
方法增强的逻辑:
我们所定义的通知,是如何于目标对象结合在一起对目标对象的方法进行增强的。
在运行的时候,AOP的底层是基于动态代理技术进行实现的。在程序运行的时候,会自动基于动态代理技术为目标对象生成对应的代理对象。在这个代理对象中,就会对原始的对象进行功能增强
那么在Controller运行注入的,就不再是目标对象,而是代理对象。所以最终调用的方法,就是代理对象中被增强的方法。
通知类型
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行@AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
注意事项
@Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。
@PointCut
该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。
且要注意权限修饰符,private只能在本类中使用。
private:仅能在当前切面类中引用该表达式
public:在其他外部的切面类中也可以引用该表达式
执行顺序
1.不同切面类中,默认按照切面类的类名字母排序:
目标方法前的通知方法:字母排名靠前的先执行目标方法后的通知方法:字母排名靠前的后执行
2.用@Order(数字)加在切面类上来控制顺序
目标方法前的通知方法:数字小的先执行
目标方法后的通知方法:数字小的后执行
切入点表达式
切入点表达式:描述切入点方法的一种表达式作用:主要用来决定项目中的哪些方法需要加入通知
常见形式:
1.execution.....):根据方法的签名来匹配
2.@annotation (....):根据注解匹配
切入点表达式-execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数)throws 异常?)
其中带?的表示可以省略的部分
访问修饰符:可省略(比如:public、protected)
包名.类名:可省略
throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
可以使用通配符描述切入点
:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
execution(* com.*.service.*.update*(*))
..:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
execution(* com.itheima..DeptService.*(..))
书写建议
所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是find开头,更新类方法都是update开头。
描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。
在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用..,使用*匹配单个包。
切入点表达式-@annotation
@annotation切入点表达式,用于匹配标识有特定注解的方法。
@annotation(com.itheima.anno.Log)
连接点
在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
> 对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint
> 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是 ProceedingJoinPoint 的父类型
