目录
[1. AOP](#1. AOP)
[1.1 AOP 概念](#1.1 AOP 概念)
[1.2 AOP 核心概念](#1.2 AOP 核心概念)
[1.3 AOP 作用](#1.3 AOP 作用)
[2. AOP 详解](#2. AOP 详解)
[2.1 切点(Pointcut)](#2.1 切点(Pointcut))
[2.2 连接点(Join Point)](#2.2 连接点(Join Point))
[2.3 通知(Advice)](#2.3 通知(Advice))
[2.4 切面(Aspect)](#2.4 切面(Aspect))
[2.5 通知类型](#2.5 通知类型)
[2.5.1 @Around 环绕通知](#2.5.1 @Around 环绕通知)
[2.5.2 @Before 前置通知](#2.5.2 @Before 前置通知)
[2.5.3 @After 后置通知](#2.5.3 @After 后置通知)
[2.5.4 @AfterReturning 返回后通知](#2.5.4 @AfterReturning 返回后通知)
[2.5.5 @AfterThrowing 异常后通知](#2.5.5 @AfterThrowing 异常后通知)
[2.6 @Pointcut](#2.6 @Pointcut)
[2.7 切面优先级](#2.7 切面优先级)
[2.8 切面表达式](#2.8 切面表达式)
[2.8.1 execution()](#2.8.1 execution())
[2.8.2 @annotation](#2.8.2 @annotation)
[2.8.2.1 编写自定义注解](#2.8.2.1 编写自定义注解)
[2.8.2 @annotation 描述切点](#2.8.2 @annotation 描述切点)
[2.8.3 添加自定义注解](#2.8.3 添加自定义注解)
[3. Spring AOP 原理](#3. Spring AOP 原理)
[3.1 代理模式](#3.1 代理模式)
[3.1.1 静态代理](#3.1.1 静态代理)
[3.1.2 动态代理](#3.1.2 动态代理)
[3.1.3 Spring AOP 动态代理机制详解](#3.1.3 Spring AOP 动态代理机制详解)
[4. SpringBoot 统一功能处理](#4. SpringBoot 统一功能处理)
[4.1 拦截器](#4.1 拦截器)
[5. 统一数据返回格式](#5. 统一数据返回格式)
[5.1 注意事项](#5.1 注意事项)
[5.2 优点](#5.2 优点)
[6. 统一异常处理](#6. 统一异常处理)
[7. Spring 事务](#7. Spring 事务)
[7.1 事务概念](#7.1 事务概念)
[7.2 事务的操作](#7.2 事务的操作)
[7.3 Spring 事务实现](#7.3 Spring 事务实现)
[7.3.1 编程式事务 (手动写代码操作事务)](#7.3.1 编程式事务 (手动写代码操作事务))
[7.3.2 声明式事务 (通过注解自动开启和提交事务)](#7.3.2 声明式事务 (通过注解自动开启和提交事务))
[7.4 @Transactional 作用](#7.4 @Transactional 作用)
[7.5 事务传播机制](#7.5 事务传播机制)
[7.6 常见问题](#7.6 常见问题)
1. AOP
1.1 AOP 概念
• Aspect Oriented Programming (面向切面编程),AOP 是一种思想,它的实现方法有很多,如 Spring AOP, AspectJ, CGLIB 等。AOP 是对某一类事情的集中处理,其中 SpringBoot 统一功能处理就是 AOP 思想的实现。
引入AOP 依赖,在 pom.xml 文件中添加配置
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency
认识 AOP 程序
java
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
@Aspect
@Component
public class TimeAspect {
/**
* 记录⽅法耗时
*/
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
//记录⽅法执⾏开始时间
long begin = System.currentTimeMillis();
//执⾏原始⽅法
Object result = pjp.proceed();
//记录⽅法执⾏结束时间
long end = System.currentTimeMillis();
//记录⽅法执⾏耗时
log.info(pjp.getSignature() + "执⾏耗时: {}ms", end - begin);
return result;
}
}
1.2 AOP 核心概念
• @Aspect:表示这是一个切面类
• @Around:环绕通知,在目标方法前后都会被执行,后面的表达式表示作用域。也可以理解为表达对哪些方法增强
• ProceedingJoinPoint.proceed:表示让目标方法执行
1.3 AOP 作用
• 代码无入侵:不修改原始的业务方法,就可以对功能进行增强或者改变。
• 减少了重复代码,提高开发效率
• 维护方便
2. AOP 详解
2.1 切点(Pointcut)
切点也被称为切入点,Pointcut 用于标识程序中哪些 连接点(Join Point)(如方法调用、异常抛出)需要应用横切逻辑,上面的表达式 execution(* com.example.demo.controller.*.*(..)) 就是切点表达式。
2.2 连接点(Join Point)
连接点就是满足切点表达式规则的方法,也就是可以被 AOP 控制的方法。
以下 BookController 类下的所有方法都是连接点。
java
package com.example.demo.controller;
@RequestMapping("/book")
@RestController
public class BookController {
@RequestMapping("/addBook")
public Result addBook(BookInfo bookInfo) {
//...代码省略
}
@RequestMapping("/queryBookById")
public BookInfo queryBookById(Integer bookId){
//...代码省略
}
@RequestMapping("/updateBook")
public Result updateBook(BookInfo bookInfo) {
//...代码省略
}
}
2.3 通知(Advice)
通知就是具体要做的工作,指重复的逻辑,也就是共性功能。
比如红框中的代码就是通知

2.4 切面(Aspect)
切面 = 切点 (我想要拦截哪些方法) + 通知,切面所在的类我们一般称为 切面类

2.5 通知类型
Spring 中 AOP 有以下五种通知类型:
2.5.1 @Around 环绕通知
包裹目标方法,在方法执行前后均可插入逻辑。需要通过 ProceedingJoinPoint.proceed() 手动触发目标方法,可修改参数、捕获异常、修改返回值。
• 必须调用 proceed()
,否则目标方法不会执行。
• 必须返回结果(除非目标方法返回 void)。
java
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Around ⽅法开始执⾏");
Object result = joinPoint.proceed();
log.info("Around ⽅法结束执⾏");
return result;
}
2.5.2 @Before 前置通知
• 在目标方法 执行前 触发。
• 适用于参数校验、权限检查、日志记录等。
java
@Before("execution(* com.example.demo.controller.*.*(..))")
public void doBefore() {
log.info("执⾏ Before ⽅法");
}
2.5.3 @After 后置通知
• 在目标方法 执行后 触发(无论是否抛出异常)。
• 适用于资源清理(如关闭文件、释放连接)。
java
@After("execution(* com.example.demo.controller.*.*(..))")
public void doAfter() {
log.info("执⾏ After ⽅法");
}
2.5.4 @AfterReturning 返回后通知
• 仅在目标方法 正常返回 时触发(不处理异常)。
• 可获取方法返回值,通过 returning 属性绑定返回值
java
@AfterReturning("execution(* com.example.demo.controller.*.*(..))")
public void doAfterReturning() {
log.info("执⾏ AfterReturning ⽅法");
}
2.5.5 @AfterThrowing 异常后通知
• 仅在目标方法 抛出异常 时触发。
• 可捕获特定异常类型
• 通过 throwing 属性绑定异常对象
java
@AfterThrowing("execution(* com.example.demo.controller.*.*(..))")
public void doAfterThrowing() {
log.info("执⾏ doAfterThrowing ⽅法");
}
正常时顺序如下:

程序发生异常时,@AfterReturning 标识的方法不会执行,@AfterThrowing 标识的方法会执行,@Around 环绕通知中的环绕后代码逻辑不会执行。
异常时顺序如下:

2.6 @Pointcut
上述代码中存在大量的 "execution(* com.example.demo.controller.*.*(..))",对于勤奋的程序猿来说,能不能把这些公共重复的提取出来呢?我们可以通过 @Pointcut 注解,来把公共的切点表达式提取出来。
java
@Slf4j
@Aspect
@Component
public class AspectDemo {
//定义切点(公共的切点表达式)
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
//...代码省略
}
//后置通知
@After("pt()")
public void doAfter() {
//...代码省略
}
//返回后通知
@AfterReturning("pt()")
public void doAfterReturning() {
//...代码省略
}
//抛出异常后通知
@AfterThrowing("pt()")
public void doAfterThrowing() {
//...代码省略
}
//添加环绕通知
@Around("pt()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
//...代码省略
}
}
当 @Pointcut 修饰的切点被 private 修饰,仅能在当前切面类中使用,其他切面类要使用的时候,需要把 private 改成 public 修饰。
2.7 切面优先级
在一个项目中,多个切面类修饰同一个方法的情况下,我们可以通过 @Order 注解来决定切面类的优先级。Order 中的值越小,执行的优先级越高。
java
@Aspect
@Component
@Order(2)
public class AspectDemo2 {
//...代码省略
}
java
@Aspect
@Component
@Order(1)
public class AspectDemo3 {
//...代码省略
}
java
@Aspect
@Component
@Order(3)
public class AspectDemo4 {
//...代码省略
}
结果如下:

如果不使用 Order 来指定切面类顺序,则会按照项目中 切面类 的排序顺序。
2.8 切面表达式
2.8.1 execution()
execution(......) 根据方法的签名来匹配, 一般用于一个类中都需要被 AOP 增强的情况下。

切点表达式通配符表达:
" * " 表示匹配任意字符,只匹配一个元素," .. " 表示匹配多个连续的任意符号,可以通配任意层级的包,或者任意类型,任意个数的参数。
其中访问修饰符可以省略
2.8.2 @annotation
execution() 表达式更适合于有规则的,如果要匹配一个类中几个方法,这个时候我们可以使用自定义注解的方式来修饰,使用 @annotation 来描述这一类的切点。
实现步骤:
2.8.2.1 编写自定义注解
创建注解类
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {}
@Target 注解表明 Annotation 所修饰的对象范围
|-------------------|-------------------------|
| FIELD
| 标注字段(如 @Autowired
) |
| CONSTRUCTOR
| 标注构造方法 |
| LOCAL_VARIABLE
| 标注局部变量(较少使用) |
| ANNOTATION_TYPE
| 标注其他注解(元注解,如 @Target)
|
@Retention 指 Annotation 被保留的时间长短,表明注解的生命周期
特性 | SOURCE |
CLASS |
RUNTIME |
---|---|---|---|
存在阶段 | 源码 | 源码 + 字节码 | 源码 + 字节码 + 运行时 |
反射可获取性 | 不可获取 | 不可获取 | 可获取 |
常见用途 | 编译检查、代码生成 | 字节码处理工具 | 运行时动态处理 |
性能影响 | 无 | 无 | 反射可能带来性能开销 |
2.8.2 @annotation 描述切点
使用 @annotation 切点表达式定义切点
切⾯类代码如下: 表示只对 @MyAspect 注解生效
java
@Slf4j
@Component
@Aspect
public class MyAspectDemo {
//前置通知
@Before("@annotation(com.example.demo.aspect.MyAspect)")
public void before(){
log.info("MyAspect -> before ...");
}
//后置通知
@After("@annotation(com.example.demo.aspect.MyAspect)")
public void after(){
log.info("MyAspect -> after ...");
}
}
2.8.3 添加自定义注解
java
@MyAspect
@RequestMapping("/t1")
public String t1() {
return "t1";
}
@MyAspect
@RequestMapping("/u1")
public String u1(){
return "u1";
}
观察结果:

也可以基于 Spring API 来实现 AOP (通过 xml 配置的方法)
3. Spring AOP 原理
上面我们主要了解了 Spring AOP 的应用,接下来将介绍 Spring AOP 如何实现。
Spring AOP 基于动态代理来实现 AOP
3.1 代理模式
3.1.1 静态代理
静态代理:程序运行前,代理类的.class 文件就已经存在,静态代理在编译时就已经确定代理和被代理的关系,需要为每个被代理的类编写一个代理类。
下面通过一个简单的代码体现静态代理,以租房为例
(1) 定义接口:
java
public interface HouseSubject {
void rentHouse();
}
(2) 实现接口:
java
public class RealHouseSubject implements HouseSubject{
@Override
public void rentHouse() {
System.out.println("我是房东, 我出租房⼦");
}
}
(3) 代理 (中介帮房东出售房子)
java
public class HouseProxy implements HouseSubject{
//将被代理对象声明为成员变量
private HouseSubject houseSubject;
public HouseProxy(HouseSubject houseSubject) {
this.houseSubject = houseSubject;
}
@Override
public void rentHouse() {
//开始代理
System.out.println("我是中介, 开始代理");
//代理房东出租房⼦
houseSubject.rentHouse();
//代理结束
System.out.println("我是中介, 代理结束");
}
}
(4) 使用
java
public class StaticMain {
public static void main(String[] args) {
HouseSubject subject = new RealHouseSubject();
//创建代理类
HouseProxy proxy = new HouseProxy(subject);
//通过代理类访问⽬标⽅法
proxy.rentHouse();
}
}
结果如下:

由于代码过于死板,对目标每个方法的增强都是手动完成,所以日常中几乎看不见静态代理
3.1.2 动态代理
动态代理不需要预先编写代理类,运行时通过反射机制动态生成创建。动态代理的灵活性体现在他可以代理不同的接口或类,行为可以通过不同的InvocationHandler实现来改变,而不是代理本身在运行时改变行为。
Java 中提供了两种动态代理类: JDK动态代理和CGLIB动态代理
JDK 动态代理只能代理实现了接口的类,当不定义接口时,我们只能使用 CGLIB 动态代理来解决。CGLIB 是一个基于 ASM 的字节码生成库,允许我们在运行时对字节码进行修改和动态生成,CGLIB 通过继承的方式实现代理。
• 静态代理 :需提前写死,像定制工具------"一把钥匙开一把锁"。
• 动态代理 :运行时生成,像万能钥匙------"一把钥匙开所有锁"
3.1.3 Spring AOP 动态代理机制详解
Spring AOP 的代理方式由 目标对象是否实现接口 决定:
• JDK 动态代理:若目标对象实现了至少一个接口,默认使用 JDK 动态代理。
• CGLIB 动态代理:若目标对象未实现接口,默认使用 CGLIB 动态代理。
CGLIB的局限性:
• 无法代理 final 类或 final 方法(如 String 类)。
• 代理类会继承目标类,CGLIB 代理类会自动生成构造函数,**隐式调用父类无参构造函数,**因此目标类必须有可访问的无参构造函数,并不是子类一定要重写父类无参构造方法。
Spring Boot 2.x 及更高版本的默认行为:
• 无论目标对象是否实现接口,默认使用 CGLIB 代理。
• 可通过配置 spring.aop.proxy-target-class=false 切换回 JDK 动态代理(需目标对象实现接口)。
JDK 动态代理 vs CGLIB 代理
特性 | JDK 动态代理 | CGLIB 动态代理 |
---|---|---|
代理目标 | 只能代理接口 | 可代理类(通过继承目标类) |
性能 | 生成代理对象较快 | 生成代理对象较慢,但方法调用更快 |
依赖 | 无额外依赖(JDK 自带) | 需要引入 CGLIB 库(Spring 已内置) |
限制 | 目标对象必须实现接口 | 无法代理 final 类或 final 方法 |
Spring Boot 默认 | 需手动配置 proxy-target-class=false | 默认启用 |
proxyTargetClass 配置对代理方式的影响
proxyTargetClass | 目标对象情况 | 代理方式 | 说明 |
---|---|---|---|
false |
实现了接口 | JDK 代理 | 优先使用 JDK 动态代理(基于接口) |
false |
未实现接口(仅实现类) | CGLIB 代理 | JDK 代理无法代理无接口的类,自动回退到 CGLIB |
true |
实现了接口 | CGLIB 代理 | 强制使用 CGLIB 代理(基于继承),即使目标有接口 |
true |
未实现接口(仅实现类) | CGLIB 代理 | 唯一可行的代理方式 |
4. SpringBoot 统一功能处理
4.1 拦截器
概念:拦截器是 Spring 框架提供的核心功能之一,是 AOP 的一种实现,主要用来拦截用户的请求。
拦截器的使用分为两个步骤:
(1) 定义拦截器 :创建一个类实现 HandlerInterceptor 接口,并重写其所有方法
java
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");
}
}
• preHandle() :目标方法执行前执行,返回 true 才能继续执行后续操作
• postHandle() :目标方法执行后执行
(2) 注册拦截器
创建一个类实现 WebMvcConfigurer 接口,并重写 addInterceptors 方法。
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
//⾃定义的拦截器对象
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册⾃定义拦截器对象
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所
有请求)
}
}
addPathPatterns() 方法拦截器路径匹配如下:
模式 | 匹配层级 | 参数支持 | 示例路径 | 典型场景 |
---|---|---|---|---|
* |
1层 | 否 | /user |
首页/基础资源访问 |
/** |
任意层 | 否 | /user/login、reg |
API网关/静态资源路由 |
/book/* |
1层 | 否 | /book/detail |
固定二级菜单 |
/book/** |
任意层 | 否 | /book/detail/123 |
文件下载/复杂业务路径 |
/user/{id} |
1层 | 是 | /user/123 |
动态资源访问(用户中心) |
/user/{id}/** |
任意层 | 是 | /user/123/profile |
用户个人空间(含子页面) |
拦截器处理流程:
(1) 拦截器会在 Controller 之前进行相应的业务处理,请求会被拦截器截住,执行 preHandle() 方法之后会返回一个 布尔值,返回 true 就表示放行本次操作,如果返回 false 则不会放行 (本次 Controller 方法也不会执行)
(2) Controller 方法执行之后,再执行 postHandle 方法及 afterCompletion() 方法
5. 统一数据返回格式
在项目中我们想统一返回格式时,使用 @ControllerAdvice 注解,继承 ResponseBodyAdvice 接口并重写supports() 和 beforeBodyWrite()。
返回时间在 Controller方法执行后,在视图渲染前、响应体转换前
• supports() 方法:判断是否要执行 beforeBodyWrite() 方法, true 为执行,false 则不执行。
• beforeBodyWrite() 方法:对 response 方法进行具体操作处理,但是当 Controller 返回值是 String 类型的时候,beforeBodyWrite() 方法会报错。
java
import com.example.demo.model.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest
request, ServerHttpResponse response) {
return Result.success(body);
}
}
值得一提的是 Spring MVC 处理层顺序
java
sequenceDiagram
participant C as Controller
participant A as ResponseBodyAdvice
participant H as HttpMessageConverter
participant R as Response
C->>A: 返回原始数据
A->>A: 执行beforeBodyWrite()
A-->>C: 返回包装数据
C->>H: 传递包装数据
H->>H: 执行序列化(JSON/XML)
H-->>R: 生成响应体
- @ ResponseBodyAdvice 拥有数据修改权(可改变返回对象)
- HttpMessageConverter 拥有格式转换权 (不会修改数据结构)
5.1 注意事项
• String 类型不支持转换
可以通过信息序列化来解决
java
//如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化
if (body instanceof String){
return mapper.writeValueAsString(Result.success(body));
}
(1) Controller 方法返回对象时,Spring 会遍历已注册的 HttpMessageConverter 列表找到第一个能处理该类型的转换器。
(2) 当方法返回 String 时,StringHttpMessageConverter 会被优先选中,编译差异如下:
对于非 String 类 (如 Result 对象):
<1> ResponseBodyAdvice 先将返回值包装为 Result
<2> Spring 遍历 HttpMessageConverter ,会跳过支持 String 的StringHttpMessageConverter
<3> 最终匹配到 MappingJackson2HttpMessageConverter,他能处理任意对象 (通过Json序列化)。
对于 String 类型:
<1> ResponseBodyAdvice 先将返回值包装为 Result
<2> 匹配到优先级更高的 StringHttpMessageConverter,但它仅支持 String 类型
<3> 由于类型不匹配,触发 ClassCastException
• 原生对象直接返回
若直接返回 Result 对象,需要避免 ResponseBodyAdvice 重复包装,可通过注解或条件排除
5.2 优点
(1) 方便前端程序员更好的接收和解析后端数据接口返回的数据
(2) 降低前端程序员和后端程序员的沟通成本,因为所有接口都是这个数据格式返回
(3) 有利于项目统一数据的维护和修改
(4) 有利于后端技术部门统一规范的标准
6. 统一异常处理
统一异常处理通过 @ControllerAdvice 和 @ExceptionHandler 注解实现,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器。
java
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handler(Exception e) {
return Result.fail(e.getMessage());
}
}
@ResponseBody详解:
(1) @ResponseBody 是 Spring MVC 中一个注解,用于将 Controller 方法的返回值直接作为 HTTP 响应体(Response Body)。 如常用的 @requestMapping 从客户端返回的默认是视图名称,但是如果@RequestMapping 搭配上 @RestController 时,方法返回的值都会自动作为响应体。
HTTP 响应的结构:
状态行(Status Line)
• 包含协议版本(如
HTTP/1.1
)、状态码(如200 OK
)和状态描述。• 示例:
HTTP/1.1 200 OK
响应头(Response Headers)
• 描述响应的元信息,如内容类型(
Content-Type
)、内容长度(Content-Length
)、缓存策略等。响应体(Response Body)
**•**实际传输给客户端的数据(如 HTML、JSON、XML、文件流等)。
(2) @ResponseBody 在引入 JSON 库的时候默认值为 JSON 格式,可以通过 produces 属性来指定其他返回格式。
(3) @ResponseBody 依赖 HttpMessageConverter 来序列化数据,把数据格式转化为 JSON 格式。
7. Spring 事务
7.1 事务概念
事务是一组操作的集合,是一个不可分割的操作。事务会把所有操作,一起向数据库提交或者是撤销操作。也就是说这组操作要么同时全部成功,要么同时全部失败。
7.2 事务的操作
事务操作主要有三个步骤:
(1) 开启事务 start transaction/begin (一组操作前开启事务)
(2) 提交事务 commit (这组操作全部成功,提交事务)
(3) 回滚事务 rollback (这组操作中间任何一个操作出现异常,回滚事务)
7.3 Spring 事务实现
7.3.1 编程式事务 (手动写代码操作事务)
先准备数据库依赖
java
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #配置驼峰⾃动转换
SpringBoot 内置了两个对象:
-
DataSourceTransactionManager 事务管理器,用来获取事务 (开启事务),提交或回滚事务。
-
TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得一个事务 TransactionStatus。
具体实现逻辑如下:
java
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
// JDBC 事务管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
// 定义事务属性
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name,String password){
// 开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
//⽤⼾注册
userService.registryUser(name,password);
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
//回滚事务
//dataSourceTransactionManager.rollback(transactionStatus);
return "注册成功";
}
}
7.3.2 声明式事务 (通过注解自动开启和提交事务)
声明式事务很简单,通过**@Transactional**注解来实现,无需手动开启和提交事务。方法执行完自动提交事务,如果方法执行中发生了没有处理的异常,会自动回滚事务。如果异常被处理 (方法内部捕获了异常但未重新抛出),事务依然会被提交。
先添加依赖
java
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
在需要的方法上添加 @Transactional 注解
java
@RequestMapping("/trans")
@RestController
public class TransactionalController {
@Autowired
private UserService userService;
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
return "注册成功";
}
7.4 @Transactional 作用
@Transactional 可以用来修饰方法或类,修饰方法时只对 public 方法才生效。修饰类时,类中所有 public 方法都会生效。
@Transactional 详解
(1) 手动回滚事务
使用 TransactionAspectSupport.currentTransactionStatus() 得到当前事务,并使用 setRollbackOnly() 手动回滚
java
@Transactional
@RequestMapping("/registry")
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
log.info("⽤⼾数据插⼊成功");
//对异常进⾏捕获
try {
//强制程序抛出异常
int a = 10/0;
}catch (Exception e){
// ⼿动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return "注册成功";
}
(2) @Transactional 的三种属性
**• rollbackFor:**异常回滚属性,指定能够触发事务回滚的异常类型,可以指定多个异常类。
@Transactional 仅在遇到 RuntimeException 或 Error 时回滚,其他异常(如 IOException) 不会回滚,这个时候我们就可以通过指定 rollbackFor 属性来回滚。

• Isolation: 事务的隔离级别,默认值为 Isolation.DEFAULT。详细解释可以参考作者MySql 自我总结-CSDN博客 这篇文章。
**• propagation:**事务的传播机制,默认值为 Propagation.REQUIRED
7.5 事务传播机制
• REQUIRED:默认的传播机制。如果事务A存在,事务B加入事务A (成为同一事物)。事务A不存在则事务B新建事务。这里没有挂起的情况,因为事务B直接加入。如果事务A或B抛出异常,整个事务回滚。
• SUPPORTS:如果事务A存在,事务B加入。如果事务A不存在,事务B以非事务执行。没有挂起,事务B的异常在事务A存在时会影响事务A。
• MANDATORY:必须存在事务A,事务B加入。如果不存在事务A则报错。没有挂起,事务A或B异常会回滚事务A。
• REQUIRES_NEW:无论事务A是否存在,事务B都会新建事务。如果事务A存在,会被挂起(保存事务A状态),事务B的异常不会影响事务A。
• NOT_SUPPORTED:事务B以非事务执行,如果事务A存在,挂起事务A。事务B的异常不影响事务A。
• NEVER:事务B必须非事务执行,如果事务A存在则报错。
• NESTED:如果事务A存在,事务B作为嵌套事务 (保存点) 。如果事务A不存在,则类似REQUIRED。嵌套事务的回滚不影响事务A,但事务A的回滚会影响事务B。
这里常用的是 REQUIRED、REQUIRES_NEW 和 NESTED
事务传播机制对比表:

7.6 常见问题
(1) REQUIRED 和 NESTED 的区别?
整个事务如果全部执行成功,二者的结果是一样的。如果一部分执行成功,REQUIRED 加⼊事务会使整个事务都回滚,而 NESTED嵌套事务 可以实现局部回滚。
(2) NESTED 传播行为需要数据库支持吗?
NESTED 依赖数据库的保存点 (Savepoint) 功能
=============================================================================
如果觉得有帮助的话给博主点个赞吧!祝您在学习的路上顺风顺水!