ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Java事务传播-派系之争

Java事务传播-派系之争

前言:

你还在死记硬背Java事务传播方式吗,你是否为事务传播特性差异难以理解而苦恼吗,下面我将通过一个简单的事务行为分类,让你彻底理解和记住Java的七种事务传播行为。

首先我们看一下Java支持哪些事务传播行为

传播行为核心逻辑(面对当前事务)是否必须有事务?
REQUIRED(默认)有事务就加入,没有就新建一个。是(没有就建)
SUPPORTS有事务就加入,没有就以非事务方式运行。否(随缘)
MANDATORY必须已有事务,否则抛异常。是(必须存在)
REQUIRES_NEW挂起当前事务,永远新建一个独立事务。是(强制新建)
NOT_SUPPORTED挂起当前事务,以非事务方式运行。否(拒绝事务)
NEVER必须没有事务,否则抛异常。否(强制非事务)
NESTED如果存在事务,则创建嵌套事务(保存点);否则新建事务。是(存在则嵌套)

我们通过这些行为的特性,分类为下面四种:支持派、反对派、独立派和分支派

派系关键字包含行为核心逻辑(人话版)经典场景
支持派同生共死REQUIRED
SUPPORTS
MANDATORY

有事务就加入,

没事务时

REQUIRED建新事务、SUPPORTS不要事务MANDATORY直接报错

常规业务(增删改查),99%的代码都用REQUIRED
独立派各不相干REQUIRES_NEW
NOT_SUPPORTED

挂起外面的事务,自己单独玩

REQUIRES_NEW新开事务,NOT_SUPPORTED非事务执行

审计日志(业务失败也要存)、大文件导出(释放锁)
反对派拒之门外NEVER检测到有事务就直接报错,绝不妥协缓存清理全文索引(防止死锁)
分支派主干分支NESTED寄生在主事务里,但自己打标记;局部失败只回滚自己,主干失败全完蛋批量导入(1000条里坏几条,只回滚坏的那几条)

