1.事务管理
概述:是指一组数据库操作序列,这些操作要么全部执行成功,要么全部失败回滚,保证数据库的一致性和完整性。
事务的操作主要有三步:
-
开启事务(一组操作开始前,开启事务):start transaction / begin ;
-
提交事务(操作全部成功后,提交事务):commit ;
-
回滚事务(中间任何一个操作出现异常,回滚事务):rollback ;
事务的四个基本特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability),通常简称为 ACID 特性。
- 原子性:一个事务内的所有操作被视为一个不可分割的原子单元,要么全部成功执行,要么全部失败回滚。
- 一致性:事务将数据库从一种状态转换到另一种状态,保证在操作序列中,每个事务的执行都符合系统的一致性规则,从而维护数据库的一致性。
- 隔离性:多个事务可以并发执行,但是它们之间不能互相干扰,每个事务的操作似乎独立于其它事务的操作。
- 持久性:当一个事务完成后,它对数据库所作的修改就应该永久保存下来,即使出现系统故障也不应该丢失。
1.1Spring事务管理
概述:在方法,类,接口上添加@Transactional注解,就可以管理事务
@Transactional注解上的属性:
1.异常回滚的属性:rollbackFor
@Transactional注解默认情况下,只有异常是运行时异常,才会回滚事务,rollbackFor属性可以设置异常回滚的范围
@Transactional(rollbackFor=Exception.class)
则设置为所以异常都回滚事务
2.事务传播行为:propagation
概述:就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。
例如:两个事务方法,一个A方法,一个B方法。在这两个方法上都添加了@Transactional注解,就代表这两个方法都具有事务,而在A方法当中又去调用了B方法。
属性值 | 含义 |
---|---|
REQUIRED | 【默认值】需要事务,有则加入,无则创建新事务 |
REQUIRES_NEW | 需要新事务,无论有无,总是创建新事务 |
@Transactional(propagation = Propagation.REQUIRES_NEW)
使用场景:
-
REQUIRED :大部分情况下都是用该传播行为即可。
-
REQUIRES_NEW :当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。
2.AOP基础
2.1 概述:
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性,
原理:基于动态代理实现的,
2.2 实现步骤:
1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.编写AOP程序
@Component
@Aspect //当前类为切面类
@Slf4j
public class TimeAspect {
@Around("execution(* com.*.*.*.*(..))")
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
//记录方法执行开始时间
long start = System.currentTimeMillis();
//执行原始方法
Object result = joinPoint.proceed();
//记录方法执行结束时间
long end = System.currentTimeMillis();
//计算方法执行耗时
log.info(joinPoint.getSignature()+"执行耗时: {}毫秒",end-start);
return result;
}
}
2.3 核心概念
-
切面(Aspect):切面是关注点的模块化,它包含一组相互关联的通知(Advice)和切点(Pointcut)。通知是在应用程序执行期间特定的连接点(Join Point)执行的代码,切点定义了哪些连接点会触发通知。
-
连接点(Join Point):连接点是在应用程序执行期间进行拦截的点,比如方法调用、异常处理等。连接点是 AOP 的基础,所有通知都与连接点相关联。
-
通知(Advice):通知是在特定连接点执行的代码,它包括了要在连接点执行之前、之后或周围执行的逻辑。常见的通知类型有 Before(前置通知)、After(后置通知)、Around(环绕通知)、AfterReturning(返回通知)和 AfterThrowing(异常通知)等。
-
切入点(Pointcut):切点是一个表达式,它定义了一组连接点,通知会被织入到这些连接点上,从而实现对应用程序的切面切入。
-
目标对象(Target Object):目标对象是包含连接点的原始对象。在 AOP 中,切面将会织入到目标对象中,从而改变目标对象的行为。
2.4 Spring中AOP的通知类型
-
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
-
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
-
@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
-
@AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
-
@AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
切面类的执行顺序:
默认按照切面类的类名字母排序,
目标方法前的通知方法:字母排名靠前的先执行
目标方法后的通知方法:字母排名靠前的后执行
可以通过Spring提供的@Order注解改变切面类的执行顺序
- @Order(数字)
- 切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
2.5 切入点表达式
概述:描述切入点方法的一种表达式
作用:主要用来决定项目中的哪些方法需要加入通知
1.execution(...)根据方法的签名来匹配
@Around("execution(*com.*.*.*.*(..))")
-
*
:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分 -
..
:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
切入点表达式的语法规则:
-
方法的访问修饰符可以省略
-
返回值可以使用
*
号代替(任意返回值类型) -
包名可以使用
*
号代替,代表任意包(一层包使用一个*
) -
使用
..
配置包名,标识此包以及此包下的所有子包 -
类名可以使用
*
号代替,标识任意类 -
方法名可以使用
*
号代替,表示任意方法 -
可以使用
*
配置参数,一个任意类型的参数 -
可以使用
..
配置参数,任意个任意类型的参数
注意事项:可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式
2.@annotation(...)根据注解匹配
实现步骤:
-
编写自定义注解
-
在业务类要做为连接点的方法上添加自定义注解
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
在方法上添加注解
@Log //自定义注解(表示:当前方法属于目标方法)
public void list() {
//业务代码
}
定义切面类,
@Component
@Aspect //标注此类为切面类
public class MyAspect6 {
//环绕通知
@Around("@annotation(Log注解的全类名)")
public Object recordAround(ProceedingJoinPoint joinPoint){
//代码实现
}
}
连接点
@Slf4j
@Component
@Aspect
public class MyAspect {
@Pointcut("@annotation(com.*.*.MyLog)")
private void pt(){}
//前置通知
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info(joinPoint.getSignature().getName() + " MyAspect -> before ...");
}
//后置通知
@Before("pt()")
public void after(JoinPoint joinPoint){
log.info(joinPoint.getSignature().getName() + " MyAspect -> after ...");
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//获取目标类名
String name = pjp.getTarget().getClass().getName();
log.info("目标类名:{}",name);
//目标方法名
String methodName = pjp.getSignature().getName();
log.info("目标方法名:{}",methodName);
//获取方法执行时需要的参数
Object[] args = pjp.getArgs();
log.info("目标方法参数:{}", Arrays.toString(args));
//执行原始方法
Object returnValue = pjp.proceed();
return returnValue;
}
}