@Transactional 失效的 8 个场景——90% 的 Java 开发者都踩过至少一个
引言
上季度有个资金对账任务出了个诡异 bug:扣款和加款两条 SQL,一条提交了,另一条抛异常没执行——钱少扣了,账不平了。排查了半天,最后发现原因是同事为了"复用代码",把事务方法从订单 Service 拆到了另一个类里,然后通过 this.xxx() 调用——事务悄悄失效了,代码没有任何报错,测试全绿。
@Transactional 的失效是最阴险的一类 bug:它不会报错、不会抛异常、甚至大部分场景连日志都没有,只是在你以为有事务的时候,实际上每条 SQL 都在自动提交。平时没事,等出问题就是数据不一致级别的故障。
这篇文章把 8 个失效场景一次讲全。每个场景给三样东西:能跑的失效代码、背后原理、最小修复方案。先给一张总览表,读者可以对照自查:
| # | 场景 | 失效本质 | 频率 |
|---|---|---|---|
| 1 | 非 public 方法 | 代理只增强 public 方法 | 高 |
| 2 | 同类内部调用 this.xxx() | 调用绕过了代理对象 | 极高 |
| 3 | 异常被 catch 吞掉 | 代理看不到异常,无法触发回滚 | 高 |
| 4 | rollbackFor 没配对 | 默认只回滚 RuntimeException/Error | 高 |
| 5 | 多线程 | 事务绑定 ThreadLocal 连接,不跨线程 | 中 |
| 6 | 数据库引擎不支持 | MyISAM 没有事务能力 | 低 |
| 7 | 传播行为配置错误 | NOT_SUPPORTED 挂起事务/REQUIRES_NEW 边界误解 | 中 |
| 8 | final / static 方法 | CGLIB 无法重写 | 低 |
一、场景一:非 public 方法——代理只增强 public
1.1 失效代码
@Service
public class OrderService {
@Transactional
protected void createOrder(Order order) { // ❌ protected
orderMapper.insert(order);
stockMapper.deduct(order.getProductId(), order.getQuantity());
// 两步之间抛异常 → 不会回滚(因为事务根本没开启)
}
}
1.2 原理
Spring 事务是代理模式:容器里注入的其实是 OrderService 的代理对象,代理在目标方法前后做"开启事务 → 执行 → 提交/回滚"。而代理实现对方法可见性有要求:
- CGLIB(Spring Boot 默认):通过生成子类 + 方法重写实现增强。protected 方法技术上可以重写,但 Spring 事务的
AbstractFallbackTransactionAttributeSource明确只处理 public 方法(computeTransactionAttribute 里allowPublicMethodsOnly()检查),protected/package-private 直接返回 null 事务属性——等于没配。 - JDK 动态代理:只能代理接口方法,非 public 更不可能。
代理不认识这个方法上的注解 → 事务切面根本不生效 → 方法裸奔执行。
1.3 修复
// ✅ 方案一(推荐):改成 public
@Transactional
public void createOrder(Order order) { ... }
// ✅ 方案二:protected 换成"拆类调用"——见场景二的通用解法
IDEA 的内置帮助:安装 Spring 插件后,非 public 方法上的 @Transactional 会显示灰色警告 "Spring @Transactional method should be public"——团队的 IDE 统一装插件,一半的失效场景在写代码时就能被拦下。
二、场景二:同类内部调用 this.xxx()——最高频的坑
2.1 失效代码
@Service
public class OrderService {
public void batchImport(List<Order> orders) {
for (Order order : orders) {
this.createOrder(order); // ❌ 内部调用,事务失效
}
}
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
stockMapper.deduct(order.getProductId(), order.getQuantity());
}
}
现象:createOrder 单独被 Controller 调用时事务正常;被 batchImport 里 this.createOrder() 调用时完全无事务。
2.2 原理:this 不是那个代理
Spring 容器里注入的是代理对象,但对象内部的方法互调走的是 this 引用——this 指向的是原始对象(代理对象内部的 target),而不是代理本身:
外部调用(正常):
Controller → [代理: 开事务 → OrderService.createOrder() → 提交/回滚]
内部调用(失效):
Controller → [代理: batchImport 无事务,直接执行]
→ this.createOrder() ← this 是原始对象,不是代理
→ 直接执行方法体,没有任何事务逻辑
@Transactional 是靠"拦截代理上的方法调用"生效的,this 调用天然绕过代理——注解还在那里,但没人去读它。
2.3 修复:三种方案按优先级
// ✅ 方案一(推荐):拆类——把有事务的方法挪到另一个 Bean
@Service
public class OrderTxService {
@Transactional
public void createOrder(Order order) { ... }
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderTxService orderTxService; // 注入的是代理
public void batchImport(List<Order> orders) {
orders.forEach(orderTxService::createOrder); // 走代理,事务生效
}
}
// ✅ 方案二:注入自身代理((ObjectProvider) 拿自己,避免构造循环依赖)
@Service
public class OrderService {
@Autowired
private ObjectProvider<OrderService> selfProvider;
public void batchImport(List<Order> orders) {
OrderService self = selfProvider.getObject(); // 这是代理对象
orders.forEach(self::createOrder); // ✅ 走代理
}
}
// ✅ 方案三:AopContext.currentProxy()(需 @EnableAspectJAutoProxy(exposeProxy = true))
@Transactional
public void createOrder(Order order) { ... }
public void batchImport(List<Order> orders) {
OrderService proxy = (OrderService) AopContext.currentProxy();
orders.forEach(proxy::createOrder); // ✅ 显式用代理
}
// 缺点:侵入性强、依赖暴露开关,能不用就不用
顺带一提:把 batchImport 整个标 @Transactional 也能"解决"问题(外层有事务,内层被包住),但大事务包住整个导入过程,连接长时间占用——数据正确性和系统吞吐要一起考虑,别只修 bug 不看代价。
三、场景三:异常被 catch 吞掉——代理"看不见"异常
3.1 失效代码
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
try {
orderMapper.insert(order);
stockMapper.deduct(order.getProductId(), order.getQuantity()); // 这里抛异常
} catch (Exception e) {
log.error("创建订单失败", e); // ❌ 吞了异常,没有重新抛出
// 方法正常返回 → 代理只看到"正常结束" → commit
}
}
}
结果:insert 和 deduct 都在事务里,异常被 catch 后方法正常返回,代理以为一切顺利,执行 commit——半截业务数据被提交进库。
3.2 原理
回滚的触发点是代理的异常感知:TransactionInterceptor.invoke 在目标方法抛出异常时执行 rollback。异常被 catch 住且不重新抛出,代理看来就是一次正常返回——它没有读心术。
3.3 修复
// ✅ 方案一(推荐):catch 里处理完后必须重新抛出(或抛业务异常)
@Transactional
public void createOrder(Order order) {
try {
orderMapper.insert(order);
stockMapper.deduct(order.getProductId(), order.getQuantity());
} catch (Exception e) {
log.error("创建订单失败", e);
throw new BizException("ORDER_CREATE_FAIL", e.getMessage()); // ✅ 抛出去让代理看见
}
}
// ✅ 方案二:确实想吞异常但需要回滚时,手动标记回滚
@Transactional
public void createOrder(Order order) {
try {
orderMapper.insert(order);
stockMapper.deduct(...);
} catch (Exception e) {
log.error("创建订单失败", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // ✅ 显式回滚
// 不抛异常,方法正常返回,但事务已标记回滚
}
}
衍生场景:catch 的是 Throwable/Exception 但抛出的是 @Transactional(noRollbackFor = XxxException.class) 声明的不回滚异常、或 catch 后抛出的异常是受检异常且没配 rollbackFor(衔接场景四)——多个场景叠加时按此逻辑逐层剥。
四、场景四:rollbackFor 没配对——默认只回滚 RuntimeException
4.1 失效代码
@Transactional
public void importOrders(List<Order> orders) throws IOException {
orderMapper.batchInsert(orders);
File auditFile = writeAuditLog(orders); // 抛 IOException(受检异常)
// ❌ IOException 属于受检异常 → 默认不回滚 → batchInsert 已提交
}
4.2 原理
@Transactional 的默认回滚规则:RuntimeException 和 Error 回滚,受检异常(checked)不回滚。源码在 DefaultTransactionAttribute.rollbackOn:
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error); // 默认规则
}
这个设计沿袭了 EJB 的约定:"受检异常被视为调用方可以预期的业务情况,不视为系统错误"。但对业务开发者来说,这个默认值经常反直觉——抛了异常数据却提交了。
4.3 修复
// ✅ 统一规范:显式配置 rollbackFor,一劳永逸
@Transactional(rollbackFor = Exception.class)
public void importOrders(List<Order> orders) throws IOException {
orderMapper.batchInsert(orders);
writeAuditLog(orders); // 现在受检异常也回滚了
}
团队规范建议:项目级规定所有 @Transactional 必须显式写 rollbackFor = Exception.class,把这个"知道默认规则"的智力负担直接消除掉。IDEA 的 Spring 插件同样会对此给出提示。
五、场景五:多线程——事务不跨线程传播
5.1 失效代码
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class)
public void processOrder(Order order) {
orderMapper.insert(order);
CompletableFuture.runAsync(() -> {
logMapper.insert(buildLog(order)); // ❌ 新线程
if (order.isVip()) {
pointsMapper.add(order.getUserId(), 100); // ❌ 抛异常
}
}); // 子线程里的操作不在主事务里:主事务回滚,子线程照常提交
}
}
现象:主事务因其他异常回滚了,但子线程的日志和积分已经提交——数据不一致。
5.2 原理
Spring 事务通过 ThreadLocal(TransactionSynchronizationManager 里的连接和资源绑定)实现"同一个事务的多个 SQL 用同一个数据库连接"。ThreadLocal 是线程隔离的,新线程拿不到主线程的连接:
- 子线程的
logMapper.insert走的是连接池新拿的连接、自动提交——和主事务零关系; - 主线程 commit/rollback 影响不了子线程已提交的数据;
- 子线程抛异常也传导不回主线程的事务(除非手动 join 并传播)。
5.3 修复:想清楚事务边界在哪个线程
// ✅ 方案一(推荐):把需要事务的操作留在主线程,子线程只做不需要事务的事
@Transactional(rollbackFor = Exception.class)
public void processOrder(Order order) {
orderMapper.insert(order);
logMapper.insert(buildLog(order)); // 事务内
if (order.isVip()) {
pointsMapper.add(order.getUserId(), 100); // 事务内
}
// 发短信、写 ES 等不需要事务的 → 异步
asyncNotifySender.send(order); // 子线程,失败可补偿
}
// ✅ 方案二:子线程独立事务 + 明确的补偿语义(接受最终一致)
@Transactional(rollbackFor = Exception.class)
public void processOrder(Order order) {
orderMapper.insert(order);
CompletableFuture.runAsync(() ->
self.pointsInNewTx(order) // 子线程自己开新事务(REQUIRES_NEW 语义)
);
}
关键认知:不存在"把主线程事务传给子线程"的办法——事务的物理基础是"一个数据库连接",连接不能跨线程共享。跨线程的一致性只能靠架构手段(消息表、事务消息、补偿)解决。
六、场景六:数据库引擎不支持——MyISAM 没有事务
6.1 失效代码
-- 表是 MyISAM 引擎
CREATE TABLE t_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
...
) ENGINE=MyISAM;
Java 侧 @Transactional 配置完全正确,但异常后数据照样提交了。
6.2 原理
事务最终是数据库引擎的能力。MyISAM 不支持事务(也不支持行锁、崩溃安全恢复),Spring 在应用层把事务该做的事都做了(BEGIN→执行→ROLLBACK),但引擎层面根本没有回滚这回事。Spring 还会依赖 JDBC 连接的 autocommit 语义——MyISAM 下这些全是空操作。
断点确认法:在 DataSourceTransactionManager.doBegin 打断点,看 con.getAutoCommit() 与 SHOW TABLE STATUS WHERE Name='t_order' 的 Engine 列——应用层正常 + 引擎 MyISAM,就是本场景。
6.3 修复
-- ✅ 换成 InnoDB(MySQL 5.5+ 默认引擎)
ALTER TABLE t_order ENGINE=InnoDB;
-- 迁移前评估:全文索引改用 ES、全文索引以外的 MyISAM 特性极少有真实依赖
-- MySQL 8.0 已彻底移除对 MyISAM 事务的幻想,别再新用
老系统排查:SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE ENGINE='MyISAM' AND TABLE_SCHEMA='your_db'; 一次查清存量,逐个迁移。
七、场景七:传播行为配置错误
7.1 典型失效:NOT_SUPPORTED 挂起了事务
@Transactional(propagation = Propagation.NOT_SUPPORTED) // ❌ 主动挂起事务、以非事务方式执行
public void readStatistics(String date) { ... }
// 调用方:
@Transactional(rollbackFor = Exception.class)
public void dailyJob() {
readStatistics(today); // 主事务被挂起!readStatistics 内的写操作以自动提交跑
updateJobStatus(today); // 后续主事务继续,但前面已经提交出去的数据回不来了
}
7.2 七种传播行为速查(重点标出"容易踩"的)
| 传播行为 | 行为 | 踩坑点 |
|---|---|---|
| REQUIRED(默认) | 有事务加入,没有就新建 | 安全 |
| REQUIRES_NEW | 挂起当前事务,新开独立事务 | 内层提交后数据已落库,外层回滚也收不回("我以为一起回滚了") |
| NESTED | 保存点(savepoint)式嵌套,外层回滚连带内层 | 与 REQUIRES_NEW 语义差一体现在"外层回滚是否波及内层" |
| NOT_SUPPORTED | 挂起当前事务,以非事务执行 | "明明在事务方法里调的,怎么没事务" |
| NEVER | 有事务就抛异常 | 配错直接炸 |
| MANDATORY | 必须有事务,否则抛异常 | 反向约束,反而是好的防御 |
| SUPPORTS | 有就用没有就不开 | 读接口配它常配出"时而有时而无"的诡异现象 |
7.3 修复原则
// ✅ 场景一:外层大事务中调"耗时的纯读统计" → 拆出去,别用 NOT_SUPPORTED 塞回来
public void dailyJob() {
statisticService.readStatistics(today); // 无事务方法,事务外执行,语义清晰
txService.updateJobStatus(today); // 独立的小事务
}
// ✅ 场景二:日志/审计类操作希望"主事务回滚也要保留" → REQUIRES_NEW,但要接受它的语义
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void auditLog(AuditEvent event) { ... }
// 用前自问:这条数据"外层回滚后仍存在"是需求还是隐患?
修复的核心方法论:传播行为不是"配上去试试看",而是先用一句话说清这条数据在"外层失败"时的预期命运(共存亡 / 独立存活 / 永不参与),再反查上表选行为。
八、场景八:final / static 方法——CGLIB 重写不了
8.1 失效代码
@Service
public class OrderService {
@Transactional
public final void createOrder(Order order) { // ❌ final:CGLIB 无法重写
...
}
@Transactional
public static void createOrderStatic(Order order) { // ❌ static:不属于实例方法
...
}
}
8.2 原理
- CGLIB 靠子类重写方法实现增强,final 方法不可被重写——Spring 事务源码同样在方法可见性检查里对 final 做了过滤(与场景一同一个 checkPoint),注解被静默忽略;
- static 方法属于类而非实例,代理是"实例级"的,根本没有拦截入口;
- JDK 动态代理下更直接:非接口方法全都不代理。
附带提醒:Spring Boot 2.0+ 默认 proxyTargetClass=true(CGLIB),构造器注入的 final 字段没问题——失效的只是 final 方法,别把 final 字段和 final 方法搞混。
8.3 修复
// ✅ 去掉 final / static;如果 final 是为了"禁止子类重写",
// 事务方法的扩展点请放在类级别(类本身不加 final),方法交给代理
@Transactional
public void createOrder(Order order) { ... }
补充一个同族冷知识:@Transactional 加在接口方法上、用 JDK 代理时能生效,但切换到 CGLIB 代理模式后会有兼容性边界(部分版本不继承接口注解)——团队规范建议注解一律加在实现类方法上,不赌代理模式的差异。
九、自查工具:把失效场景拦在上线前
9.1 静态检查:IDEA 内置能力
| 检查项 | 位置 |
|---|---|
| 非 public 事务方法 | Spring 插件自动黄底/灰提示 |
| rollbackFor 缺省提示 | Settings → Inspections → Spring → Spring Transactions |
| 自调用警告 | 插件会提示 "Method called from the same class"(需手动确认) |
9.2 运行时验证:一行日志证明事务存在
最可靠的验证方式——在 DataSourceTransactionManager 打日志断点,或临时开 DEBUG:
logging:
level:
org.springframework.transaction: DEBUG
org.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUG
# 正常开启时能看到:
# Acquired Connection [...] for JDBC transaction
# Committing JDBC transaction on Connection [...]
测试基建建议:对资金/库存等核心事务方法写"失败注入"单测——人为让第二步抛异常,断言第一步的数据不存在。这个用例在场景 2/3/4 的任何一次重构回归中都能第一时间报警。
@Test
void createOrder_rollback_whenDeductFails() {
when(stockMapper.deduct(any(), any())).thenThrow(new RuntimeException("库存不足"));
assertThatThrownBy(() -> orderService.createOrder(order));
assertThat(orderMapper.count(order.getId())).isZero(); // ✅ 必须为 0,说明回滚了
}
十、常见问题
10.1 为什么 IDEA 有时给 this 调用报黄,有时不报?
IDEA Spring 插件的自我调用检测依赖方法解析成功(lambda 引用、方法引用、反射调用都可能漏报)。IDE 提示是辅助不是保障,团队 Code Review 把"事务方法有没有被同类别名调用"列为固定检查项。
10.2 @Transactional 加在类上和方法上有什么区别?
类级注解作用于该类所有 public 方法(不含非 public,规则同场景一)。风险是"被动覆盖":加个新的 public 方法默认进事务,可能把长事务包装了不该包的操作。建议:方法级精确标注,类级仅用于"整个 Service 都是写操作"的窄场景。
10.3 readOnly=true 有什么用?会失效吗?
@Transactional(readOnly = true) 的作用有二:驱动层优化(MySQL 驱动关闭脏检查、JPA 跳过快照管理)、以及传播约束(只读事务里执行写操作部分驱动/ORM 会报错)。它"失效"的常见姿势:只读方法里被塞进了写 SQL 但没开对应校验,于是它退化成一个普通事务——语义上人畜无害,性能上白配。查数据接口配只读是值得保持的习惯。
10.4 多个事务管理器(多数据源)时怎么失效?
@Transactional 默认绑 transactionManager 这个 Bean。多数据源没配 @Transactional("orderTxManager") 指定时,事务管理器可能拿到的连接和 Mapper 实际用的数据源不是同一个——事务照常"开启/提交",但对真正的目标库毫无作用。排查标志:DEBUG 日志里 Acquired Connection 的连接池标识和 Mapper 实际执行的不一致。
10.5 Spring Boot 3 / Spring 6 有新增的失效场景吗?
核心机制未变,8 个场景全部适用。两个新注意点:① AOT/Native 场景对动态代理的限制更严格,CGLIB 相关场景(1/8)在 GraalVM Native 下行为需专门验证;② 声明式事务的新选项(@Transactional 的 isolation 变体、TransactionTemplate 编程式)不影响失效规则。结论:这张 8 场景清单在 Spring 5/6 通用。
10.6 有没有"一劳永逸"替代声明式事务的方案?
编程式事务(TransactionTemplate.execute(...))可以天然规避场景 1/2/8——事务边界是显式代码块,不存在代理匹配问题,且边界一目了然。代价是模板代码和"事务边界不显眼"。团队策略建议:常规 CRUD 用声明式 + 规范(rollbackFor 必写、public、不内部调用);复杂边界/多线程边界用编程式。
十一、总结
8 场景速查卡
┌────┬───────────────────┬──────────────────────────────┬──────────────────────┐
│ # │ 场景 │ 本质 │ 最小修复 │
├────┼───────────────────┼──────────────────────────────┼──────────────────────┤
│ 1 │ 非 public 方法 │ 代理只增强 public │ 改 public / 拆类 │
│ 2 │ this 内部调用 │ 绕过代理对象 │ 拆类注入 / 自身代理 │
│ 3 │ 异常被 catch │ 代理感知不到异常 │ 重抛 / setRollbackOnly│
│ 4 │ rollbackFor 缺省 │ 受检异常默认不回滚 │ rollbackFor=Exception│
│ 5 │ 多线程 │ ThreadLocal 连接不跨线程 │ 事务收主线程/独立事务 │
│ 6 │ MyISAM 引擎 │ 引擎无事务能力 │ 换 InnoDB │
│ 7 │ 传播行为配错 │ NOT_SUPPORTED 挂起 / R_N 误解 │ 按数据命运反查行为 │
│ 8 │ final/static 方法 │ CGLIB 无法重写/实例外方法 │ 去 final/static │
└────┴───────────────────┴──────────────────────────────┴──────────────────────┘
一句话
@Transactional 的 8 个失效场景,本质只有一句话:注解是给"代理对象"看的,凡是让方法调用绕开代理、让代理看不见异常、或让数据库不支持回滚的情况,事务就静默消失。抓住"代理"这个关键词,8 个场景其实是一个原理的 8 种表现——而防御它只需要三件事:rollbackFor 必写、事务方法不内部调用不 private 不 final、失败注入单测进 CI。
给团队的建议
| 项 | 建议 |
|---|---|
| 编码规范 | @Transactional(rollbackFor = Exception.class) 全量显式;只标实现类方法 |
| Code Review | 三查:非 public?同类别名调用?catch 有没有吞? |
| 测试 | 资金/库存类事务方法必须配失败注入单测 |
| IDE | 全员装 Spring 插件,事务警告当 error 级对待 |
| 数据库 | 存量 MyISAM 排查清零 |
| 复杂边界 | 多线程/跨服务一致性不用事务硬扛,用消息表/补偿 |
互动话题:8 个场景你踩过几个?被
this内部调用坑得最惨的一次是什么情况?评论区聊聊,看看谁的"最阴险失效"能刷新下限。
参考资料
- Spring Framework 官方文档:Transaction Management
- Spring 官方文档:声明式事务的 @Transactional 说明
- AbstractFallbackTransactionAttributeSource 源码(public 检查)
- DefaultTransactionAttribute 源码(默认回滚规则)
- Spring Boot 官方文档:代理方式(proxyTargetClass)
- MySQL 官方文档:InnoDB 与 MyISAM 对比
- TransactionSynchronizationManager(ThreadLocal 事务资源绑定)
标题:@Transactional 失效的 8 个场景——90% 的 Java 开发者都踩过至少一个
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/09/07/1788593168238.html
公众号:服务端技术精选
- 引言
- 一、场景一:非 public 方法——代理只增强 public
- 1.1 失效代码
- 1.2 原理
- 1.3 修复
- 二、场景二:同类内部调用 this.xxx()——最高频的坑
- 2.1 失效代码
- 2.2 原理:this 不是那个代理
- 2.3 修复:三种方案按优先级
- 三、场景三:异常被 catch 吞掉——代理"看不见"异常
- 3.1 失效代码
- 3.2 原理
- 3.3 修复
- 四、场景四:rollbackFor 没配对——默认只回滚 RuntimeException
- 4.1 失效代码
- 4.2 原理
- 4.3 修复
- 五、场景五:多线程——事务不跨线程传播
- 5.1 失效代码
- 5.2 原理
- 5.3 修复:想清楚事务边界在哪个线程
- 六、场景六:数据库引擎不支持——MyISAM 没有事务
- 6.1 失效代码
- 6.2 原理
- 6.3 修复
- 七、场景七:传播行为配置错误
- 7.1 典型失效:NOT_SUPPORTED 挂起了事务
- 7.2 七种传播行为速查(重点标出"容易踩"的)
- 7.3 修复原则
- 八、场景八:final / static 方法——CGLIB 重写不了
- 8.1 失效代码
- 8.2 原理
- 8.3 修复
- 九、自查工具:把失效场景拦在上线前
- 9.1 静态检查:IDEA 内置能力
- 9.2 运行时验证:一行日志证明事务存在
- 十、常见问题
- 10.1 为什么 IDEA 有时给 this 调用报黄,有时不报?
- 10.2 @Transactional 加在类上和方法上有什么区别?
- 10.3 readOnly=true 有什么用?会失效吗?
- 10.4 多个事务管理器(多数据源)时怎么失效?
- 10.5 Spring Boot 3 / Spring 6 有新增的失效场景吗?
- 10.6 有没有"一劳永逸"替代声明式事务的方案?
- 十一、总结
- 8 场景速查卡
- 一句话
- 给团队的建议
- 参考资料
评论