Spring boot事物管理&AOP 笔记

Spring事务管理

事务 是一组操作的集合,它是一个不可分割的工作单位,这些操作 要么同时成功,要么同时失败

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

事务的打开、回滚和提交是由事务管理器来完成的,我们使用不同的数据库访问框架,就要使用与之对应的事务管理器。在Spring Boot中,当你添加了数据库访问框架的起步依赖时,它就会进行自动配置,即自动实例化正确的事务管理器。

对于声明式事务,是使用@Transactional进行标注的。这个注解可以标注在类或者方法上。

  • 当它标注在类上时,代表这个类所有公共(public)非静态的方法都将启用事务功能。

  • 当它标注在方法上时,代表这个方法将启用事务功能。

另外,在@Transactional注解上,我们可以使用isolation属性声明事务的隔离级别,使用propagation属性声明事务的传播机制。

@Transactional // spring事务管理
@Override
public void delete(Integer id) {
    deptMapper.deleteById(id);
    
	int  id = 1/0; // 发生异常会回滚
    
    empMapper.deleteByDeptId(id);
}

日志开关配置

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

rollbackFor

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

@Transactional(rollbackFor = Exception.class) // spring事务管理 回滚所有异常

propagation

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

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
	deptLogMapper.insert(deptLog);
}
@Transactional(rollbackFor = Exception.class) // spring事务管理
@Override
public void delete(Integer id) {
    try {
        deptMapper.deleteById(id); // 删除部门
        int d = 1/0;
        empMapper.deleteByDeptId(id); // 删除部门下的员工
    } finally {
        DeptLog deptLog = new DeptLog();
        deptLog.setCreateTime(LocalDateTime.now());
        deptLog.setDescription("执行了解散部门操作,本次解散的是" + id + "部门号");
        deptLogService.insert(deptLog); // 记录日志操作
    }
}

事物属性-传播行为

REQUIRED:大部分情况下都是用该传播行为即可。(如果当前没有事务,则新建一个事务;如果已存在一个事务,则加入到这个事务中。这是最常见的选择。)
REQUIRES_NEW:当我们不希望事务之间相互影响时,可以使用该传播行为。比如:下订单前需要记录日志,不论订单保存成功与
否,都需要保证日志记录能够记录成功。(新建事务,如果当前存在事务,则把当前事务挂起。)

Spring事务管理的亮点在于声明式事务管理,它允许我们通过声明的方式,在IoC配置中指定事务的边界和事务属性,Spring会自动在指定的事务边界上应用事务属性。相对于编程式事务来说,这种方式十分的方便,只需要在需要做事务管理的方法上,增加@Transactional注解,以声明事务特征即可。

AOP

AOP

Aspect Oriented Programming(面向切面编程、面向方面编程),其实就是面向特定方法编程。

AOP(Aspect Oriented Programing)是面向切面编程思想,这种思想是对OOP的补充,它可以在OOP的基础上进一步提高编程的效率。简单来说,它可以统一解决一批组件的共性需求(如权限检查、记录日志、事务管理等)。在AOP思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方、什么时机调用。当满足调用条件时,AOP会将该业务代码织入到我们指定的位置,从而统一解决了问题,又不需要修改这一批组件的代码。

AOP应用场景

记录操作日志、权限控制、事务管理……

Spring AOP为IoC的使用提供了更多的便利,一方面,应用可以直接使用AOP的功能,设计应用的横切关注点,把跨越应用程序多个模块的功能抽象出来,并通过简单的AOP的使用,灵活地编制到模块中,比如可以通过AOP实现应用程序中的日志功能。另一方面,在Spring内部,一些支持模块也是通过Spring AOP来实现的,比如事务处理。从这两个角度就已经可以看到Spring AOP的核心地位了。

优势

代码无入侵、减少重复代码、提高开发效率、维护方便

SpringAOP开发步骤:

  • 引入依赖

  • 根据需要编写代码

