当前位置: 首页 > news >正文

[Java EE] Spring AOP 和 事务

目录

1. AOP

1.1 AOP 概念

1.2 AOP 核心概念

1.3 AOP 作用

2. AOP 详解

2.1 切点(Pointcut)

2.2 连接点(Join Point)

2.3 通知(Advice)

2.4 切面(Aspect)

2.5 通知类型

2.5.1 @Around 环绕通知

2.5.2 @Before 前置通知

2.5.3 @After 后置通知

2.5.4 @AfterReturning 返回后通知

2.5.5 @AfterThrowing 异常后通知

2.6 @Pointcut

2.7 切面优先级

2.8 切面表达式

2.8.1 execution()

2.8.2 @annotation

2.8.2.1 编写自定义注解

2.8.2 @annotation 描述切点

2.8.3 添加自定义注解

3. Spring AOP 原理

3.1 代理模式

3.1.1 静态代理

3.1.2 动态代理

3.1.3 Spring AOP 动态代理机制详解

4.  SpringBoot 统一功能处理

4.1 拦截器

5. 统一数据返回格式

5.1 注意事项

5.2 优点

6. 统一异常处理

7.  Spring 事务

7.1 事务概念

7.2 事务的操作

7.3 Spring 事务实现

7.3.1 编程式事务 (手动写代码操作事务)

7.3.2 声明式事务 (通过注解自动开启和提交事务)

7.4 @Transactional 作用

7.5 事务传播机制

7.6 常见问题


1. AOP

1.1 AOP 概念

• Aspect Oriented Programming (面向切面编程),AOP 是一种思想,它的实现方法有很多,如 Spring AOP,  AspectJ, CGLIB 等。AOP 是对某一类事情的集中处理,其中 SpringBoot 统一功能处理就是 AOP 思想的实现。

引入AOP 依赖,在 pom.xml 文件中添加配置

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

认识 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@Aspect@Componentpublic 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 类下的所有方法都是连接点。

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)。

    @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 前置通知

• 在目标方法 执行前 触发。

• 适用于参数校验、权限检查、日志记录等。

    @Before("execution(* com.example.demo.controller.*.*(..))")public void doBefore() {log.info("执⾏ Before ⽅法");}

2.5.3 @After 后置通知

• 在目标方法 执行后 触发(无论是否抛出异常)。

• 适用于资源清理(如关闭文件、释放连接)。

    @After("execution(* com.example.demo.controller.*.*(..))")public void doAfter() {log.info("执⾏ After ⽅法");}

2.5.4 @AfterReturning 返回后通知

• 仅在目标方法 正常返回 时触发(不处理异常)。

• 可获取方法返回值,通过 returning 属性绑定返回值

    @AfterReturning("execution(* com.example.demo.controller.*.*(..))")public void doAfterReturning() {log.info("执⾏ AfterReturning ⽅法");}

2.5.5 @AfterThrowing 异常后通知

• 仅在目标方法 抛出异常 时触发。

• 可捕获特定异常类型

• 通过 throwing 属性绑定异常对象

    @AfterThrowing("execution(* com.example.demo.controller.*.*(..))")public void doAfterThrowing() {log.info("执⾏ doAfterThrowing ⽅法");}

正常时顺序如下:

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

异常时顺序如下:

2.6 @Pointcut

上述代码中存在大量的 "execution(* com.example.demo.controller.*.*(..))",对于勤奋的程序猿来说,能不能把这些公共重复的提取出来呢?我们可以通过 @Pointcut 注解,来把公共的切点表达式提取出来。

@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 中的值越小,执行的优先级越高。

@Aspect
@Component
@Order(2)
public class AspectDemo2 {//...代码省略 
}
@Aspect
@Component
@Order(1)
public class AspectDemo3 {//...代码省略 
}
@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 编写自定义注解

创建注解类

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 被保留的时间长短,表明注解的生命周期

特性SOURCECLASSRUNTIME
存在阶段源码源码 + 字节码源码 + 字节码 + 运行时
反射可获取性不可获取不可获取可获取
常见用途编译检查、代码生成字节码处理工具运行时动态处理
性能影响反射可能带来性能开销
2.8.2 @annotation 描述切点

