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

Spring事务自调用失效问题:Spring 默认使用代理(proxy)来实现事务拦截:只有通过代理对象的调用才会触发事务增强

我们来写一个完整可运行的 Spring Boot + MySQL Demo,验证

ServiceA.methodA() 中直接调用 methodB()(同类自调用),即使 methodB() 上有 @Transactional,事务也不会生效


🧱 一、环境假设

  • Spring Boot 版本:2.7.x3.x 均可
  • MySQL 已启动(比如:localhost:3306/test
  • 表结构自建或由程序初始化
  • 你可以直接通过 Postman/浏览器 访问 http://localhost:8080/test/selfCall 验证效果

🧩 二、项目结构

src/main/java/com/example/txdemo/├── TxDemoApplication.java         # 启动类├── controller/TestController.java # 控制层入口├── service/DemoService.java       # 核心业务逻辑,包含methodA与methodB└── resources/application.yml

🧰 三、依赖(pom.xml)

<project xmlns="http://maven.apache.org/POM/4.0.0" ...><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>txdemo</artifactId><version>0.0.1-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.10</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency></dependencies><properties><java.version>11</java.version></properties>
</project>

⚙️ 四、配置文件(application.yml

spring:datasource:url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver# 显示SQL日志jpa:show-sql: truelogging:level:org.springframework.jdbc.core.JdbcTemplate: DEBUG

🧠 五、建表 SQL

CREATE TABLE IF NOT EXISTS person (id BIGINT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100)
);

🧩 六、启动类(TxDemoApplication.java

package com.example.txdemo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class TxDemoApplication {public static void main(String[] args) {SpringApplication.run(TxDemoApplication.class, args);}
}

🧭 七、Service 逻辑(核心验证点)

package com.example.txdemo.service;import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class DemoService {private final JdbcTemplate jdbc;public DemoService(JdbcTemplate jdbc) {this.jdbc = jdbc;}// 方法A:不带事务,内部调用方法Bpublic void methodA() {System.out.println("====> 进入 methodA()");try {// 同类内部调用 -> 不经过代理methodB();} catch (Exception e) {System.out.println("methodA 捕获异常: " + e.getMessage());}System.out.println("<==== 退出 methodA()");}// 方法B:带事务注解@Transactionalpublic void methodB() {System.out.println("====> 进入 methodB() [事务标注生效?]");jdbc.update("INSERT INTO person(name) VALUES (?)", "内部调用事务测试");System.out.println("插入完成,马上抛异常测试事务回滚...");throw new RuntimeException("测试事务回滚异常");}// 用于通过代理验证(控制器直接调用)@Transactionalpublic void methodBFromController() {System.out.println("====> 进入 methodBFromController() [事务标注生效]");jdbc.update("INSERT INTO person(name) VALUES (?)", "Controller直接调用事务测试");System.out.println("插入完成,马上抛异常测试事务回滚...");throw new RuntimeException("测试事务回滚异常");}public int count() {return jdbc.queryForObject("SELECT COUNT(*) FROM person", Integer.class);}public void clear() {jdbc.update("DELETE FROM person");}
}

🌐 八、Controller(TestController.java

package com.example.txdemo.controller;import com.example.txdemo.service.DemoService;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/test")
public class TestController {private final DemoService demoService;public TestController(DemoService demoService) {this.demoService = demoService;}// 1️⃣ 验证【内部调用】事务不生效@GetMapping("/selfCall")public String testSelfCall() {demoService.clear();try {demoService.methodA(); // A 内部调用 B(事务不会生效)} catch (Exception ignored) {}int count = demoService.count();return "执行完毕(内部调用),表中记录数:" + count;}// 2️⃣ 验证【通过代理调用】事务生效@GetMapping("/proxyCall")public String testProxyCall() {demoService.clear();try {demoService.methodBFromController(); // Controller -> 代理 -> Service 方法(事务生效)} catch (Exception ignored) {}int count = demoService.count();return "执行完毕(代理调用),表中记录数:" + count;}
}

🚀 九、运行 & 验证

启动项目后访问:

✅ 情况 1:内部自调用(事务不生效)

GET http://localhost:8080/test/selfCall

控制台输出:

====> 进入 methodA()
====> 进入 methodB() [事务标注生效?]
插入完成,马上抛异常测试事务回滚...
methodA 捕获异常: 测试事务回滚异常
<==== 退出 methodA()

返回结果:

执行完毕(内部调用),表中记录数:1

👉 插入提交成功,说明事务没有生效(因为 B 的事务未被代理拦截)。

✅ 情况 2:通过代理调用(事务生效)

GET http://localhost:8080/test/proxyCall

控制台输出:

====> 进入 methodBFromController() [事务标注生效]
插入完成,马上抛异常测试事务回滚...

返回结果:

执行完毕(代理调用),表中记录数:0

👉 插入回滚成功,说明事务确实生效。


✅ 十、结论

场景调用方式是否经过代理事务是否生效表中记录
methodA → this.methodB()同类内部调用❌ 否❌ 不生效✅ 插入成功
Controller → DemoService.methodB()代理调用✅ 是✅ 生效❌ 回滚成功

将带事务的方法(B)拆分到另一个独立的 @Service 中,通过 Spring 容器注入并调用

将带事务的方法(B)拆分到另一个独立的 @Service,通过 Spring 容器注入并调用。
这样就会通过 Spring AOP 代理对象 调用,从而让 @Transactional 生效。


✅ 一、实现目标

我们现在的目标是:

场景调用方式是否通过代理事务生效结果
Controller → ServiceA.methodA() → 调用 ServiceB.methodB()✅ 是✅ 生效插入会回滚

🧩 二、项目结构

src/main/java/com/example/txdemo/├── TxDemoApplication.java├── controller/TestController.java├── service/ServiceA.java       # 外层逻辑,不带事务├── service/ServiceB.java       # 内层逻辑,带事务└── resources/application.yml

🧠 三、ServiceB(带事务的方法)

package com.example.txdemo.service;import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class ServiceB {private final JdbcTemplate jdbc;public ServiceB(JdbcTemplate jdbc) {this.jdbc = jdbc;}@Transactionalpublic void methodB() {System.out.println("====> 进入 ServiceB.methodB() [事务生效]");jdbc.update("INSERT INTO person(name) VALUES (?)", "事务正常生效");System.out.println("插入完成,马上抛异常测试回滚...");throw new RuntimeException("测试事务回滚异常");}public int count() {return jdbc.queryForObject("SELECT COUNT(*) FROM person", Integer.class);}public void clear() {jdbc.update("DELETE FROM person");}
}

四、ServiceA(调用方)

package com.example.txdemo.service;import org.springframework.stereotype.Service;@Service
public class ServiceA {private final ServiceB serviceB;public ServiceA(ServiceB serviceB) {this.serviceB = serviceB;}public void methodA() {System.out.println("====> 进入 ServiceA.methodA()");try {// 调用另一个 bean 的方法,会经过代理serviceB.methodB();} catch (Exception e) {System.out.println("ServiceA 捕获异常: " + e.getMessage());}System.out.println("<==== 退出 ServiceA.methodA()");}
}

🌐 五、Controller

package com.example.txdemo.controller;import com.example.txdemo.service.ServiceA;
import com.example.txdemo.service.ServiceB;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/test2")
public class TestController {private final ServiceA serviceA;private final ServiceB serviceB;public TestController(ServiceA serviceA, ServiceB serviceB) {this.serviceA = serviceA;this.serviceB = serviceB;}// 测试拆分Service后事务是否生效@GetMapping("/crossService")public String crossServiceCall() {serviceB.clear();serviceA.methodA();  // A -> B(带事务)int count = serviceB.count();return "执行完毕(跨Service调用),表中记录数:" + count;}
}

⚙️ 六、配置文件(application.yml)

spring:datasource:url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverlogging:level:org.springframework.jdbc.core.JdbcTemplate: DEBUG

🚀 七、验证执行

启动项目后访问:

GET http://localhost:8080/test2/crossService

控制台输出:

====> 进入 ServiceA.methodA()
====> 进入 ServiceB.methodB() [事务生效]
插入完成,马上抛异常测试回滚...
ServiceA 捕获异常: 测试事务回滚异常
<==== 退出 ServiceA.methodA()

返回结果:

执行完毕(跨Service调用),表中记录数:0

👉 表明:

  • ServiceB.methodB() 的事务确实生效;
  • 抛出的异常导致回滚;
  • 表中记录未插入成功(回滚成功)。

✅ 八、对比总结

场景调用方式事务生效表记录结果
A 内部直接调用 B(同类)❌ 未经过代理❌ 不生效✅ 插入成功
A 调用 B(不同 Service)✅ 经过代理✅ 生效❌ 回滚成功

💡 九、建议与实践经验

  1. 建议将带事务的业务逻辑拆分到单独的 Service 中,让调用经过 Spring 代理。
  2. @Transactional 一般放在 public 方法上(AOP 代理只拦截 public)。
  3. 避免 private 方法加事务,或同类自调用导致事务失效。
  4. 如果确实必须在同类内部调用,也可以启用 AopContext.currentProxy() + exposeProxy=true,但不推荐生产环境使用。

http://www.dtcms.com/a/532515.html

相关文章:

  • 兰州网站seo收费标准张槎网站建设
  • Vue Pinia 状态管理实战指南
  • 向量内积可看作 1 行 ×1 列的矩阵乘法,矩阵乘法则可拆成 多个向量内积的集合
  • 做社区网站怎么做巫山做网站哪家强
  • RabbitMQ -- 保障消息可靠性
  • [sam2图像分割] mask_decoder | TwoWayTransformer
  • 京东面试题解析:SSO、Token与Redis交互、Dubbo负载均衡等
  • 网站建设哪家效益快做百度推广网站排名
  • RabbitMQ -- 高级特性
  • 克隆网站后台asp.net 网站数据库
  • 零基础新手小白快速了解掌握服务集群与自动化运维(十S四)储存服务-Ceph储存
  • 土壤侵蚀相关
  • 花卉网站建设规划书平台推广计划书模板范文
  • 如何使用C#编写DbContext与数据库连接
  • 从一到无穷大 #52:Lakehouse 不适用时序?打破范式 —— Catalog 架构选型复盘
  • 机器学习 (1) 监督学习
  • 从哪里找网络推广公司网站优化 毕业设计
  • Java如何将数据写入到PDF文件
  • 开发板网络配置
  • 14天备考软考-day1: 计组、操作系统(仅自用)
  • 企业网站模板包含什么有什么软件可以做网站
  • .gitignore 不生效问题——删除错误追踪的文件
  • 深度学习优化器详解
  • 做企业公示的数字证书网站wordpress有识图接口吗
  • 中国商标注册申请官网百度蜘蛛池自动收录seo
  • GitHub 热榜项目 - 日榜(2025-10-26)
  • 数据分析:指标拆解、异动归因类题目
  • 做网站需要那些软件设计建网站
  • Gorm(十二)乐观锁和悲观锁
  • neo4j图数据库笔记