【javaweb】学习日记Day13 - AOP 事务管理 切入点 连接点

目录

一、完善解散部门功能

[二、spring 事务](#二、spring 事务)

[(1)@Transactional 事务管理](#(1)@Transactional 事务管理)

[① rollbackFor 控制异常类型](#① rollbackFor 控制异常类型)

[② propagation 事务传播控制](#② propagation 事务传播控制)

1、定义解散部门操作日记

三、AOP基础

1、概述

2、快速入门

(1)案例:统计各个业务层方法的执行耗时

[① 引入AOP依赖](#① 引入AOP依赖)

[② 建立AOP类](#② 建立AOP类)

3、AOP核心概念

(1)AOP的执行流程

四、AOP进阶

1、通知类型

[(1)@PointCut 公共切点表达式](#(1)@PointCut 公共切点表达式)

2、通知顺序

3、切入点表达式

(1)execution

(2)@annotation

4、连接点

五、AOP案例

(1)引入AOP依赖

(2)在数据库里建操作日记记录表

(3)定义数据库表对应的实体类

(4)定义对应的Mapper接口

(5)定义注解

(6)完成AOP类编写

(7)给需要匹配的增删改方法加上注解


一、完善解散部门功能

删除部门时,应该把部门下相应的员工也一并删除

注意:数据库不推荐物理外键,一般都是逻辑外键

step 1:改写Dept的Service层

java 复制代码
    @Override
    public void delete(Integer id) {
        deptMapper.deleteById(id); //根据部门id删除部门

        empMapper.deleteByDeptId(id); //根据部门id删除该部门下对应的员工
    }

step 2:完善Emp的mapper层

java 复制代码
    //根据部门id删除对应员工
    @Delete("delete from emp where dept_id = #{deptId}")
    void deleteByDeptId(Integer deptId);

二、spring 事务

当中间出现异常时,会出现,异常前的语句执行了,但是异常后的语句没有成功执行,会出现bug

因此我们需要进行事务回滚(事务回滚指的是当发生错误或异常时,事务能够自动地撤销已经执行的操作,返回到事务开始之前的状态)

(1)@Transactional 事务管理

① rollbackFor 控制异常类型

  • 作用:控制出现何种异常类型,事务回滚
  • 默认情况下,只有出现RuntimeException才会回滚异常
java 复制代码
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(Integer id) {
        deptMapper.deleteById(id); //根据部门id删除部门

        empMapper.deleteByDeptId(id); //根据部门id删除该部门下对应的员工
    }

② propagation 事务传播控制

作用:当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制

|-------------------|---------------------------------------|
| 属性值 | 含义 |
| REQUIRED(默认值) | 需要事务,有则加入,无则创建新事务 |
| REQUIRES_NEW | 需要新事务,无论有无事务,总是创建新事务, 当我们不希望事务相互影响时使用 |

这里我们运用一个案例进行详细说明:

要求解散部门时,无论解散成功or失败,都要记录操作日志

1、定义解散部门操作日记

(1)DeptLog实体类

java 复制代码
// 解散部门日志
@Data //@Data注解的主要作用是提高代码的简洁,使用这个注解可以省去实体类中大量的get()、 set()、 toString()等方法
@NoArgsConstructor
@AllArgsConstructor
public class DeptLog {

    private Integer id;
    private LocalDateTime createTime;
    private String description;
}

(2)DeptLogService

java 复制代码
public interface DeptLogService {

    //插入日志
    void insert(DeptLog deptLog);
}
java 复制代码
public class DeptLogServiceImpl implements DeptLogService {

    @Autowired
    private DeptLogMapper deptLogMapper;

    @Transactional
    @Override
    public void insert(DeptLog deptLog) {
        deptLogMapper.insert(deptLog);
    }
}

(3)DeptLogMapper

java 复制代码
@Mapper
public interface DeptLogMapper {

    @Insert("insert into dept_log(create_time,description) values (#{createTime},#{description})")
    void insert(DeptLog deptLog);
}
  • 因为若不指定propagation的值,默认为REQUIRED ,即为++若需要新事务,则无需再创建,直接加入已有事务++,也就是insert方法加入到delete方法的事务中
  • 此时若delete事务出现异常,整个事务发生回滚,因此也不会有日志记录
  • 因此我们需要在insert上指定propagation的值为REQUIRES_NEW ,即++若需要新事务,则再开一个新事务++,当delete事务出现异常时,只在delete事务中发生回滚,insert事务正常运行,日志正常记录
java 复制代码
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(Integer id) {
        try{
            deptMapper.deleteById(id); //根据部门id删除部门

            empMapper.deleteByDeptId(id); //根据部门id删除该部门下对应的员工
        }finally {
            DeptLog deptLog = new DeptLog();
            deptLog.setCreateTime(LocalDateTime.now());
            deptLog.setDescription("本次解散的是"+id+"号部门");
            deptLogService.insert(deptLog);
        }
    }
java 复制代码
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insert(DeptLog deptLog) {
        deptLogMapper.insert(deptLog);
    }

三、AOP基础

1、概述

开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决

AOP 的主要作用就是在不侵入原有程序的基础上实现对原有功能的增强

2、快速入门

(1)案例:统计各个业务层方法的执行耗时

① 引入AOP依赖
javascript 复制代码
        <!--AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
② 建立AOP类
javascript 复制代码
@Slf4j
@Component
@Aspect // AOP类
public class TimeAspect {

    @Around("execution(* com.itroye.service.*.*(..))") //切入点表达式
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        //记录开始时间
        long begin = System.currentTimeMillis();

        //调用原始方法
        Object result = joinPoint.proceed();

        //记录结束时间
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature()+"执行耗时:{}ms",end-begin);

        return result;
    }
}

3、AOP核心概念

  • 连接点 JoinPoint:可以被AOP控制的方法

  • 通知 Advice:指重复的逻辑,也就是共性功能

  • 切入点 PointCut:匹配连接点的条件,通知仅会在切入点方法执行时被应用

  • 切面 Aspect:通知+切入点

  • 目标对象 Target:通知所应用的对象

(1)AOP的执行流程

运行的不是原始的目标对象,而是基于目标对象所生成的代理对象

四、AOP进阶

1、通知类型

  • 前置通知(@Before):在目标方法执行前执行的通知。

  • 后置通知(@After):在目标方法执行后执行的通知,无论目标方法是否抛出异常都会执行。

  • 返回通知(@AfterReturning):在目标方法正常返回后执行的通知。

  • 异常通知(@AfterThrowing):在目标方法抛出异常后执行的通知。

  • 环绕通知(@Around):在目标方法执行前后都可以执行的通知,可以控制目标方法的执行。
    注意:

  • @Around 需要调用ProceedingJoinPoint.proceed()让原始方法执行,其他通知不需要目标方法执行

  • @Around 的返回值必须是Object,来接收原始方法的返回值

(1)@PointCut 公共切点表达式

javascript 复制代码
@Slf4j
@Component
@Aspect
public class MyAspect1 {

    @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public void pt(){}

    @Before("pt()")
    public void before(){
        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(){
        log.info("after ...");
    }

    @AfterReturning("pt()")
    public void afterReturning(){
        log.info("afterReturning ...");
    }

    @AfterThrowing("pt()")
    public void afterThrowing(){
        log.info("afterThrowing ...");
    }
}

2、通知顺序

3、切入点表达式

(1)execution

(2)@annotation

用于匹配标识有特定注解的方法

新建注解

javascript 复制代码
@Retention(RetentionPolicy.RUNTIME) //运行时机
@Target(ElementType.METHOD) //该注解可以定义在方法上
public @interface MyLog {
}

在需要切入的切入点方法上加上该注解

然后在切面处 @annotation(注解全类名),即可匹配拥有该注解的方法

4、连接点

连接点 就是可以被AOP控制的方法

  • 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等
    • 对于@Around通知,获取连接点信息只能用ProceedingJoinPoint
    • 对于其他四种通知,获取连接点信息只能用JoinPoint,它是ProceedingJoinPoint的父类型

五、AOP案例

思路分析:

  • 需要对所有Service的增删改方法添加统一功能,使用AOP技术 运用@Around环绕通知
  • 由于增删改方法名无规律,自定义@Log注解完成目标方法匹配

(1)引入AOP依赖

javascript 复制代码
        <!--AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

(2)在数据库里建操作日记记录表

javascript 复制代码
-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_user int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

(3)定义数据库表对应的实体类

javascript 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
    private Integer id; //ID
    private Integer operateUser; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时
}

(4)定义对应的Mapper接口

javascript 复制代码
@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}

(5)定义注解

javascript 复制代码
@Retention(RetentionPolicy.RUNTIME) //运行时机
@Target(ElementType.METHOD) //该注解可以定义在方法上
public @interface Log {
}

(6)完成AOP类编写

java 复制代码
@Slf4j
@Component
@Aspect //切面类
public class LogAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private OperateLogMapper operateLogMapper;

    @Around("@annotation(com.itroye.anno.Log)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        //操作人ID - 当前登录员工ID
        //获取请求头中的jwt令牌, 解析令牌
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);
        Integer operateUser = (Integer) claims.get("id");

        //操作时间
        LocalDateTime operateTime = LocalDateTime.now();

        //操作类名
        String className = joinPoint.getTarget().getClass().getName();

        //操作方法名
        String methodName = joinPoint.getSignature().getName();

        //操作方法参数
        Object[] args = joinPoint.getArgs();
        String methodParams = Arrays.toString(args);

        long begin = System.currentTimeMillis();
        //调用原始目标方法运行
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();

        //方法返回值
        String returnValue = JSONObject.toJSONString(result);

        //操作耗时
        Long costTime = end - begin;


        //记录操作日志
        OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
        operateLogMapper.insert(operateLog);

        log.info("AOP记录操作日志: {}" , operateLog);

        return result;
    }

}

(7)给需要匹配的增删改方法加上注解

这里是给Controller层加的注释!

保证返回值都是Result

相关推荐
坐吃山猪1 小时前
SpringBoot01-配置文件
java·开发语言
我叫汪枫2 小时前
《Java餐厅的待客之道:BIO, NIO, AIO三种服务模式的进化》
java·开发语言·nio
yaoxtao2 小时前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
liliangcsdn3 小时前
Leiden社区发现算法的学习和示例
学习·数据分析·知识图谱
Swift社区3 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT4 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy4 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss6 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续6 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升