AOP实例——统计各个业务层耗时

<!--AOP-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Slf4j
@Component
@Aspect //AOP类
public class TimeAspect {

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

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

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

    	return result;
    }
}

AOP核心概念

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

  • 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)

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

  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)

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

详细术语:

  • 连接点(join point):对应的是具体被拦截的对象,因为Spring只能支持方法,所以被拦截的对象往往就是指特定的方法,AOP将通过动态代理技术把它织入对应的流程中。

  • 切点(point cut):有时候,我们的切面不单单应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点。切点就是提供这样一个功能的概念。

  • 通知(advice):就是按照约定的流程下的方法,分为前置通知、后置通知、环绕通知、事后返回通知和异常通知,它会根据约定织入流程中。

  • 目标对象(target):即被代理对象。

  • 引入(introduction):是指引入新的类和其方法,增强现有Bean的功能。

  • 织入(weaving):它是一个通过动态代理技术,为原有服务对象生成代理对象,然后将与切点定义匹配的连接点拦截,并按约定将各类通知织入约定流程的过程。

  • 切面(aspect):是一个可以定义切点、各类通知和引入的内容,SpringAOP将通过它的信息来增强Bean的功能或者将对应的方法织入流程。

Spring AOP:

AOP可以有多种实现方式,而Spring AOP支持如下两种实现方式。

  • JDK动态代理:这是Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用这种方式,在接口的代理实例中织入代码。

  • CGLib动态代理:采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象不存在接口时,Spring AOP就会采用这种方式,在子类实例中织入代码。

通知(advice)

通知类型:

  1. @Around: 环绕通知,重点,此注解标注的通知方法在目标方法前、后都被执行
  2. @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  3. @After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  4. @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  5. @AfterThrowing: 异常后通知,此注解标注的通知方法发生异常后执行
  • @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。
package whopxx.start.aop;

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

@Slf4j
@Component
@Aspect
public class MyAspect {


    @Pointcut("execution(* whopxx.start.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 ...");
    }
}

通知执行顺序:

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

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

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

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

@Slf4j
@Component
@Aspect
@Order(1) // 数字越小越先执行
public class MyAspect {
切入点表达式

切入点表达式

  • 描述切入点方法的一种表达式

  • 作用:主要用来决定项目中的哪些方法需要加入通知

  • 常见形式:

    1. execution(…):根据方法的签名来匹配
    2. @annotaion(…):根据注解匹配

切入点表达式-execution(“”)

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

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

  • 其中带**?**的表示可以省略的部分 -->访问修饰符、包名.类名、throws 异常(注意是方法声明上抛出的异常)

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

    • *:单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
    • ..:多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
@Pointcut("execution(public java.util.List whopxx.start.service.impl.DeptServiceImpl.GetInfo())")
public void pt(){}
@Before("execution(* whopxx.start.service.impl.DeptServiceImpl.*(..))")
public void before(){
	log.info("before ...");
}
  • 所有业务方法名命名时尽量规范,方便切入点表达式快速匹配.如:查询类方法都是 find 开头,更新类方法都是 update开头.
  • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
  • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如: 包名匹配尽量不使用 …,使用*匹配单个包。

切入点表达式-@annotation(“”)

@annotation(注解全类名)

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

@annotation(注解全类名)

自定义一个注解,起标识作用

package whopxx.start.aop;

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 MyLog {
}

对应需要起作用的方法上加上注解

@MyLog
@Override
public List<Dept> GetInfo() {
    return deptMapper.GetInfo();
}

匹配切入点

@Pointcut("@annotation(whopxx.start.aop.MyLog)")
public void pt(){}

这种基于注解的方式比较灵活

连接点

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

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

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

    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info("M2 --- before");
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("M2 around before ...");
        // 1. 获取目标对象的类名
        String className = joinPoint.getTarget().getClass().getName();
        log.info("目标对象类名:{}",className);
        // 2. 获取目标的方法名
        String methodName = joinPoint.getSignature().getName();
        log.info("目标方法名:{}",methodName);
        // 3. 获取目标方法运行时传入的参数
        Object[] arg = joinPoint.getArgs();
        log.info("目标方法运行时传入的参数:{}",arg);
        // 4.放行 目标方法执行
        Object result = joinPoint.proceed(); // 调用原始目标方法
        // 5. 过去目标方法运行的返回值
        log.info("目标运行的返回值:{}",result);

