事务和AOP
1 事务
1.1 特点 ACID
1.1.1 原子性 (Atomicity)
把多个操作看成一个整体,要么一起成功,要么一起回滚
1.1.2 一致性 (Consistency)
数据操作前后,都要保持一致,要么一起修改,要么一起不修改
1.1.3 隔离性 (Isolation)
两个事务之前,相互独立,没有影响
1.1.4 持久性 (Durability)
一旦对数据库的数据进行修改,影响是永久的
1.2 Spring的事务的实现
1.2.1 @Transational
表示开启事务, 此时回滚异常只可以为: 运行时异常 => Runtime Exception, 编译时异常不会开启事务回滚
/\ 可以在方法上添加
/\ 可以在类上添加
/\ 可以在接口上添加
PS: 建议在接口上添加
@Transactional
1.3 注解中两个属性
1.3.1 rollbackfor
因为事务默认在RuntimeException回滚,我们通过这个属性可以指定所有的异常
@Transactional (rollbackFor = Exception.class)
1.3.2 propagation (传播行为)
1) REQUIRED => 大部分选择这种,全部操作要么都成功要么都失败
默认,有事务,如果加入的的方法有事务,也会直接加入到当前事务中 (当前操作只有一个事务)
2) REQUIRED_NEW => 彼此的事务不会有影响。一部分的事务相互独立, 表示一定要做这件事
有事务。如果当前方法有事务,那么调用的方法本身也会创建新的事务。(当前是有两个事务)
@Transactional (rollbackFor = Exception.class)
@Override
public void deleteDeptById(Integer id) {
//利用事务无论成功或失败都进行logging
try {
deptMapper.deleteDeptbyId(id);
int i = 1/0;
empMapper.deleteEmpByDept(id);
} finally {
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("有人修改了表格");
deptLogMapper.insert(deptLog);
}
}
@Mapper
public interface DeptLogMapper {
@Transactional (rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
@Insert("insert into dept_log(create_time,description) values(#{createTime},#{description})")
void insert(DeptLog log);
}
2 AOP
2.1 本质
动态代理, AOP实际上会创建一个动态代理的对象位于底层, 并通过动态代理的方式增强功能
2.2 概念
面向切面编程 (面向方法编程) 是一种编程范式,用于将横切关注点与核心业务逻辑进行解耦和分离。它通过在运行时动态地将预定义的代码片段(通知)插入到程序的特定位置(连接点/切入点)上,以实现跨多个模块和对象的横切关注点的重用。
2.3 目标
在不改变原来代码的情况下,对功能进行增强
AOP 的一个重要功能是日志记录。通过定义一个日志切面(Aspect),在方法调用前后插入日志记录的通知(Advice),并将这些通知应用于特定的连接点(Pointcut),可以实现在不修改核心业务逻辑的情况下,将日志记录逻辑与业务逻辑解耦和分离。
2.4 实现步骤
2.4.1 导入AOP的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.4.2 创建一个类,标记为切面类 (@Component, @Aspect)
@Component
@Aspect
public class MyAspect {
//省略
}
2.4.3 定义方法,加强被代理的方法 (确定通知类型, 确定切入点【管理哪一些方法】)
@Around
2.5 通知
2.5.1 通知类型
指怎么去管理被代理的方法
确定我们增强的功能是放在【目标】方法执行前 、 后 、 前后 、 返回后、异常之后执行
分别为:@Before, @After, @Around, @AfterReturning, @AfterThrowing
其中@Around比较常用, 该通知在【目标】 方法前后都会执行, 此时执行为动态代理对象执行, 需传入参数 ProceedingJoinPoint , 执行原方法为ProceedingJoinPoint的方法proceed() ,返回值类型为 Object, 此时由真实对象执行
2.5.2 通知执行顺序
执行顺序为
@Around(开始) @Before, @AfterReturning, @After, @AfterThrowing/@Around(结束)
public class MyAspect {
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void before(){
log.info("执行了before~~~");
}
@After("pointCut()")
public void after(){
log.info("执行了after~~~");
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pj) throws Throwable { // 是一定要放入一个 ProceedingJoinPoint
log.info("around前~~~~");
Object result = pj.proceed();
log.info("around后..");
return result;
}
@AfterReturning("pointCut()")
public void afterReturning(){
log.info("执行了AfterReturning~~~");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
log.info("执行了AfterThrowing~~~");
}
}
2.5.3 多个切面的执行顺序
如果同时定义多个切面, 这里定义了三个
则执行顺序应开始时从上往下按通知顺序执行, 结束时从下往上按通知顺序执行, 如图:
2.6 切入点表达式
指定需要管理哪一些方法
2.6.1 executiion("* com.example.service..(..)")
使用场景: 如果是通配的情况下
2.6.2 @annotation(注解的类全名)
使用精确的方法上
1) 需要创建一个anno类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
}
2) 写入到切入点
@Pointcut("@annotation(com.itheima.annotation.MyAnno)")
public void pt() {}
注意:注解加载实现类的方法上, 如注解加载接口上的方法则不会产生作用
2.7 连接点 ProceedingJoinPoint
通过连接点,可以获取当前方法的类、方法全名,参数,返回值。调用方法
2.8 常用概念
-
连接点(Join Point):程序执行过程中的特定位置,例如方法调用、异常抛出等。
-
切入点(Pointcut):在连接点中选择一部分具体的位置,以便将通知应用到这些位置上。切入点使用表达式指定要匹配的连接点。
-
通知(Advice):具体的代码片段,定义了在连接点上执行的操作。常见的通知类型有前置通知(Before advice)、后置通知(After advice)、环绕通知(Around advice)、异常通知(After-throwing advice)和返回通知(After-returning advice)。
-
切面(Aspect):切面是通知和切入点的结合,它定义了在哪里和何时应用通知。切面是一个模块化的单元,可用于表示横切关注点的逻辑。
-
目标对象(Target Object):被切面所通知的对象,它是核心业务逻辑所在的对象。
@Around("pt()") public void around(ProceedingJoinPoint pjp) throws Throwable { String name = pjp.getTarget().getClass().getName(); Signature signature = pjp.getSignature(); Object[] args = pjp.getArgs(); Object proceed = pjp.proceed(); log.info("全类名 :"+name); log.info("方法名: "+signature); log.info("参数名: "+ Arrays.toString(args)); log.info("结果: "+proceed); }
2.9 常见问题
2.9.1 在AOP @Around中捕获JoinPoint的异常
@Around("execution(* com.example.service.DeptService.*(..))")
public Object loging(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
try {
Object proceed = pjp.proceed();
long end = System.currentTimeMillis();
// 此处省略日志相关的代码
return proceed;
} catch (Throwable e) {
// 输出异常信息
log.error("异常信息:", e);
// 触发回滚操作
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw e;
}
}
2.9.2 使用AOP实现事务回滚
使用 AOP 的方式可以实现事务回滚,并在删除部门的方法中记录操作日志。如果出现异常,会回滚事务并记录异常操作日志,如果执行成功,则记录正常操作日志。
主程序捕获异常
@Transactional (rollbackFor = Exception.class)
@Override
public void deleteDept(List<Integer> ids) {
ids.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
deptMapper.deleteDept(integer);
try {
int i = 1/0;
} catch (Exception e) {
e.printStackTrace();
}
empMapper.deleteEmp(integer);
}
});
}
AOP
@Aspect
@Component
@Slf4j
public class AILOG {
// private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);
@Autowired
private OperateLogService operateLogService;
@Around("execution(* com.example.service.DeptService.deleteDept(..))")
public Object logAndRollback(ProceedingJoinPoint pjp) throws Throwable {
try {
Object result = pjp.proceed();
saveOperateLog(pjp, result);
return result;
} catch (Exception e) {
//捕获异常
handleException(pjp, e);
throw e;
}
}
private void saveOperateLog(ProceedingJoinPoint pjp, Object result) {
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
// 创建操作日志对象
OperateLog operateLog = new OperateLog();
operateLog.setClassName(className);
operateLog.setMethodName(methodName);
operateLog.setOperateTime(LocalDateTime.now());
operateLog.setMethodParams(Arrays.toString(args));
operateLog.setReturnValue(result == null ? null : result.toString());
// 保存操作日志
operateLogService.insert(operateLog);
}
private void handleException(ProceedingJoinPoint pjp, Exception e) {
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
// 创建操作日志对象
OperateLog operateLog = new OperateLog();
operateLog.setClassName(className);
operateLog.setMethodName(methodName);
operateLog.setOperateTime(LocalDateTime.now());
operateLog.setMethodParams(Arrays.toString(args));
operateLog.setReturnValue("Exception: " + e.getMessage());
// 保存异常操作日志
operateLogService.insert(operateLog);
// 打印异常堆栈信息
log.error("Exception during method execution",e);
// logger.error("Exception during method execution:", e);
// 触发事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}