使用 @annotation 切点表达式定义切点

切⾯类代码如下: 表示只对 @MyAspect 注解生效

@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 添加自定义注解
    @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) 定义接口:

public interface HouseSubject {void rentHouse();
}

(2) 实现接口:

public class RealHouseSubject implements HouseSubject{@Overridepublic void rentHouse() {System.out.println("我是房东, 我出租房⼦");}
}

(3) 代理 (中介帮房东出售房子)

public class HouseProxy implements HouseSubject{//将被代理对象声明为成员变量 private HouseSubject houseSubject;public HouseProxy(HouseSubject houseSubject) {this.houseSubject = houseSubject;}@Overridepublic void rentHouse() {//开始代理 System.out.println("我是中介, 开始代理");//代理房东出租房⼦ houseSubject.rentHouse();//代理结束 System.out.println("我是中介, 代理结束");}
}

(4) 使用

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 接口,并重写其所有方法

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponseresponse, Object handler) throws Exception {log.info("LoginInterceptor ⽬标⽅法执⾏前执⾏..");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponseresponse, Object handler, ModelAndView modelAndView) throws Exception {log.info("LoginInterceptor ⽬标⽅法执⾏后执⾏");}
}

• preHandle() :目标方法执行前执行,返回 true 才能继续执行后续操作

• postHandle() :目标方法执行后执行

(2) 注册拦截器

创建一个类实现 WebMvcConfigurer 接口,并重写 addInterceptors 方法。

@Configuration
public class WebConfig implements WebMvcConfigurer {//⾃定义的拦截器对象 @Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//注册⾃定义拦截器对象 registry.addInterceptor(loginInterceptor).addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所有请求)}
}

addPathPatterns() 方法拦截器路径匹配如下:

