目录
2.3.1 @Transactional注解当中的两个常见的属性:
3.2.3.1 execution(……):根据方法的签名来匹配
3.2.3.2 @annotation(……) :根据注解匹配
一.数据库事务管理(简述):
事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。
1.1事务的操作:
1.开启事务(操作开始前,开启事务): start transaction / begin
2.提交事务(在该组操作全部成功后,提交事务): commit
3.回滚事务(在该组操作的时候出现任何异常,可以回滚事务): rollback
二.Spring事务管理:
2.1Spring事务管理简介:
在Spring中,事务管理是通过Spring事务管理器(Transaction Manager)来实现的。Spring事务管理器提供了一致的编程模型,使得在不同的事务管理技术(如JDBC、Hibernate、JPA等)之间切换变得容易。
Spring事务管理的核心概念是面向切面编程(Aspect-Oriented Programming,AOP)。Spring使用AOP将事务管理从业务逻辑中解耦,使得开发者可以将关注点集中在业务逻辑上,而不需要显式地处理事务管理的细节。
2.2 @Transactional注解
2.2.1 @Transactional注解的作用:
@Transactional作用:就是在当前这个方法执行开始之前来开启事务,方法执行完毕之后提交事务。如果在这个方法执行的过程当中出现了异常,就会进行事务的回滚操作。
@Transactional注解:我们一般会在业务层当中来控制事务,因为在业务层当中,一个业务功能可能会包含多个数据访问的操作。在业务层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。
2.2.2 @Transactional注解的书写位置
1.写在方法上:当前方法交给Spring进行事务管理
2.写在类上: 当前类中的所有方法都交给Spring进行事务管理
3.写在接口上: 接口下的所有实现类当中的所有方法都交给Spring进行事务管理
2.2.3 @Transactional注解案例演示
现有部门(dept),员工(emp)两张表,员工表内对应着部门表的Id,现在进行解散部门操作,即删除部门,当部门解散了不仅需要把部门信息删除了,还需要把该部门下的员工数据也删除了.
需求分析:
1.执行根据Id删除部门的操作代码
2.在delete方法上添加@Transactional注解进行Spring事务管理
3.添加Spring事务管理之后,运行后由于服务端程序引发异常,所以触发事务回滚
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Override
@Transactional //当前方法添加了事务管理
public void delete(Integer id){
//根据部门id删除部门信息
deptMapper.deleteById(id);
//模拟:异常发生
int i = 1/0;
//删除部门下的所有员工信息
empMapper.deleteByDeptId(id);
}
}
ps: 在application.yml配置文件中开启事务管理日志,就可以在控制台看到和事务相关的日志信息.
#spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
2.3事务进阶
2.3.1 @Transactional注解当中的两个常见的属性:
1.异常回滚属性:rollbackFor
2.事务传播行为:propagation
2.3.2 rollbackFor属性
在2.2.3案例中,delete方法在运行时,会引发除0的算数模拟异常,出现异常之后,由于在方法上加了@Transactional注解进行事务管理,所以会触发rollback回滚操作,从而保证事务操作前后数据是一致的.下面咱们来做一个测试,修改一下业务功能代码,模拟异常的位置上直接抛出Exception异常.
@Transactional
public void delete(Integer id) throws Exception {
//根据部门id删除部门信息
deptMapper.deleteById(id);
//模拟:异常发生
if(true){
throw new Exception("出现异常了~~~");
}
//删除部门下的所有员工信息
empMapper.deleteByDeptId(id);
}
在service中向上抛出一个Exception编译时异常之后,由于是controller调用service,所以在controller中要有异常处理代码,此时我们选择在controller中继续把异常向上抛。
@DeleteMapping("/depts/{id}")
public Result delete(@PathVariable Integer id) throws Exception {
//日志记录
log.info("根据id删除部门");
//调用service层功能
deptService.delete(id);
//响应
return Result.success();
}
测试结果显示,发生了Exception异常,但是事务依然提交了,从而得出结论,默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务。
我们想让所有的异常都回滚,需要来配置@Transactional注解当中的rollbackFor属性,通过rollbackFor这个属性可以指定出现何种异常类型回滚事务。
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Override
@Transactional(rollbackFor=Exception.class)
public void delete(Integer id){
//根据部门id删除部门信息
deptMapper.deleteById(id);
//模拟:异常发生
int num = id/0;
//删除部门下的所有员工信息
empMapper.deleteByDeptId(id);
}
}
结论:
-
在Spring的事务管理中,默认只有运行时异常 RuntimeException才会回滚。
-
如果还需要回滚指定类型的异常,可以通过rollbackFor属性来指定。
2.3.3 propagation 属性
用于控制事务在方法调用之间的传播方式。它定义了一个事务方法在调用另一个事务方法时,如何处理事务的行为。
propagation 属性可以在使用@Transactional注解时指定,它有多个取值选项,每个选项都代表了一种不同的传播行为,下面是常用的传播行为选项:
属性值 | 含义 |
---|---|
REQUIRED(默认) | 如果当前没有事务,就新建一个事务;如果已经存在一个事务中,就加入到这个事务中。这是最常用的传播行为选项,它确保多个方法调用处于同一个事务中。 |
REQUIRES_NEW | 每次都新建一个事务,如果当前存在事务,就将当前事务挂起。这个选项会创建一个独立的事务,不受调用者事务的影响。 |
NESTED | 如果当前存在事务,就在嵌套事务中执行;如果没有事务,就新建一个事务。嵌套事务是指在一个已存在的事务中创建一个子事务,它们共享事务的提交和回滚。嵌套事务可以独立于父事务进行提交或回滚。 |
SUPPORTS | 如果当前存在事务,就在事务中执行;如果没有事务,就以非事务方式执行。这个选项表示方法对事务的存在与否不敏感,如果有事务则在事务中执行,否则以非事务方式执行。 |
MANDATORY | 如果当前存在事务,就在事务中执行;如果没有事务,就抛出异常。这个选项表示方法必须在一个已存在的事务中执行,如果没有事务则抛出异常。 |
NOT_SUPPORTED | 方法以非事务方式执行,如果当前存在事务,就将事务挂起。 |
三.AOP
3.1AOP基础
AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。
3.1.1 AOP的作用
-
横切关注点的解耦:AOP可以将横切关注点与核心业务逻辑分离,避免代码中的重复和冗余。通过AOP,可以将关注点集中到切面中,并将其应用于需要的地方,而不需要在每个业务逻辑中重复编写相同的代码。
-
代码重用和模块化:通过AOP,可以将通用的横切关注点封装成切面,并在需要的地方进行应用。这样可以实现代码的重用和模块化,提高开发效率和代码质量。
-
面向切面的编程:AOP允许开发者通过定义切面来修改或扩展现有的代码行为,而无需直接修改原始代码。这种面向切面的编程方式使得系统的功能和行为可以在不修改核心业务逻辑的情况下进行灵活定制和扩展。
-
横切关注点的集中管理:通过AOP,可以将多个模块或组件共享的横切关注点集中管理。例如,日志记录、性能监控和事务管理等可以被封装成切面,而不需要在每个模块或组件中单独处理。
-
提高系统的可维护性和可测试性:将横切关注点与核心业务逻辑分离,使得代码更加清晰和可维护。同时,切面可以更容易地进行单元测试,因为关注点的实现被集中到切面中,而不会分散在各个业务逻辑中。
3.1.2 AOP快速入门
1.导入依赖:在pom.xml配置文件中到入AOP依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.编写AOP程序:针对于特定方法根据业务需要进行编程
@Component
@Aspect //当前类为切面类
@Slf4j
public class TimeAspect {
@Around("execution(* com.itheima.service.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
//记录方法执行开始时间
long begin = System.currentTimeMillis();
//执行原始方法
Object result = pjp.proceed();
//记录方法执行结束时间
long end = System.currentTimeMillis();
//计算方法执行耗时
log.info(pjp.getSignature()+"执行耗时: {}毫秒",end-begin);
return result;
}
}
3.常见的应用场景如下:
记录系统的操作日志
权限控制
事务管理:我们前面所讲解的Spring事务管理,底层其实也是通过AOP来实现的,只要添加@Transactional注解之后,AOP程序自动会在原始方法运行前先来开启事务,在原始方法运行完毕之后提交或回滚事务
3.1.3 AOP核心
1.连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
2.通知: Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
3.切入点: PointCut ,匹配连接点的条件,通知仅会在切入点方法执行时被应用
4.切面: Aspect,描述通知与切入点的对应关系(通知+切入点)
5.目标对象: Target,通知所应用的对象
3.2 AOP进阶
3.2.1 通知类型
Spring中AOP的通知类型:
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
@AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
@AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
@Slf4j
@Component
@Aspect
public class MyAspect1 {
//切入点方法(公共的切入点表达式)
@Pointcut("execution(* com.itheima.service.*.*(..))")
private void pt(){
}
//前置通知(引用切入点)
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("before ...");
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
//原始方法在执行时:发生异常
//后续代码不在执行
log.info("around after ...");
return result;
}
//后置通知
@After("pt()")
public void after(JoinPoint joinPoint){
log.info("after ...");
}
//返回后通知(程序在正常执行的情况下,会执行的后置通知)
@AfterReturning("pt()")
public void afterReturning(JoinPoint joinPoint){
log.info("afterReturning ...");
}
//异常通知(程序在出现异常的情况下,执行的后置通知)
@AfterThrowing("pt()")
public void afterThrowing(JoinPoint joinPoint){
log.info("afterThrowing ...");
}
}
3.2.2 通知顺序
-
不同的切面类当中,默认情况下通知的执行顺序是与切面类的类名字母排序是有关系的
-
可以在切面类上面加上@Order注解,来控制不同的切面类通知的执行顺序
3.2.3 切入表达式
常见形式:
3.2.3.1 execution(……):根据方法的签名来匹配
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
切入点表达式的语法规则:
1. 方法的访问修饰符可以省略
2. 返回值可以使用`*`号代替(任意返回值类型)
3. 包名可以使用`*`号代替,代表任意包(一层包使用一个`*`)
4. 使用`..`配置包名,标识此包以及此包下的所有子包
5. 类名可以使用`*`号代替,标识任意类
6. 方法名可以使用`*`号代替,表示任意方法
7. 可以使用 `*` 配置参数,一个任意类型的参数
8. 可以使用`..` 配置参数,任意个任意类型的参数
切入点表达式示例
1.省略方法的修饰符号
execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
2.使用*
代替返回值类型
execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
3.使用*
代替包名(一层包使用一个*
)
execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
4.使用..
省略包名
execution(* com..DeptServiceImpl.delete(java.lang.Integer))
5.使用*
代替类名
execution(* com..*.delete(java.lang.Integer))
6.使用*
代替方法名
execution(* com..*.*(java.lang.Integer))
7.使用 *
代替参数
execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))
8.使用..
省略参数
execution(* com..*.*(..))
注意事项:
-
根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。
3.2.3.2 @annotation(……) :根据注解匹配
用于匹配带有特定注解的方法。切点表达式定义了切入点(Join Point),即在哪些方法执行的时候,应该触发切面(Aspect)中的通知(Advice)。
@Aspect
@Component
public class MyAspect {
@Before("@annotation(com.example.MyAnnotation)")
public void beforeMethod(JoinPoint joinPoint) {
// 在带有@MyAnnotation注解的方法执行之前执行的逻辑
// ...
}
}