想象下面这个后厨工作场景

  • 主厨(主事务):正在炖一锅巨大的佛跳墙(主事务)。

  • 支持派:你进来帮主厨切菜,切坏了(异常)整锅倒掉。

  • 独立派:你端走一口新锅,去旁边单独炒个青菜,炒糊了只倒自己这盘。

  • 反对派:你看到主厨在炖佛跳墙,嫌油烟大,直接摔围裙走人(报错),要求必须关火你才进厨房。

  • 分支派(NESTED:主厨让你在这个大锅旁边拿个小碗调点酱汁。调坏了(异常)只把你小碗里的倒掉(回滚到保存点),大锅继续炖;但如果主厨心情不好,把整锅佛跳墙都泼了(主事务回滚),你那碗酱汁就算调好了也得跟着倒进下水道。

代码样例:

@Service public class OrderService { @Autowired private InventoryService inventoryService; @Autowired private AuditLogService auditLogService; @Autowired private PromotionService promotionService; @Autowired private NotificationService notificationService; @Autowired private QueryService queryService; @Autowired private SecurityService securityService; @Autowired private CacheService cacheService; /** * 场景 1:REQUIRED(默认) * 主事务入口,整个下单流程的大事务。 * 如果后续子事务抛出异常(且未捕获),所有 REQUIRED 和 NESTED 操作都会回滚。 */ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void placeOrder(String orderId) { System.out.println("========== 主事务开始 =========="); // 1. 扣减核心库存(REQUIRED -> 加入当前事务) inventoryService.deductCoreStock(orderId); // 2. 校验安全规则(MANDATORY -> 必须在当前事务内) securityService.verifySecurity(orderId); // 3. 批量扣减优惠券(NESTED -> 使用保存点,部分回滚) promotionService.batchDeductCoupons(orderId); // 4. 记录操作日志(REQUIRES_NEW -> 独立事务,不受主事务回滚影响) auditLogService.logOrderCreation(orderId); // 5. 发送短信(NOT_SUPPORTED -> 挂起当前事务,非事务执行) notificationService.sendSms(orderId); // 6. 查询额外数据(SUPPORTS -> 随缘事务,只读查询) queryService.getProductInfo(orderId); // 7. 清除本地缓存(NEVER -> 若当前有事务则抛异常) cacheService.evictLocalCache(orderId); System.out.println("========== 主事务提交 =========="); } } @Service public class InventoryService { /** * 场景 2:REQUIRED(加入主事务) * 扣减核心库存。如果这里抛异常,主事务整体回滚。 */ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class) public void deductCoreStock(String orderId) { System.out.println(" [REQUIRED] 扣减核心库存,加入主事务"); // 模拟数据库操作 // int rows = stockMapper.deduct("product_001", 1); // if (rows == 0) throw new RuntimeException("库存不足"); } } @Service public class SecurityService { /** * 场景 3:MANDATORY(必须存在事务) * 强制要求调用方必须在事务内。如果脱离事务单独调用,直接抛出异常。 * 适用:敏感操作强制与业务事务绑定。 */ @Transactional(propagation = Propagation.MANDATORY) public void verifySecurity(String orderId) { System.out.println(" [MANDATORY] 校验安全规则(必须在事务中)"); // 如果当前没有事务,这里会抛出 IllegalTransactionStateException } } @Service public class PromotionService { /** * 场景 4:NESTED(嵌套事务,基于保存点 Savepoint) * 批量扣减多张优惠券。假设第3张券失效,仅回滚到保存点(即只回滚第3张), * 不影响主事务和其他券的扣减。 */ @Transactional(propagation = Propagation.NESTED) public void batchDeductCoupons(String orderId) { System.out.println(" [NESTED] 开始批量扣减优惠券"); String[] coupons = {"COUPON_A", "COUPON_B", "COUPON_C"}; for (int i = 0; i < coupons.length; i++) { // 模拟扣减第3张券时失败 if (i == 2) { System.out.println(" [NESTED] 优惠券 C 扣减失败,仅回滚本保存点"); throw new RuntimeException("优惠券无效"); } System.out.println(" [NESTED] 优惠券 " + coupons[i] + " 扣减成功"); } // 注意:即使这里抛异常,主事务中前面扣减的库存不会回滚,除非主事务捕获异常后主动回滚。 // 在实际开发中,通常需要捕获 NESTED 的异常,决定是否继续。 } } @Service public class AuditLogService { /** * 场景 5:REQUIRES_NEW(独立事务) * 无论主订单是否回滚(比如库存不足导致下单失败), * "用户尝试下单"的操作日志必须持久化保存,用于审计排查。 */ @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void logOrderCreation(String orderId) { System.out.println(" [REQUIRES_NEW] 独立事务:记录操作日志"); // 这里先挂起主事务,开启新连接/事务,执行 insert,立即提交。 } } @Service public class NotificationService { /** * 场景 6:NOT_SUPPORTED(不支持事务) * 发送短信或邮件涉及网络IO,耗时长。为了避免长时间占用数据库连接, * 挂起当前事务,以非事务方式执行。 * 如果发送失败,不影响主事务的回滚(因为它不在事务内)。 */ @Transactional(propagation = Propagation.NOT_SUPPORTED) public void sendSms(String orderId) { System.out.println(" [NOT_SUPPORTED] 非事务:发送下单短信通知"); // 模拟耗时短信发送 try { Thread.sleep(100); } catch (InterruptedException e) {} // 这里异常不会导致主事务回滚(因为主事务被挂起后,这里无事务) } } @Service public class QueryService { /** * 场景 7:SUPPORTS(支持事务,但不强制) * 查询操作。如果当前有事务,则加入(并可以使用 readOnly 优化); * 如果没有事务,则以普通非事务查询执行。 * 通常用于 Service 层内部的只读查询。 */ @Transactional(propagation = Propagation.SUPPORTS, readOnly = true) public void getProductInfo(String orderId) { System.out.println(" [SUPPORTS] 只读查询(有事务则加入,无则忽略)"); // select * from product where id = ... } } @Service public class CacheService { /** * 场景 8:NEVER(禁止事务) * 强制要求当前不能存在事务。如果在主事务中被调用,会直接抛出异常。 * 场景:某些缓存更新(如 Redis)或全文索引同步,如果在事务中执行, * 可能会因为数据库长事务导致死锁或性能下降。 */ @Transactional(propagation = Propagation.NEVER) public void evictLocalCache(String orderId) { System.out.println(" [NEVER] 清除本地缓存(强制无事务)"); // 如果当前存在事务,抛出 IllegalTransactionStateException } }
返回列表