事务和AOP

65 阅读6分钟

事务和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 多个切面的执行顺序

​ 如果同时定义多个切面, 这里定义了三个

image-20230916002313388转存失败,建议直接上传图片文件

则执行顺序应开始时从上往下按通知顺序执行, 结束时从下往上按通知顺序执行, 如图:

image-20230916002457020转存失败,建议直接上传图片文件

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 常用概念
  1. 连接点(Join Point):程序执行过程中的特定位置,例如方法调用、异常抛出等。

  2. 切入点(Pointcut):在连接点中选择一部分具体的位置,以便将通知应用到这些位置上。切入点使用表达式指定要匹配的连接点。

  3. 通知(Advice):具体的代码片段,定义了在连接点上执行的操作。常见的通知类型有前置通知(Before advice)、后置通知(After advice)、环绕通知(Around advice)、异常通知(After-throwing advice)和返回通知(After-returning advice)。

  4. 切面(Aspect):切面是通知和切入点的结合,它定义了在哪里和何时应用通知。切面是一个模块化的单元,可用于表示横切关注点的逻辑。

  5. 目标对象(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();
        }
    }