Springboot 事务&AOP
@Transactional事务注解
@Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事
务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。
@Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能
可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在
一个事务范围内。
-
异常回滚的属性:rollbackFor
-
事务传播行为:propagation
@Transactional //当前方法添加了事务管理
public void delete(Integer id){
//根据部门id删除部门信息
deptMapper.deleteById(id);
//模拟:异常发生
int i = 1/0;
//删除部门下的所有员工信息
empMapper.deleteByDeptId(id);
}
application.yml配置文件中开启事务管理日志,这样就可以在控制看到和事务相关的日志信息了
#spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
rollbackFor
默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务。
想让所有的异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性:
rollbackFor=Exception.class
propagation
什么是事务的传播行为呢?
- 就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
例子:
-
执行解散部门的业务:先删除部门,再删除部门下的员工
-
记录解散部门的日志,到日志表
@Transactional(rollbackFor = Exception.class)
//通过rollbackFor这个属性可以指定出现何种异常类型回滚事务(Exception.class)
//将当前方法交给spring进行事务管理
// 出现任何异常回滚事物
@Override
public void delete(Integer id) throws Exception{
try {
//根据部门id删除部门信息
deptMapper.deleteById(id);
//模拟:异常
int i=1/0;
//会引发除0的算数运算异常(运行时异常)
// if(true){
// throw new Exception("出现异常了~~~");
// }
//编译时异常
//删除部门下的所有员工信息
empMapper.deleteByDeptId(id);
}finally {
//不论是否有异常,最终都要执行的代码:记录日志
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("执行了解散部门的操作,此时解散的是" + id + "号部门");
//调用其他业务类中的方法
deptLogService.insert(deptLog);
}
}
DeptServiceImpl中的delete方法运行时,会开启一个事务。 当调用 deptLogService.insert(deptLog) 时,也会创建一个新的事务,那此时,当insert方法运行完毕之后,事务就已经提交了。
即使外部的事务出现异常,内部已经提交的事务,也不会回滚了,因为是两个独立的事务。
@Service
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
//需要一个新的事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
REQUIRED :大部分情况下都是用该传播行为即可。
REQUIRES_NEW :当不希望事务之间相互影响时,可以使用该传播行为。比如:下订
单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
AOP
添加依赖pom.xml
<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
- 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
- 切入点:Pointcut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
- 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
- 目标对象:Target,通知所应用的对象
![外链图片转存失败,源站可能有防盗链机制,建议将### 五种常见的通知类型:
- @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行
- @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
- @AfterReturning: 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行
@Around:环绕通知
Object result =joinPoint.proceed();
@Around环绕通知需要自己调用 joinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
@Around环绕通知方法的返回值,必须指定为0bject,来接收原始方法的返回值。
@Pointcut
该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可。
@Pointcut("excution(* 包名+类名.*(..))")
public void pt(){}
@Before("pt()")
当切入点方法使用private修饰时,仅能在当前切面类中引用该表达式, 当外部其他切面类中也要引用当前类中的切入点表达式,就需要把private改为public,而在引用的时候需要写:全类名.方法名()
//引用MyAspect1切面类中的切入点表达式
@Before("包名+类名..pt()")
通知顺序
在不同切面类中,默认按照切面类的类名字母排序:
- 目标方法前的通知方法:字母排名靠前的先执行
- 目标方法后的通知方法:字母排名靠前的后执行
控制通知的执行顺序:
在切面类上使用@Order注解,控制通知的执行顺序:
@Order(2) //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
切入点表达式
execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution( 返回值 包名.类名.方法名(方法参数) throws 异常)
访问修饰符:可省略(比如: public、protected)
包名.类名: 可省略
throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
* :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分。
… :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数。
例如:
execution(void funnyboy.service.impl.DeptServiceImpl.delete(java.lang.Integer))
execution(* com..DeptServiceImpl.delete(java.lang.Integer))
// 使用..省略包名
execution(* com.itheima.service.*.*(..))
//常见的用法
@annotation
- 基于注解的方式来匹配切入点方法
实现步骤:
- 编写自定义注解
- 在业务类要做为连接点的方法上添加自定义注解
- 编写切面类并引用,@annotation(funnyboy.anno.MyLog)
自定义注解的写法
添加元注解@Retention
SOURCE:编译生成的字节码文件中就不存在了。
CLASS:编译以后的字节码文件中,运行的时候内存中没有,默认值。
**RUNTIME:**编译以后的字节码文件中、运行时内存中,程序可以通过反射获取注解。(编译时运行)
添加元注解@Target
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
格式
元注解
public @interface 注解名称{
属性列表;
}
连接点
在Spring中用JoinPoint抽象了连接点,可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
- 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型。
在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
格式
元注解
public @interface 注解名称{
属性列表;
}
连接点
在Spring中用JoinPoint抽象了连接点,可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。
- 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型。
- 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型。