        log.info("M2 around after ...");

        return result;
    }
}
基于AOP操作日志记录案例

数据库建表

-- 操作日志表
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 '操作日志表';

对应实体类

@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; //操作耗时
}

Mapper接口

@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);

}

AOP

采用@annotation注解标记的形式选择需要记录日志操作的方法

自定义注解

@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Target(ElementType.METHOD) // 作用于方法
public @interface Log {
}

切面类

package whopxx.start.anno;

import com.alibaba.fastjson.JSONObject;
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 whopxx.start.mapper.OperateLogMapper;
import whopxx.start.pojo.OperateLog;
import whopxx.start.utils.JwtUtils;

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

@Aspect // 切面类
@Slf4j
@Component
public class LogAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private OperateLogMapper operateLogMapper;

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

        // 操作时间
        LocalDateTime operatorTime = 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 constTime =end - begin;

        // 记录操作日志
        OperateLog operateLog = new OperateLog(null,operatorUser,operatorTime,className,methodName,methodParams,returnValue,constTime);
        operateLogMapper.insert(operateLog);

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

}
  • 10
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
springboot学习笔记 spring基础 Spring概述 Spring的简史 xml配置 注解配置 java配置 Spring概述 Spring的模块 核心容器CoreContainer Spring-Core Spring-Beans Spring-Context Spring-Context-Support Spring-Expression AOP Spring-AOP Spring-Aspects Messaging Spring-Messaging WEB Spring-Web Spring-Webmvc Spring-WebSocket Spring-Webmvc-Portlet 数据访问/集成(DataAccess/Intefration) Spring-JDBC Spring-TX Spring-ORM Spring-OXM Spring-JMS Spring的生态 Spring Boot Spring XD Spring Cloud Spring Data Spring Integration Spring Batch Spring Security Spring HATEOAS Spring Social Spring AMQP Spring Mobile Spring for Android Spring Web Flow Spring Web Services Spring LDAP Spring Session Spring项目快速搭建 Maven简介 Maven安装 Maven的pom.xml dependencies dependency 变量定义 编译插件 Spring项目的搭建 Spring Tool Suite https://spring.io/tools/sts/all IntelliJ IDEA NetBeans https://netbeans.org/downloads/ Spring基础配置 依赖注入 声明Bean的注解 @Component组件,没有明确的角色 @Service在业务逻辑层(service层) @Repository在数据访问层(dao层) @Controller在展现层(MVC→SpringMVC) 注入Bean的注解 @Autowired:Spring提供的注解 @Inject:JSR-330提供的注解 @Resource:JSR-250提供的注解 Java配置 @Configuration声明当前类是一个配置类 @Bean注解在方法上,声明当前方法的返回值为一个Bean AOP @Aspect 声明是一个切面 拦截规则@After @Before @Around PointCut JoinPoint Spring常用配置 Bean的Scope Singleton Prototype Request Session GlobalSession SpringEL和资源调用 注入普通字符 注入操作系统属性 注入表达式云算结果 注入其他Bean的属性 注入文件内容 注入网址内容 注入属性文件 Bean的初始化和销毁 Java配置方式 注解方式 Profile @Profile 通过设定jvm的spring.profiles.active参数 web项目设置在Servlet的context parameter中 事件Application Event 自定义事件,集成ApplicationEvent 定义事件监听器,实现ApplicationListener 使用容器发布事件 Spring高级话题 Spring Aware BeanNameAware BeanFactoryAware

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值