事务管理和AOP

事务管理&A O P


事务管理

事务是一组操作的集合,他是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。在之前的MySQL数据库的使用中就使用到了这个知识点。具体操作如下。

事务的操作

image-20240802093143093

通常用在需要同时成功或者失败的操作上面,例如删除部门表中的部门和部门里面的员工的操作。

例如在删除K-ON!部的时候时候,就应该同时删除部门里面的城南员工。

image-20240802093655261

image-20240802093726668

delete from dept where id = 7;
delete from emp where dept_id==7;

正常情况下,这两句sql语句会正常执行,并删除古典文学部和城南这位员工,但是如果我们在删除员工1的时候故意写错语法,那么就会出现删除部门成功,但是部门中的成员依旧存在的情况。

image-20240802094433018

这个时候就可以使用事务来解决。

start transaction ;
delete from dept where id = 9;
delete from emp where dept_id=9;
commit ;
rollback ;

先执行 start transaction 语句开启事务,然后执行下面两句数据库操作语句。如果成功的话,就可以执行 commit 语句提交事务,将数据写入数据库。如果失败,则可以执行 rollback语句进行回滚操作。

SpringBoot中的事务功能

案例分析

image-20240802095425089

SpringBoot中已经集成好了事务功能,我们只需要在需要使用的方法,类,或者接口上加上 @Transactional 注解就可以了。

Spring事务管理

image-20240802095858685

我们可以在配置文件中打开事务管理日志

logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

我们故意写一段错误的代码对它进行测试。

@Transactional
    @Override
    public void delete(Integer id) {
        deptMapper.delete(id);
        int i = 1/0;
        empMapper.deleteByDeptId(id);
    }

观察控制台的输出。

image-20240802100728430

可以看出执行了回滚操作。

值得一提的是,在@Transactional 注解中,可以给它转递参数,参数用于控制出现何种异常时,回滚事务。

image-20240802100957401

除了传递 rollbackFor参数,我们还可以传递propagation 参数,用于指定传播行为。

image-20240802101221240

通常用于被@Transactional 注解的方法去调用另一个也被次注解注解的方法。

image-20240802101343677

A O P

什么是AOP

image-20240802111539961

spring中的AOP主要是用了动态代理功能。要使用spring中的AOP,我们首先需要在配置文件中引入依赖。

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

然后再定义一个方法。

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * {@code @Author} 19667
 * {@code @create} 2024/8/2 11:06
 */
@Slf4j
@Component
@Aspect
public class TimeAspect {
    @Around("execution(* com.itheima.service.*.*(..))")
    public Object recodeTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long begin = System.currentTimeMillis();

        Object object = proceedingJoinPoint.proceed();

        long end = System.currentTimeMillis();

        log.info(proceedingJoinPoint.getSignature()+"执行时间:{}ms",end-begin);
        return object;
    }
}

注解解释

  • @Slf4j 主要作用是在日志输出语句。
  • @Component 将类交给IOC容器管理。
  • @Aspect 申明这是一个切面类。
  • @Around 环绕通知。

AOP的应用场景与优势

image-20240802125446341

AOP核心概念

image-20240802113148614

AOP执行流程

image-20240802113327300

Spring框架下的AOP只要是通过动态代理的技术去实行的,可以理解为它先去生成了一个代理对象,在对象中加入自己要实现的逻辑,然后再去调用目标对象的对应方法。当IOC容器进行自动注入的时候,会注入代理对象

AOP进阶

通知类型

image-20240802115622698

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * {@code @Author} 19667
 * {@code @create} 2024/8/2 11:57
 */
@Slf4j
@Component
@Aspect
public class Aspect1 {

    @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 object = proceedingJoinPoint.proceed();
        log.info("around after");
        return object;
    }

    @After("pt()")
    public void after(){
        log.info("after");
    }

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

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

注解@PintCut

image-20240802125337899

通知顺序

image-20240802144445344

切入点表达式

在AOP中我们通常需要在通知类型的注解中编写切入点表达式来控制需要加入通知的方法。

image-20240802144824186

execution 表达式

image-20240802144936799

execution 表达式中,我们还可以通过通配符来扩大匹配的范围。

image-20240802145107585

aunotation 表达式

如果要匹配的方法使用execution 表达式太过复杂,我们也可以使用aunotation 表达式。

具体使用方法就是自定义一个注解。

package com.itheima.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * {@code @Author} 19667
 * {@code @create} 2024/8/2 14:11
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}

@Retention 注解配置次注解在什么时候生效

@Target注解配置这个注解加在什么上面,例如。

/**
     * 新增员工
     * @param emp
     * @return
     */
    @Log
    @PostMapping
    public Result save(@RequestBody Emp emp){
        empService.addEmp(emp);
        return Result.success();
    }

这样我们就可以使用@Around("@annotation(com.itheima.anno.Log)")来指定方法加入通知。

连接点

image-20240802145824035

在案例中使用AOP

image-20240802145920970

由于增删改的方法的方法名没有太多共通性,决定使用aunotation

定义Log注解

package com.itheima.anno;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * {@code @Author} 19667
 * {@code @create} 2024/8/2 14:11
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}

构造AOP方法

package com.itheima.aop;

import com.alibaba.fastjson.JSONObject;
import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
import com.itheima.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * {@code @Author} 19667
 * {@code @create} 2024/8/2 14:13
 */
@Slf4j
@Component
@Aspect
public class LogAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private OperateLogMapper operateLogMapper;

    @Around("@annotation(com.itheima.anno.Log)")
    public Object recodeLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //获取id
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);
        Integer operateUser = (Integer) claims.get("id");
        //时间
        LocalDateTime operateTime = LocalDateTime.now();
        //类名
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        //方法名
        String methodName = proceedingJoinPoint.getSignature().getName();
        //操作方法参数
        String methodParams = Arrays.toString(proceedingJoinPoint.getArgs());
        //操作返回值
        long begin = System.currentTimeMillis();
        Object object = proceedingJoinPoint.proceed();
        long end = System.currentTimeMillis();
        String returnValue = JSONObject.toJSONString(object);
        //操作耗时
        long costTime = end - begin;
        OperateLog operateLog = new OperateLog(null, operateUser, operateTime, className, methodName, methodParams, returnValue, costTime);
        operateLogMapper.insert(operateLog);
        log.info("AOP记录日志:{}", operateLog);
        return object;
    }
}

值得一提的是,我们想要获取操作的用户的id的时候,需要用到HttpServletRequest 但是在AOP中,对方法的参数有严格的限制,所以我们选择创建接口加上自动注入的形式来获取。

在增删改的方法上加上注解

进行测试

image-20240802150445191

至此,功能实现成功

eTime, className, methodName, methodParams, returnValue, costTime);
operateLogMapper.insert(operateLog);
log.info(“AOP记录日志:{}”, operateLog);
return object;
}
}


值得一提的是,我们想要获取操作的用户的id的时候,需要用到`HttpServletRequest` 但是在AOP中,对**方法的参数**有严格的限制,所以我们选择创建接口加上自动注入的形式来获取。

## 在增删改的方法上加上注解

略

## 进行测试

[外链图片转存中...(img-4uBJUpHS-1722585401056)]

至此,功能实现成功

  • 30
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值