Spring事务管理、SpringAop

目录

​编辑

Spring事务管理

注解:@Transactional

rollbackFor

事务属性-传播行为

propagation

SpringAOP

 AOP核心概念

通知类型

通知顺序

切入点表达式

 切入点表达式-execution

切入点表达式-@annotation

连接点

​编辑

将案例中 增、删、改 相关接口的操作日志记录到数据库表中 


Spring事务管理

注解:@Transactional

位置:业务(service)层的方法上、类上、接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行完毕,提交事务;出现异常,回滚事务

配置文件

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug
 @Transactional//spring事务管理
    @Override
    public void delete(Integer id) {
        deptMapper.deleteById(id);//根据id删除部门数据
        int a=1/0;//模拟异常
        empMapper.deleteById(id);根据部门ID删除该部门下的员工
    }

 

 不加@Transactional这个注解的话,出现了异常之后,只删除了部门的数据,该部门下的员工数据并没有被删除

rollbackFor

默认情况下,只有出现 RuntimeException 才回滚异常。rolbackFor属性用于控制出现何种异常类型,回滚事务

如果异常是这样的话,是不会回滚事务的,部门数据还是被删除了 

而之前属于算数异常

算数异常父类是 RuntimeException

这样就可以了

事务属性-传播行为

propagation

事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。 

REQUIRED        【默认值】需要事务,有则加入,无则创建新事务
REQUIRES_NEW        需要新事务,无论有无,总是创建新事务
SUPPORTS        支持事务,有则加入,无则在无事务状态中运行
NOT SUPPORTED       不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY        必须有事务,否则抛异常
NEVER        必须没事务,否则抛异常

REQUIRED:大部分情况下都是用该传播行为即可

REQUIRES_NEW:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。 

SpringAOP

快速入门:统计各个业务层方法执行耗时 

导入依赖:在pom.xml中导入AOP的依赖

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

编写AOP程序:针对于特定方法根据业务需要进行编程

package com.example.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;

@Slf4j
@Component
@Aspect//Aop类
public class TimeAspect {
    @Around("execution(* com.example.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;
    }
}

 AOP核心概念

连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)

通知类型

@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

@AfterReturning :返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行

@AfterThrowing :异常后通知,此注解标注的通知方法发生异常后执行

 可以用@Pointcut优化一下

@PointCut
该注解的作用是将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。 

 

private:仅能在当前切面类中引用该表达式

public:在其他外部的切面类中也可以引用该表达式

注意 

@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行

@Around环绕通知方法的返回值,必须指定为0bject,来接收原始方法的返回值。

通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行 

1.不同切面类中,默认按照切面类的类名字母排序:

目标方法前的通知方法:字母排名靠前的先执行

目标方法后的通知方法:字母排名靠前的后执行

2.用 @Order(数字)加在切面类上来控制顺序

目标方法前的通知方法:数字小的先执行

目标方法后的通知方法:数字小的后执行 

切入点表达式

切入点表达式:描述切入点方法的一种表达式作用:主要用来决定项目中的哪些方法需要加入通知
常见形式:
1. execution(.):根据方法的签名来匹配
2.@annotation(…):根据注解匹配

 切入点表达式-execution

execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数)throws 异常?)

其中带?的表示可以省略的部分
访问修饰符:可省路(比如:public、protected)
包名.类名: 可省略(不建议省略)
throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

可以使用通配符描述切入点

单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

execution(* com.*.service.*.update*(*))

多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

execution(* com.example..DeptService.*(..))

 就是通配DeptService接口下的所有方法,但是要求这个方法的返回值必须是void,而且这个方法中必须有一个Integer类型的参数

书写建议:

所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法都是 find 开头,更新类方法都是 update开头

描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。

在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用…,使用*匹配单个包,。

切入点表达式-@annotation

@annotation切入点表达式,用于匹配标识有特定注解的方法,

先自定义一个注解

这就简化了

 

连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于 @Around 通知,获取连接点信息只能使用ProceedingJoinPoint
  • 对于其他四种通知,获取连接点信息只能使用 JoinPoint, 它是 ProceedingJoinPoint 的父类型

around类型 

before类型 

将案例中 增、删、改 相关接口的操作日志记录到数据库表中 

自定义注解

package com.example.Anno;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}

 定义切面类

package com.example.Aop;

import com.alibaba.fastjson.JSONObject;
import com.example.Mapper.OperateLogMapper;
import com.example.Pojo.OperateLog;
import com.example.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;

@Slf4j
@Component
@Aspect//切面类
public class LogAspect {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private OperateLogMapper operateLogMapper;
    @Around("@annotation(com.example.Anno.Log)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        //操作人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;
    }
}

还要在增删改操作上加上自定义注解@Log


努力遇见更好的自己!!!

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力敲代码的小火龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值