模式匹配层级参数支持示例路径典型场景
*1层/user首页/基础资源访问
/**任意层/user/login、regAPI网关/静态资源路由
/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() 方法会报错。

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 {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType, Class selectedConverterType, ServerHttpRequestrequest, ServerHttpResponse response) {return Result.success(body);}
}

值得一提的是 Spring MVC 处理层顺序

sequenceDiagramparticipant C as Controllerparticipant A as ResponseBodyAdviceparticipant H as HttpMessageConverterparticipant R as ResponseC->>A: 返回原始数据A->>A: 执行beforeBodyWrite()A-->>C: 返回包装数据C->>H: 传递包装数据H->>H: 执行序列化(JSON/XML)H-->>R: 生成响应体
  1. @ ResponseBodyAdvice 拥有数据修改权(可改变返回对象)
  2.  HttpMessageConverter 拥有格式转换权 (不会修改数据结构)

5.1 注意事项

• String 类型不支持转换

可以通过信息序列化来解决

//如果返回结果为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 是异常处理器

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 {@ExceptionHandlerpublic 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 编程式事务 (手动写代码操作事务)

先准备数据库依赖

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.StdOutImplmap-underscore-to-camel-case: true #配置驼峰⾃动转换

SpringBoot 内置了两个对象:

1. DataSourceTransactionManager 事务管理器,用来获取事务 (开启事务),提交或回滚事务。

2. TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得一个事务 TransactionStatus。

具体实现逻辑如下:

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 事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;// 定义事务属性@Autowiredprivate TransactionDefinition transactionDefinition;@Autowiredprivate 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 注解来实现,无需手动开启和提交事务。方法执行完自动提交事务,如果方法执行中发生了没有处理的异常,会自动回滚事务。如果异常被处理 (方法内部捕获了异常但未重新抛出),事务依然会被提交。

先添加依赖

<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId>
</dependency>

在需要的方法上添加 @Transactional 注解

@RequestMapping("/trans")
@RestController
public class TransactionalController {@Autowiredprivate 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() 手动回滚

@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) 功能

=============================================================================

如果觉得有帮助的话给博主点个赞吧!祝您在学习的路上顺风顺水!

相关文章:

[Java EE] Spring AOP 和 事务

目录 1. AOP 1.1 AOP 概念 1.2 AOP 核心概念 1.3 AOP 作用 2. AOP 详解 2.1 切点(Pointcut) 2.2 连接点(Join Point) 2.3 通知(Advice) 2.4 切面(Aspect) 2.5 通知类型 2.5.1 Around 环绕通知 2.5.2 Before 前置通知 2.5.3 After 后置通知 2.5.4 AfterReturning …...

2025年KBS SCI1区TOP:增强天鹰算法EBAO,深度解析+性能实测

目录 1.摘要2.天鹰算法AO原理3.改进策略4.结果展示5.参考文献6.代码获取 1.摘要 本文提出了增强二进制天鹰算法&#xff08;EBAO&#xff09;&#xff0c;针对无线传感器网络&#xff08;WSNs&#xff09;中的入侵检测系统&#xff08;IDSs&#xff09;。由于WSNs的特点是规模…...

适合IIS部署网页应用的编程语言

直接部署在 IIS 上的语言/技术 PHP 使用 FastCGI 模块直接集成安装 PHP Manager for IIS 可简化配置示例配置&#xff1a;在 web.config 中添加处理程序映射指向 php-cgi.exe Node.js 使用 iisnode 模块实现直接集成允许 Node.js 应用在 IIS 进程中运行支持进程管理、负载均衡…...

43.[前端开发-JavaScript高级]Day08-ES6-模板字符串-展开运算符-ES7~ES11

ES6~ES13新特性&#xff08;二&#xff09; 1 模板字符串的详解 字符串模板基本使用 标签模板字符串使用 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content…...

边生成边训练:构建合成数据驱动的在线训练系统设计实战

目录 边生成边训练&#xff1a;构建合成数据驱动的在线训练系统设计实战 一、系统目标与能力总览 ✅ 核心目标&#xff1a; 二、系统架构图&#xff08;推荐用于PPT展示&#xff09; 三、关键模块设计解析 ✅ 1. 合成数据生成器模块 ✅ 2. 质量评分器模块 ✅ 3. 在线训…...

AF3 create_alignment_db_sharded脚本main函数解读

AlphaFold3 create_alignment_db_sharded 脚本在源代码的scripts/alignment_db_scripts文件夹下。 该脚本中的 main 函数是整个对齐数据库生成脚本的核心入口,它 orchestrates(编排)了所有流程,从读取链目录到生成 .db 文件、构建索引、处理重复链、写入最终索引文件。 ma…...

52. Java 类和对象 - 什么是隐藏字段?

文章目录 52. Java 类和对象 - 什么是隐藏字段&#xff1f;&#x1f3af; 参数名称的作用与规则✅ 参数名称的命名规则 &#x1f3af; 什么是隐藏字段&#xff1f;&#x1f6a8; 问题定义✅ 解决办法&#xff1a;使用 this 关键字 &#x1f3af; 如何避免隐藏字段带来的困扰&am…...

IntelliJ IDEA右键快捷方式设置方法

IntelliJ IDEA右键快捷方式设置方法 在 IntelliJ IDEA 中设置右键快捷方式快速打开项目或文件&#xff08;Windows 系统&#xff09;&#xff0c;可以通过以下方法实现&#xff1a; 方法 1&#xff1a;通过注册表添加右键菜单&#xff08;推荐&#xff09; 打开注册表编辑器 按…...

深入剖析JavaScript内存泄漏:识别、定位与实战解决

在JavaScript的世界里&#xff0c;开发者通常不必像使用C那样手动管理内存的分配和释放&#xff0c;这得益于JavaScript引擎内置的垃圾回收&#xff08;Garbage Collection, GC&#xff09;机制。然而&#xff0c;这并不意味着我们可以完全忽视内存管理。“自动"不等于&qu…...

JVM原理与实战

一、Java虚拟机概述 java程序通过虚拟机实现了java代码的跨平台。 二、java虚拟机运行过程&#xff1a; 类编译器编译java代码为class文件&#xff0c; 类加载器将class文件加载到jvm&#xff0c; 程序计数器控制程序的执行&#xff0c; 虚拟机栈存放局部变量&#xff0c;方法名…...

MCP协议用到的Node.js 和 npm npx

一、Node.js 与 npm、npx 的介绍 Node.js&#xff1a;是一个基于 Chrome V8 引擎的 JavaScript 运行时环境&#xff0c;使您能够在服务器端运行 JavaScript 代码。它广泛用于构建服务器端应用程序和工具。 npm&#xff08;Node Package Manager&#xff09;&#xff1a;是 Nod…...

如何写 commit message?

前言 每次写 commit message 时&#xff0c;都会纠结用什么动词&#xff0c;格式应该什么样&#xff0c;所以决定总结一下。 查了一下&#xff0c;还是挺复杂的。因为只面向我个人日常使用&#xff0c;所以只进行一些简单的、适合我的总结。 正文 message 分为两部分&#…...

【厦门大学】DeepSeek大模型赋能高校教学和科研

DeepSeek赋能高校教学和科研 引言人工智能发展简史&#xff1a;从图灵测试到大模型时代大模型核心技术解析&#xff1a;构筑智能金字塔DeepSeek赋能高校&#xff1a;打造智能校园生态本地部署方案&#xff1a;安全、高效与定制化兼得教学革新&#xff1a;重塑知识传授与学习体验…...

【专刷】滑动窗口(一)

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…...

cJSON_Print 和 cJSON_PrintUnformatted的区别

cJSON_Print 和 cJSON_PrintUnformatted 是 cJSON 库中用于将 cJSON 对象转换为 JSON 字符串的两个函数&#xff0c;它们的区别主要在于输出的格式&#xff1a; 1. cJSON_Print 功能&#xff1a;将 cJSON 对象转换为格式化的 JSON 字符串。 特点&#xff1a; 输出的 JSON 字符…...

C 语 言 --- 指 针 4(习 题)

C 语 言 --- 指 针 4&#xff08;习 题&#xff09; sizeofstrlen整 型 数 组 - - - int a[ ]字 符 数 组 - - - char arr[ ]字 符 数 组 - - - char arr1[ ]字 符 串 常 量 指 针 - - - char arr[ ]二 维 数 组 - - - char arr[3][4]总结 &#x1f4bb;作 者 简 介&#xff1a…...

可发1区的超级创新思路(python 、MATLAB实现):基于区域注意力双通道MABMA的时间序列预测模型

首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 应用领域:功率预测、故障诊断、流量预测等领域! 目录 首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 应用领域:功率预测、故障诊断、流…...

可发1区的超级创新思路(python 、MATLAB实现):基于多尺度注意力TCN-KAN与小波变换的时间序列预测模型

一、数学模型与原理 1.1 小波变换多尺度分解 输入功率序列 x(t) 经小波变换分解为近似系数 Aj​ 与细节系数 Dj​: 1.2 多尺度TCN特征提取 对每个尺度子序列 {A3​,D3​,D2​,D1​} 采用独立TCN: 式中 ∗d​ 为扩张率 d=2l 的扩张卷积,Wd​ 为可学习参数。 1.3 多尺度注…...

PyTorch `flatten()` 和 `squeeze()` 区别

PyTorch flatten() 和 squeeze() 区别 在 PyTorch 里,flatten() 和 squeeze(0) 是两个不同的张量操作, 1. flatten() 方法 flatten() 方法用于把一个多维张量展开成一维张量。它会将张量里的所有元素按顺序排列成一个一维序列。 语法 torch.flatten(input, start_dim=...

使用Java基于Geotools的SLD文件编程式创建与磁盘生成实战

前言 在地理信息系统&#xff08;GIS&#xff09;领域&#xff0c;地图的可视化呈现至关重要&#xff0c;而样式定义语言&#xff08;SLD&#xff09;文件为地图元素的样式配置提供了强大的支持。SLD 能够精确地定义地图图层中各类要素&#xff08;如点、线、面、文本等&#x…...

opencv练习

1.创建一个 PyQt 应用程序&#xff0c;该应用程序能够&#xff1a; &#xff08;1&#xff09;使用 OpenCV 加载一张图像。 &#xff08;2&#xff09;在 PyQt 的窗口中显示这张图像。 &#xff08;3&#xff09;提供四个按钮&#xff08;QPushButton&#xff09;&#xff1…...

opencv--基础

opencv OpenCV是一个实现数字图像处理和计算机视觉通用算法的开源跨平台库。 链接 opencv中的cv是什么意思 在OpenCV中&#xff0c;"cv" 是 "Computer Vision"&#xff08;计算机视觉&#xff09; 的缩写。 opencv的实现语言 opencv的底层实现代码是使…...

基于模板匹配的信用卡号码识别系统

本项目实现了一个基于模板匹配的信用卡号码识别系统。 1. 导入库和设置参数 # -*- coding: utf-8 -*- # 导入工具包 from imutils import contours import numpy as np import argparse import cv2 import myutils import os# 设置参数 ap argparse.ArgumentParser() # 替换…...

Spring Boot中Excel处理完全指南

文章目录 1. Excel处理基础知识1.1 为什么需要在应用中处理Excel文件?1.2 Java中的Excel处理库介绍1.2.1 Apache POI1.2.2 EasyExcel1.2.3 JExcel1.2.4 Apache POI SXSSF1.3 Spring Boot中集成Excel处理2. 在Spring Boot中集成Excel处理库2.1 集成Apache POI2.1.1 添加依赖2.1…...

洛谷P1312 [NOIP 2011 提高组] Mayan 游戏

题目 #算法/进阶搜索 思路: 根据题意,我们可以知道,这题只能枚举,剪枝,因此,我们考虑如何枚举,剪枝. 首先,我们要定义下降函数down(),使得小木块右移时,能够下降到最低处,其次,我们还需要写出判断函数,判断矩阵内是否有小木块没被消除.另外,我们还需要消除函数,将矩阵内三个相连…...

c++ (异常)

1.异常的概念及使用 1.1异常的概念 异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理&#xff0c; 异常使得我们能够将问题的检测与解决问题的过程分开&#xff0c;程序的一部分负责检测问题的出现&#xff0c;然后 解决问题的任务传…...

MySQL如何实现行行比较

概述 在MySQL中实现行行比较通常涉及比较同一表或不同表中不同行的数据。以下是几种常见的方法及示例&#xff1a; 1. 自连接&#xff08;Self-Join&#xff09; 通过将表与自身连接&#xff0c;比较不同行的数据。 场景示例&#xff1a;比较同一用户相邻订单的金额差异。 …...

springboot2.X创建maven多模块工程

因为需要&#xff0c;所以付出。 好长时间没有搭建新的框架了&#xff0c;最近在搭建微服务的多模块maven工程&#xff0c;现在就将创建的过程记录下来&#xff0c;方便自学的小伙伴找寻资料&#xff0c;少走弯路。好了下面直接开干。 开发工具 &#xff1a;idea 、springboo…...

八股文---Redis(1)

目录 1.Redis-使用场景 1.我看你做的项目中&#xff0c;都用到了redis&#xff0c;你在最近的项目中哪些场景使用了redis呢&#xff1f; 2.缓存三兄弟可以我看我另一个文章&#xff08;穿透&#xff0c;雪崩&#xff0c;击穿&#xff09; 3.redis做为缓存&#xff0c;mysql…...

QT聊天项目DAY06

1.从git上同步项目 编译测试&#xff0c;编译通过 Post请求测试 测试成功 2. email is 打印有问题&#xff0c;检查 解析结果是存储在jsonResult中的&#xff0c;修改 3. 客户端实现Post验证码请求 3.1 同步Qt客户端项目 检查QT版本&#xff0c;由于我在公司用的还是QT5.12.9…...

python(八)-数据类型转换

#数据类型转换 #转换为整型int #字符串str--》整数int #纯数字的字符串可以转换&#xff0c;否则会报错 s 2025 n int(s) print(type(s),type(n)) print(n)#浮点数float--》整数int s1 2.23 print(int(s1))#bool-->整数int s2,s3 True,False print(int(s2),int(s3))#转…...

JavaScript 变量命名规范

在编写JavaScript代码时&#xff0c;遵循良好的变量命名规范对于提高代码的可读性、可维护性和协作效率至关重要。一个清晰且一致的命名习惯不仅有助于开发者自己理解代码&#xff0c;也能让其他团队成员更容易上手和维护项目。本文将详细介绍JavaScript中常见的变量命名规则和…...

2025年渗透测试面试题总结-拷打题库05(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 2025年渗透测试面试题总结-拷打题库05 1. 病毒和蠕虫的区别 2. DNS欺骗&#xff08;DNS Spoofing&…...

【排队论】Probabilistic Forecasts of Bike-Sharing Systems for Journey Planning

Probabilistic Forecasts of Bike-Sharing Systems forJourney Planning abstract 我们研究了对共享单车系统&#xff08;BSS&#xff09;车站未来自行车可用性进行预测的问题。这是相关的&#xff0c;以便提出建议&#xff0c;保证用户能够进行旅行的概率足够高。为此&#x…...

Redis 的持久化机制(RDB, AOF)对微服务的数据一致性和恢复性有何影响?如何选择?

Redis 的持久化机制&#xff08;RDB 和 AOF&#xff09;对于保证 Redis 服务重启或崩溃后数据的恢复至关重要&#xff0c;这直接影响到依赖 Redis 的微服务的数据一致性和恢复能力。 1. RDB (Redis Database Backup) 机制: 在指定的时间间隔内&#xff0c;将 Redis 在内存中的…...

手撕LLM(四):从源码出发,探索大模型的预训练(pretrain)过程

前面我们基于Minimind项目介绍了大模型的推理、LoRa加载、Moe结构&#xff0c; 大家对大模型的整体结构应该有一个比较清晰的认识&#xff1b;从该篇博客开始&#xff0c;我们通过代码剖析大模型的训练过程&#xff0c;今天的主题是大模型的预训练。 那大模型的预训练是一个什么…...

Linux系统:进程终止的概念与相关接口函数(_exit,exit,atexit)

本节目标 理解进程终止的概念理解退出状态码的概念以及使用方法掌握_exit与exit函数的用法以及区别atexit函数注册终止时执行的函数相关宏 一、进程终止 进程终止&#xff08;Process Termination&#xff09;是指操作系统结束一个进程的执行&#xff0c;回收其占用的资源&a…...

keil5 µVision 升级为V5.40.0.0:增加了对STM32CubeMX作为全局生成器的支持,主要有哪些好处?

在Keil5 μVision V5.40.0.0版本中,增加了对STM32CubeMX作为全局生成器的支持,这一更新主要带来了以下三方面的提升: 开发流程整合STM32CubeMX原本就支持生成Keil项目代码,但新版本将这一集成升级为“全局生成器”级别,意味着STM32CubeMX生成的代码能直接成为Keil项目的核…...

C 语言联合与枚举:自定义类型的核心解析

上篇博客中&#xff0c;我们通过学习了解了C语言中一种自定义类型结构体的相关知识&#xff0c;那么该语言中是否还拥有相似的自定义类型呢&#xff1f;这将是我们今天学习的目标。 1.联合体 联合体其实跟结构体类似&#xff0c;也是由一个或多个成员构成&#xff0c;这些成员…...

P1113 杂务-拓扑排序

拓扑排序 P1113 杂务 题目来源-洛谷 题意 求出完成所有任务的最短时间 思路 要求完成所有任务的最短时间&#xff0c;即每个任务尽可能最短&#xff0c;所以再求完成所有任务中的最大值&#xff08;需要最长时间的任务都完成了才叫全部完成&#xff09; 问题化解&#xf…...

Flink介绍——实时计算核心论文之Kafka论文总结

引入 大数据系统中的数据来源 在开始深入探讨Kafka之前&#xff0c;我们得先搞清楚一个问题&#xff1a;大数据系统中的数据究竟是从哪里来的呢&#xff1f;其实&#xff0c;这些数据大部分都是由各种应用系统或者业务系统产生的“日志”。 比如&#xff0c;互联网公司的广告…...

模拟投资大师思维:AI对冲基金开源项目详解

这里写目录标题 引言项目概述核心功能详解多样化的AI投资智能体灵活的运行模式透明的决策过程 安装和使用教程环境要求安装步骤基本使用方法运行对冲基金模式运行回测模式 应用场景和实际价值教育和研究价值潜在的商业应用与现有解决方案的对比局限性与发展方向 结论 引言 随着…...

DAY4:数据库对象与高级查询深度解析:从视图到多表关联实战

一、数据库对象精要指南 1.1 视图&#xff08;View&#xff09;的进阶应用 视图是存储在数据库中的虚拟表&#xff0c;本质是预编译的SQL查询语句。通过视图可以简化复杂查询、实现数据安全隔离、保持业务逻辑一致性。 创建语法示例&#xff1a; CREATE VIEW sales_summary…...

【Matlab】中国东海阴影立体感地图

【Matlab】中国东海阴影立体感地图 【Matlab】中国东海阴影立体感地图 【Matlab】中国东海阴影图立体感画法 以前分享过一次&#xff0c;链接如下&#xff1a; 中国海域地形图 但是以前还是有些小问题&#xff0c;这次修改了。 另外&#xff0c;增加了新的画法&#xff1a; 另…...

python文件类操作:json/ini配置文件、logging日志统计、excel表格数据读写、os操作库

文章目录 一、with open文件操作二、csv表格数据读写三、Excel表格数据读写四、json配置文件读写五、ini配置文件读写六、logging日志统计七、os操作库&#xff08;文件拼接、创建、判断等&#xff09; 打开文件使用不同参数有着不同的含义&#xff0c;比如只读、只写、二进制读…...

VSCode安装与环境配置(Mac环境)

20250419 - 概述 大概是非常久之前了&#xff0c;装了VSCode&#xff0c;估计都得21的时候了&#xff0c;电脑上也没更新过。当时安装也直接装上就完事了。这次把版本更新一下&#xff0c;同时记录一下这个安装过程。 安装 mac下安装非常简单&#xff0c;直接从官网下载&am…...

【信息系统项目管理师】高分论文:论信息系统项目的采购管理(“营业工单系统”项目)

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 论文1、规划采购管理2、实施采购3、控制采购论文 2018年1月,我参加了 XX运营商集团公司某省分公司的“营业工单系统”的信息化建设项目,我有幸担任项目经理。该项目投资1000万元人民币,建设工期为12个月。该…...

XCVU13P-2FHGA2104I Xilinx Virtex UltraScale+ FPGA

XCVU13P-2FHGA2104I 是 Xilinx&#xff08;现为 AMD&#xff09;Virtex UltraScale™ FPGA 系列中的高端 Premium 器件&#xff0c;基于 16nm FinFET 工艺并采用 3D IC 堆叠硅互连&#xff08;SSI&#xff09;技术&#xff0c;提供业内顶级的计算密度和带宽​。该芯片集成约 3,…...

@Validated与@Valid的正确使用姿势

验证代码 Validated RestController public class A {PostMappingpublic void test(Min(value 1) Integer count) {} // 校验规则生效 }RestController public class A {PostMappingpublic void test(Validated Min(value 1) Integer count) {} // 校验规则不生效 }RestCont…...

Ubuntu20.04下Docker方案实现多平台SDK编译

0 前言 熟悉嵌入式平台Linux SDK编译流程的小伙伴都知道,假如平台a要求必须在Ubuntu18.04下编译,平台b要求要Ubuntu22.04的环境,那我只有Ubuntu20.04,或者说我的电脑硬件配置最高只能支持Ubuntu20.04怎么办?强行在Ubuntu20.04下编译,编又编不过,换到旧版本我又不愿意,…...