💫为什么一行 `@Transactional` 能救火,也能“放火”?——Spring Boot 事务管理全解析!

🏆本文收录于「滚雪球学SpringBoot」专栏(全网一个名),手把手带你零基础入门Spring Boot,从入门到就业,助你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
环境说明:Windows 10 + IntelliJ IDEA 2021.3.2 + Jdk 1.8
前言
先打个气:如果你在读到这篇的时候,正因为一条“幽灵般的脏数据”把你从周末烧烤局里拉回了工位——别怕,锅八成在事务边界和传播行为上。今天我们把 Spring Boot 事务管理 这盘大菜从“底层锅底”到“配料蘸料”都摆上桌,不仅讲个明白,还给你上手能跑的代码,顺便用几张可视化流程图把“坑点”钉在墙上,避免你下次再被同一个坑绊倒。😤🔥
🧭 你将收获什么?
- 彻底搞懂 Spring 事务抽象、
PlatformTransactionManager
、代理与 AOP 的那点事 - 声明式 vs 编程式事务的正确打开方式,何时该用哪一种
@Transactional
每个参数的“性格与脾气”:propagation
、isolation
、rollbackFor
、timeout
、readOnly
- 传播行为的“串场效应”:
REQUIRED
、REQUIRES_NEW
、NESTED
等到底怎么选 - 隔离级别的“四宗罪”:脏读、不可重复读、幻读、读写冲突如何被隔离
- 回滚的“八卦”:为啥受检异常不回滚?怎么自定义?如何捕获后仍然回滚?
- 多数据源、本地事务/JTA、测试中的事务、异步与事务、消息/Outbox/Saga 的实战建议
🗺️ 导航
- 🏗️ Spring Boot 事务管理概述
- 🎭 声明式事务 与 编程式事务
- 🏷️
@Transactional
注解的使用 - 🔁 事务传播行为 与 隔离级别
- 🧨 事务回滚机制(含常见踩坑与规避)
- 🧪 可运行的演示项目:H2 + JPA + Service 分层 + 集成测试
- 🧰 生产环境实战清单:多数据源、幂等与重试、异步与消息、监控与压测
- 🧴 收尾与复盘:一张表格搞定常见场景选型
备注:文中所有代码均可复制粘贴即跑(使用 H2 内存库演示),流程图用 Mermaid 展示关键路径,尽量把抽象问题“画出来”。🧑🎨
1) 🏗️ Spring Boot 事务管理概述
事务是数据库操作的原子单元,必须满足 ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。Spring 做的并不是“重造数据库事务”,而是提供了一个优雅的事务抽象层:统一接上不同的事务管理器(JDBC、JPA、JTA……),并通过 AOP 代理把“开启/提交/回滚”这类模板化逻辑从你的业务代码里“切”出来。
核心角色一眼看清:
-
PlatformTransactionManager
:事务总管。常见实现:DataSourceTransactionManager
(JDBC/单机库的本地事务)JpaTransactionManager
(JPA/Hibernate)JtaTransactionManager
(分布式/XA,整合 Atomikos/Narayana 等)
-
@EnableTransactionManagement
(Spring Boot Starter 已自动开启):让@Transactional
生效 -
AOP 代理:基于代理包装你的 Bean 方法,在方法前后织入事务边界逻辑(注意自调用 self-invocation 的坑)
来一张高层流程图感受一下“代理怎么织、事务怎么开”:
一句话总结:Spring 把你从琐碎的“开关事务”中解放出来,但你需要设计好事务边界,理解代理调用时机、传播行为与回滚规则。
2) 🎭 声明式事务 与 编程式事务
2.1 声明式事务(主角)
- 用
@Transactional
标注类/方法即可 - 优点:简洁、可读、统一;和切面能力天然契合
- 使用面:80%+ 的业务
2.2 编程式事务(王牌备胎)
- 用
TransactionTemplate
或PlatformTransactionManager
手工编排 - 适用:需要细粒度控制(同一方法里“分段提交/部分新事务/回滚但不中断主流程”等)、复杂异常/补偿、跨模块不便暴露注解
编程式事务示例(精选常见诉求:主逻辑失败要回滚,日志必须单独新事务写入):
@Service
@RequiredArgsConstructor
public class OrderFacade {
private final TransactionTemplate txTemplateMain; // 默认 REQUIRED
private final TransactionTemplate txTemplateRequiresNew;// 显式 REQUIRES_NEW
private final AuditLogRepository auditRepo;
private final OrderRepository orderRepo;
public Long placeOrder(OrderDTO dto) {
return txTemplateMain.execute(status -> {
Long orderId = orderRepo.createOrder(dto); // 主流程
try {
txTemplateRequiresNew.execute(s2 -> { // 单独新事务写审计日志
auditRepo.log("ORDER_PLACED", orderId);
return null;
});
} catch (Exception e) {
// 审计失败不影响主流程,但要监控告警
// log.warn("audit failed", e);
}
// 主流程可能继续更多操作...
return orderId;
});
}
}
要点:REQUIRES_NEW
让“审计日志”即使主事务失败也能单独提交,这在安全/合规/追踪场景很常见。
3) 🏷️ @Transactional
注解的使用
3.1 常用参数一图懂
参数 | 作用 | 常见取值/示例 | 细节/坑点 |
---|---|---|---|
propagation |
传播行为 | REQUIRED (默认)、REQUIRES_NEW 、NESTED … |
决定与当前事务的关系 |
isolation |
隔离级别 | DEFAULT 、READ_COMMITTED 、REPEATABLE_READ 、SERIALIZABLE |
不同数据库默认值不同 |
rollbackFor |
指定哪些异常回滚 | rollbackFor=Exception.class |
默认只对运行时异常回滚 |
noRollbackFor |
指定哪些异常不回滚 | noRollbackFor=BizWarn.class |
精准控制 |
timeout |
超时秒数 | timeout=5 |
超时会触发回滚 |
readOnly |
只读优化 | readOnly=true |
可能触发数据库/ORM 优化 |
3.2 基础用法与推荐姿势
- 尽量标注在 Service 层(聚合用例/业务边界),避免 Controller 直接开事务
- 在 类上标注 作为默认策略,在方法上细化覆盖
- 方法必须是 public 才能被代理(JDK 动态代理时),同类内部自调用不会走代理(大坑,见下)
@Service
@Transactional(readOnly = true) // 默认只读
public class UserQueryService {
private final UserRepository repo;
public UserDTO findById(Long id) {
return repo.findById(id).map(UserDTO::from).orElseThrow();
}
}
@Service
public class UserCommandService {
@Transactional // 写操作单独声明
public Long createUser(CreateUserCmd cmd) { /* ... */ }
@Transactional(rollbackFor = Exception.class, timeout = 3)
public void changeEmail(Long uid, String email) { /* ... */ }
}
3.3 自调用(Self-Invocation)大坑!
@Service
public class PaymentService {
@Transactional
public void pay() { /* ... */ }
public void orchestrate() {
// ❌ 自调用 pay():不会经过代理,事务不起效
this.pay();
}
}
修复姿势:
- 把
pay()
提取到另一个 Bean,通过注入调用 - 或者注入“自己”的代理
PaymentService proxy = AopContext.currentProxy()
(需开启exposeProxy=true
,但不太优雅) - 更推荐:按职责拆分 Service,让边界清晰也更易测
4) 🔁 事务传播行为 与 隔离级别
4.1 传播行为(Propagation)
一句“人格化”理解:“我遇到一个正在进行的事务,我要怎么办?”
传播行为 | 白话 | 典型用途 |
---|---|---|
REQUIRED |
有就加入,没有就新建 | 默认首选 |
REQUIRES_NEW |
不管有没有,都新开一个 | 审计日志、发送消息、补偿动作 |
SUPPORTS |
有就用,没有就算了 | 只读查询、缓存 |
MANDATORY |
必须在事务中,否则报错 | 强制要求 |
NOT_SUPPORTED |
挂起当前事务,以非事务方式执行 | 访问慢外部接口,避免长事务 |
NEVER |
必须没有事务,否则报错 | 极特殊 |
NESTED |
有就开保存点,无则新建 | 局部可回滚(JDBC/保存点支持) |
传播行为交互图(最常见 REQUIRED ↔ REQUIRES_NEW):
Parse error on line 2: ...D A[外层 Service.A() @Transactional(RE ----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'GRAPH', 'DIR', 'subgraph', 'SQS', 'SQE', 'end', 'AMP', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'ALPHA', 'COLON', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'START_LINK', 'LINK', 'STYLE', 'LINKSTYLE', 'CLASSDEF', 'CLASS', 'CLICK', 'DOWN', 'UP', 'DEFAULT', 'NUM', 'COMMA', 'MINUS', 'BRKT', 'DOT', 'PCT', 'TAGSTART', 'PUNCTUATION', 'UNICODE_TEXT', 'PLUS', 'EQUALS', 'MULT', 'UNDERSCORE', got 'PS'要点:
REQUIRES_NEW
真的会挂起外层事务,开启新连接/新事务,相互独立提交与回滚。
4.2 隔离级别(Isolation)
一句“症状对应药方”:
隔离级别 | 能防的现象 | 成本 |
---|---|---|
READ_UNCOMMITTED |
不能防啥(允许脏读) | 低 |
READ_COMMITTED |
防脏读 | 中(PostgreSQL 默认) |
REPEATABLE_READ |
再加防不可重复读(幻读在不同 DB 不同表现) | 较高(MySQL InnoDB 默认) |
SERIALIZABLE |
最强一致(近似串行) | 最高,吞吐骤降 |
四宗罪速记:
- 脏读:读到了别人未提交的数据
- 不可重复读:同一事务内多次读同一行,值不同
- 幻读:同一查询条件,前后 行数 变了(新增/删除)
- 串行化冲突:最高隔离下并发被强制排队/回滚
隔离级别选择建议:
- 读多写少:
READ_COMMITTED
足矣 - 金融/库存高一致:
REPEATABLE_READ
+ 乐观/悲观锁 - 极端一致性:考虑
SERIALIZABLE
但要压测
5) 🧨 事务回滚机制(与你“想象的不一样”的地方)
5.1 Spring 默认回滚规则
-
默认只对
RuntimeException
和Error
回滚 -
受检异常(
Exception
)默认不回滚- 如果你抛的是业务异常(受检),结果却提交了,那就是这个规则在“背刺”你
定制方式:
@Transactional(rollbackFor = Exception.class)
public void doBiz() throws Exception {
// 任何 Exception 都回滚
}
5.2 捕获异常后如何仍然回滚?
下述代码中你捕获了异常,“默认”就不会回滚——因为代理没看到异常抛出。
@Transactional
public void foo() {
try {
bar();
} catch (BizException e) {
// 你吞了异常,外面看不到异常 —— 默认不会回滚
}
}
两种方式要么抛回,要么手动标记:
// 方案1:重新抛出(推荐,清晰)
catch (BizException e) {
throw e;
}
// 方案2:标记回滚(不抛出也可回滚)
catch (BizException e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
5.3 @TransactionalEventListener
与提交后动作
- 你可能希望“提交成功后再发消息/清缓存”
- 用
@TransactionalEventListener(phase = AFTER_COMMIT)
保证只在成功提交后触发
@Component
public class OrderEvents {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onOrderCreated(OrderCreatedEvent evt) {
// 只在事务提交后执行
// publish to MQ / clear cache / notify downstream
}
}
配合 Outbox 模式:把要发布的消息先写到本地表(和业务数据同事务),再由后台守护任务可靠投递到 MQ,实现“最终一致”。
6) 🧪 可运行 Demo:H2 + Spring Data JPA + 事务全套演示
目标:跑起来就能复现
REQUIRED
、REQUIRES_NEW
、rollbackFor
、readOnly
、timeout
、NESTED
(若使用DataSourceTransactionManager
且数据库/驱动支持保存点)等,帮助你在本地快速体感行为差异。
6.1 pom.xml
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>dev.demo</groupId>
<artifactId>tx-lab</artifactId>
<version>1.0.0</version>
<properties>
<java.version>17</java.version>
<spring-boot.version>3.3.2</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
6.2 application.yml
spring:
datasource:
url: jdbc:h2:mem:txlab;MODE=MySQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
logging:
level:
org.hibernate.SQL: debug
org.springframework.transaction: debug
6.3 实体与仓库
@Entity
@Table(name = "t_account")
@Data @NoArgsConstructor @AllArgsConstructor @Builder
public class Account {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String owner;
private Long balance; // 分为单位
}
public interface AccountRepo extends JpaRepository<Account, Long> {
Optional<Account> findByOwner(String owner);
}
6.4 Service:演示多种事务
@Service
@RequiredArgsConstructor
public class AccountService {
private final AccountRepo repo;
// 读操作,默认只读
@Transactional(readOnly = true)
public Long balanceOf(String owner) {
return repo.findByOwner(owner).map(Account::getBalance).orElse(0L);
}
// 基础写操作 REQUIRED
@Transactional
public void deposit(String owner, long amount) {
Account acc = repo.findByOwner(owner).orElseGet(() -> repo.save(Account.builder()
.owner(owner).balance(0L).build()));
acc.setBalance(acc.getBalance() + amount);
repo.save(acc);
}
// 超时演示(单位秒),强行睡眠触发回滚
@Transactional(timeout = 1)
public void slowOperation() {
repo.save(Account.builder().owner("slow").balance(1L).build());
try { Thread.sleep(2000); } catch (InterruptedException ignored) {}
}
// 受检异常默认不回滚 —— 用 rollbackFor 自定义
@Transactional(rollbackFor = Exception.class)
public void transferWithCheckedException(String from, String to, long amount) throws Exception {
withdraw(from, amount);
deposit(to, amount);
throw new Exception("业务校验失败,需要回滚"); // 将回滚
}
@Transactional
public void withdraw(String owner, long amount) {
Account acc = repo.findByOwner(owner).orElseThrow();
if (acc.getBalance() < amount) throw new IllegalStateException("余额不足");
acc.setBalance(acc.getBalance() - amount);
repo.save(acc);
}
}
6.5 传播行为:REQUIRES_NEW
与 REQUIRED
的对撞
@Service
@RequiredArgsConstructor
public class AuditService {
private final AccountRepo repo;
// 审计日志这里就简单写入一条记录到账户表做演示
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void writeAudit(String owner, String note) {
repo.save(Account.builder().owner("audit-" + owner + "-" + note).balance(0L).build());
}
}
@Service
@RequiredArgsConstructor
public class FacadeService {
private final AccountService accountService;
private final AuditService auditService;
// 外层 REQUIRED,内层 REQUIRES_NEW:内层独立提交
@Transactional
public void businessWithAudit(String owner) {
accountService.deposit(owner, 100);
try {
auditService.writeAudit(owner, "deposit-start");
if (true) throw new RuntimeException("外层故障:让我们看看审计是否提交?");
} finally {
// finally 中别乱吞异常:要么抛出,要么 setRollbackOnly
}
}
}
运行后你会发现:外层回滚,但审计“
audit-owner-deposit-start
”那条仍然留在库里,因为它是 REQUIRES_NEW 的独立事务。✅
6.6 NESTED
(保存点)演示(可选)
- 需要
DataSourceTransactionManager
+ 数据库驱动支持保存点 - JPA 默认
JpaTransactionManager
不直接暴露嵌套保存点,想用NESTED
更稳的是走 JDBC 层或切换管理器 - 演示思路:外层插入 A,内层插入 B;内层回滚(保存点),外层继续提交,最终只有 A
实务里
NESTED
少见,但在大事务里局部容错有用;否则更多会选REQUIRES_NEW
做“旁路”提交。
6.7 集成测试(看得见回滚)
@SpringBootTest
@Transactional // 每条测试默认回滚,保证幂等
class TxLabTests {
@Autowired AccountService accountService;
@Autowired FacadeService facadeService;
@Test
void testRollbackForCheckedException() {
accountService.deposit("A", 100);
assertThrows(Exception.class, () ->
accountService.transferWithCheckedException("A", "B", 50)
);
assertEquals(100L, accountService.balanceOf("A")); // 回滚成功
assertEquals(0L, accountService.balanceOf("B"));
}
@Test
void testRequiresNewAuditSurvivesOuterRollback() {
assertThrows(RuntimeException.class, () ->
facadeService.businessWithAudit("C")
);
// 外层回滚,C 的 100 不应入账
assertEquals(0L, accountService.balanceOf("C"));
// 但审计记录(写到账户表的那条)在另一个测试事务里不可见;这里仅做行为说明
}
}
7) 🧰 生产实战清单(真的能救命的那种)
7.1 多数据源与指定事务管理器
- 配置多个
PlatformTransactionManager
,用@Transactional(transactionManager = "tmX")
指明 - 跨库一致性:谨慎上 JTA/XA(Narayana/Atomikos),性能与复杂度高;优先 拆分边界 + Outbox + 最终一致性
- 极少数核心强一致再考虑 XA,并务必压测
7.2 幂等与重试
- 读已提交下偶发冲突/死锁,
@Retryable
+ 幂等写入 - 幂等关键:业务主键(如订单号)代替“裸插入自增”
- 乐观锁(
@Version
)+ 失败重试,或悲观锁(for update
)在热点短冲突
7.3 异步与事务
@Async
新线程不继承调用方事务;需要的话自己开事务- 回调/监听要用
@TransactionalEventListener(AFTER_COMMIT)
,避免在回滚时误触发下游
7.4 缓存一致性
- 写 DB 成功后再 失效缓存,不要先删缓存再写库(异常会留下“空洞”)
- 或 读穿透策略 + 双写/延时清理,配合消息投递兜底
7.5 监控与压测
- 打开
org.springframework.transaction
debug 了解事务边界 - 监控连接池活跃数/等待时长,避免长事务(手欠的
Thread.sleep
、外部慢接口在事务里) - 压测不同隔离级别下吞吐与冲突,别想当然上
SERIALIZABLE
8) 🧪 一图速览:从请求到回滚/提交
9) 🧩 常见坑位清单(对号入座别再踩)
-
自调用导致事务失效:同类里
this.method()
不经过代理- ✅ 拆 Bean,或通过接口注入代理
-
受检异常不回滚:
Exception
默认不回滚- ✅ 指定
rollbackFor=Exception.class
,或抛RuntimeException
- ✅ 指定
-
捕获异常吞掉:外层看不见异常自然不回滚
- ✅ 重新抛出,或
setRollbackOnly()
- ✅ 重新抛出,或
-
长事务:事务里打外部 HTTP、睡眠、读大表全表扫
- ✅ 把慢操作移出事务、分段提交、批处理
-
readOnly=true
当真只读:有的数据库会启用只读优化,但你写了也可能报错/静默失败- ✅ 读写分离,读方法单独只读事务
-
传播行为滥用:处处
REQUIRES_NEW
造成连接耗尽、顺序语义混乱- ✅ 明确边界,谨慎新事务
-
隔离级别想当然:以为
REPEATABLE_READ
防了所有幻读- ✅ 了解目标数据库实现差异(MySQL 与 PostgreSQL 的区别)
-
测试不隔离:测试互相污染
- ✅
@Transactional
测试默认回滚,或@DirtiesContext
控制上下文
- ✅
-
多数据源事务忘记指定管理器:导致事务管理失效
- ✅
@Transactional(transactionManager="xxx")
- ✅
10) 🍱 结论型备忘(场景 → 推荐)
场景 | 推荐 |
---|---|
典型单体/单库业务 | Service 层 @Transactional (REQUIRED + 合理 rollbackFor ) |
写库 + 审计/消息可靠写 | 主流程 REQUIRED + 审计/消息 REQUIRES_NEW 或 Outbox |
高一致读写 | REPEATABLE_READ (MySQL)+ 乐观/悲观锁 + 幂等重试 |
跨库强一致 | 谨慎 JTA/XA;优先领域拆分 + 最终一致 |
慢外部调用 | NOT_SUPPORTED 或移出事务 |
复杂同方法细粒度控制 | TransactionTemplate 编程式事务 |
测试 | 方法级 @Transactional 自动回滚,构造初始数据用 @Sql |
🎁 附:一个“从 0 到 1”的 Mini 场景(吞异常也回滚 + 提交后发消息)
用例:创建订单 → 扣减余额 → 写审计(新事务)→ 订单事件提交后异步发布
@Service
@RequiredArgsConstructor
public class OrderService {
private final AccountService accountService;
private final OrderRepo orderRepo;
private final AuditService auditService;
private final ApplicationEventPublisher publisher;
@Transactional(rollbackFor = Exception.class)
public Long createOrder(String user, long amount) {
Order order = orderRepo.save(new Order(null, user, amount, "CREATED"));
try {
accountService.withdraw(user, amount); // 余额不足抛异常,整单回滚
} catch (Exception e) {
// 捕获后仍需回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw e;
}
// 审计走独立新事务 —— 即使外层后续异常也能保留轨迹
auditService.writeAudit(user, "order-created-" + order.getId());
// 订单创建事件:等事务提交后再发
publisher.publishEvent(new OrderCreatedEvent(order.getId()));
return order.getId();
}
}
@Component
@RequiredArgsConstructor
class OrderEventHandler {
private final MessageGateway mq;
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onCreated(OrderCreatedEvent evt) {
mq.publish("order.created", Map.of("orderId", evt.orderId()));
}
}
这套组合拳的效果:
- 业务异常 → 订单回滚,余额不变
- 审计日志(
REQUIRES_NEW
)仍然存在(有痕可查) - 只有在订单提交成功后,消息才会被发布(避免脏事件)
🧠 最后,灵魂三问(也是面试高频)
-
为什么我的
@Transactional
不生效?- 自调用?非 public?代理类型不对?方法签名被
final
?切点没匹配到?
- 自调用?非 public?代理类型不对?方法签名被
-
REQUIRES_NEW
和NESTED
有什么区别?- 前者独立事务(挂起外层),后者保存点回滚(受外层约束)
-
怎么保证“写库 + 发消息”要么都成功要么都失败?
- 本地事务 + Outbox +
AFTER_COMMIT
监听 + 后台可靠投递
- 本地事务 + Outbox +
🧾 小结(拍桌版)
事务不是“有就开一个”的开关,而是一门边界学。 Spring 帮你把“如何开关”标准化了,但“何时开、开多大、怎么组合、失败怎么处理”依然是你的技术与经验。掌握 @Transactional
的每个开关(传播/隔离/回滚/只读/超时),认清代理调用与自调用的“戏法”,把审计/消息/异步安排到对的事务相位,你的系统就会从“偶发脏数据惊魂”进化为“稳定厚重可观测”。🧘♂️
📌 彩蛋:事务调试“三件套”
logging.level.org.springframework.transaction=debug
看边界logging.level.org.hibernate.SQL=debug
看 SQL- 用 Mermaid 把你的业务调用链画出来,找自调用与传播“错配点”
祝你以后每一次“提交”都胸有成竹,每一次“回滚”都心里有数。🙌 如果想要把这篇扩成“事务专题电子书”(更细到行级锁、MVCC、不同数据库实现差异、XA vs Saga 全对拍),跟我说一声,我继续加料!
🧧福利赠与你🧧
无论你是计算机专业的学生,还是对编程有兴趣的小伙伴,都建议直接毫无顾忌的学习此专栏「滚雪球学SpringBoot」专栏(全网一个名),bug菌郑重承诺,凡是学习此专栏的同学,均能获取到所需的知识和技能,全网最快速入门SpringBoot,就像滚雪球一样,越滚越大, 无边无际,指数级提升。
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。
✨️ Who am I?
我是bug菌(全网一个名),CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云多年度十佳博主/价值贡献奖,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;更多精彩福利点击这里;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿。

-End-
- 点赞
- 收藏
- 关注作者
评论(0)