Spring 声明式事务与传播行为详解
1. 简介
在分布式和高并发系统中,事务管理对于保证数据一致性和系统稳定性至关重要。Spring 提供了声明式事务管理(基于 @Transactional
注解),开发者无需手动管理事务的开启、提交与回滚。通过设置不同的事务传播行为,可以灵活应对业务场景下的方法嵌套、事务独立与局部回滚等需求。
本篇文档将详细介绍:
- 事务的基本概念及何时存在事务;
- Spring 中常见的事务传播行为(REQUIRED、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED)的作用及应用场景;
- 一个完整的高并发业务示例,通过代码展示如何在实际项目中使用这些传播行为。
2. 事务基础概念
2.1 什么是事务?
事务是一系列对数据库操作的逻辑单元,这些操作要么全部执行成功,要么全部回滚,保证数据的原子性、一致性、隔离性和持久性(ACID)。在高并发场景下,事务能防止多个并发操作对同一数据造成冲突或不一致,确保业务数据正确。
2.2 事务何时存在?
在 Spring 中,当你调用标注了 @Transactional
注解的方法时:
- 如果当前线程中不存在事务,Spring 会在方法执行前启动一个新事务;
- 如果当前线程已经存在事务,根据设置的传播行为,内层方法可能会加入当前事务或启动新的事务。
因此,事务的存在与否由事务边界决定,也依赖于业务逻辑中方法的调用顺序及其传播设置。
3. Spring 声明式事务传播行为
Spring 中常见的事务传播行为有七种,下面详细说明每种行为的特点及其应用场景:
3.1 Propagation.REQUIRED(默认传播行为)
- 作用说明:
如果当前存在事务,则加入该事务;否则,新建一个事务。 - 应用场景:
核心业务流程,如下订单操作,其中扣减库存、生成订单记录等必须在同一事务中执行,以确保数据一致性。
3.2 Propagation.REQUIRES_NEW
- 作用说明:
总是启动一个新的事务。如果当前存在事务,则会挂起当前事务,新事务执行完毕后再恢复之前的事务。 - 应用场景:
用于记录审计日志、发送通知或更新统计数据等辅助操作,这些操作需要独立提交,避免因主事务回滚而影响。
3.3 Propagation.SUPPORTS
- 作用说明:
如果当前存在事务,则加入事务;如果没有事务,则以非事务方式执行。 - 应用场景:
适用于只读查询操作,既可以在事务中运行,也可以在非事务中执行,从而减少不必要的事务开销。
3.4 Propagation.NOT_SUPPORTED
- 作用说明:
总是以非事务方式执行操作;如果当前存在事务,则会挂起事务。 - 应用场景:
调用外部系统或接口的场景,比如发送短信、邮件等通知操作,不希望事务管理对性能造成影响。
3.5 Propagation.MANDATORY
- 作用说明:
当前方法必须在已经存在的事务中执行,否则抛出异常。 - 应用场景:
核心数据操作,如财务记录更新等,要求调用方法必须在事务中进行,以确保数据的一致性。
3.6 Propagation.NEVER
- 作用说明:
方法不允许在事务中执行;若调用者存在事务,则抛出异常。 - 应用场景:
数据采集或统计操作,不希望事务管理导致额外的锁竞争和性能问题。
3.7 Propagation.NESTED
- 作用说明:
如果当前存在事务,则启动一个嵌套事务(利用数据库的保存点机制);如果不存在事务,则行为类似于 REQUIRED。 - 应用场景:
在复杂业务流程中,某个子步骤出现异常时只回滚该子步骤,而不影响整个事务的提交,如在订单优惠促销中对局部操作进行保护。
4. 高并发场景下的项目示例
下面的示例项目以订单处理为主线,模拟了高并发环境下的业务操作。项目中:
- OrderService 使用 REQUIRED 传播行为创建订单;
- AuditService 使用 REQUIRES_NEW 记录审计日志;
- 同时引入了其他业务场景,如订单查询(SUPPORTS)、外部通知(NOT_SUPPORTED)、财务处理(MANDATORY)、数据分析(NEVER)及订单促销(NESTED)。
4.1 项目结构概览
com.example.demo├── HighConcurrencyDemoApplication.java├── entity│ ├── Order.java│ ├── AuditLog.java│ ├── FinancialRecord.java├── repository│ ├── OrderRepository.java│ ├── AuditLogRepository.java│ └── FinancialRecordRepository.java├── service│ ├── OrderService.java // REQUIRED 示例(订单创建)│ ├── AuditService.java // REQUIRES_NEW 示例(审计日志)│ ├── OrderQueryService.java // SUPPORTS 示例(订单查询)│ ├── ExternalNotificationService.java // NOT_SUPPORTED 示例(外部通知)│ ├── FinancialService.java // MANDATORY 示例(财务处理)│ ├── AnalyticsService.java // NEVER 示例(数据分析)│ ├── PromotionService.java // NESTED 示例(优惠促销)│ └── OrderPromotionService.java // 外层业务调用 PromotionService└── controller├── OrderController.java // 高并发订单创建示例└── PropagationTestController.java // 各传播行为测试接口
5. 代码详细展示
下面给出各模块的代码示例。
5.1 应用入口
HighConcurrencyDemoApplication.java
package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class HighConcurrencyDemoApplication {public static void main(String[] args) {SpringApplication.run(HighConcurrencyDemoApplication.class, args);}
}
5.2 实体定义
Order.java
package com.example.demo.entity;import javax.persistence.*;@Entity
@Table(name = "orders")
public class Order {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String product;private int quantity;// getters and setterspublic Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getProduct() { return product; }public void setProduct(String product) { this.product = product; }public int getQuantity() { return quantity; }public void setQuantity(int quantity) { this.quantity = quantity; }
}
AuditLog.java
package com.example.demo.entity;import javax.persistence.*;@Entity
@Table(name = "audit_logs")
public class AuditLog {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private Long orderId;private String message;// getters and setterspublic Long getId() { return id; }public void setId(Long id) { this.id = id; }public Long getOrderId() { return orderId; }public void setOrderId(Long orderId) { this.orderId = orderId; }public String getMessage() { return message; }public void setMessage(String message) { this.message = message; }
}
FinancialRecord.java
(用于 MANDATORY 示例)
package com.example.demo.entity;import javax.persistence.*;@Entity
@Table(name = "financial_records")
public class FinancialRecord {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String description;private double amount;// getters and setterspublic Long getId() { return id; }public void setId(Long id) { this.id = id; }public String getDescription() { return description; }public void setDescription(String description) { this.description = description; }public double getAmount() { return amount; }public void setAmount(double amount) { this.amount = amount; }
}
5.3 Repository 接口
OrderRepository.java
package com.example.demo.repository;import com.example.demo.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;public interface OrderRepository extends JpaRepository<Order, Long> {
}
AuditLogRepository.java
package com.example.demo.repository;import com.example.demo.entity.AuditLog;
import org.springframework.data.jpa.repository.JpaRepository;public interface AuditLogRepository extends JpaRepository<AuditLog, Long> {
}
FinancialRecordRepository.java
package com.example.demo.repository;import com.example.demo.entity.FinancialRecord;
import org.springframework.data.jpa.repository.JpaRepository;public interface FinancialRecordRepository extends JpaRepository<FinancialRecord, Long> {
}
5.4 各业务服务实现
5.4.1 REQUIRED 与 REQUIRES_NEW 示例
OrderService.java(REQUIRED)
创建订单时开启事务,如果外层不存在事务则新建事务,随后调用审计日志方法。
package com.example.demo.service;import com.example.demo.entity.Order;
import com.example.demo.service.AuditService;
import com.example.demo.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class OrderService {@Autowiredprivate OrderRepository orderRepository;@Autowiredprivate AuditService auditService;/*** 创建订单,事务传播行为为 REQUIRED。*/@Transactional(propagation = Propagation.REQUIRED)public void createOrder(String product, int quantity) {Order order = new Order();order.setProduct(product);order.setQuantity(quantity);orderRepository.save(order);// 调用审计日志方法,使用 REQUIRES_NEW 启动独立事务记录日志auditService.recordOrderCreation(order);// 模拟业务处理耗时try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}
AuditService.java(REQUIRES_NEW)
记录订单创建日志,保证即使主事务回滚,日志依然提交成功。
package com.example.demo.service;import com.example.demo.entity.AuditLog;
import com.example.demo.entity.Order;
import com.example.demo.repository.AuditLogRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class AuditService {@Autowiredprivate AuditLogRepository auditLogRepository;/*** 记录订单创建日志,使用 REQUIRES_NEW 启动独立事务。*/@Transactional(propagation = Propagation.REQUIRES_NEW)public void recordOrderCreation(Order order) {AuditLog log = new AuditLog();log.setOrderId(order.getId());log.setMessage("Order created for product: " + order.getProduct());auditLogRepository.save(log);}
}
5.4.2 SUPPORTS 示例
OrderQueryService.java
查询订单时使用 SUPPORTS 传播行为,既可以在事务中也可在非事务中执行,适用于只读操作。
package com.example.demo.service;import com.example.demo.entity.Order;
import com.example.demo.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class OrderQueryService {@Autowiredprivate OrderRepository orderRepository;/*** 使用 SUPPORTS 传播行为查询订单,支持事务也可非事务执行。*/@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)public Order getOrderById(Long id) {return orderRepository.findById(id).orElse(null);}
}
5.4.3 NOT_SUPPORTED 示例
ExternalNotificationService.java
外部通知服务使用 NOT_SUPPORTED,确保方法在非事务环境下执行,适用于调用外部接口(如发送短信、邮件)。
package com.example.demo.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class ExternalNotificationService {/*** 使用 NOT_SUPPORTED 传播行为调用外部接口,此方法总在非事务环境下执行。*/@Transactional(propagation = Propagation.NOT_SUPPORTED)public void sendNotification(String message) {// 模拟调用外部接口,例如发送短信或邮件通知System.out.println("Sending external notification: " + message);}
}
5.4.4 MANDATORY 示例
FinancialService.java
要求必须在已有事务中调用,否则抛出异常,适用于财务记录更新等核心操作。
package com.example.demo.service;import com.example.demo.entity.FinancialRecord;
import com.example.demo.repository.FinancialRecordRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class FinancialService {@Autowiredprivate FinancialRecordRepository financialRecordRepository;/*** 更新财务记录,传播行为为 MANDATORY,必须在事务中调用。*/@Transactional(propagation = Propagation.MANDATORY)public void updateFinancialRecord(String description, double amount) {FinancialRecord record = new FinancialRecord();record.setDescription(description);record.setAmount(amount);financialRecordRepository.save(record);}
}
5.4.5 NEVER 示例
AnalyticsService.java
数据分析服务使用 NEVER,确保方法在非事务环境下执行。
package com.example.demo.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class AnalyticsService {/*** 分析数据,传播行为为 NEVER,确保不在事务中执行。*/@Transactional(propagation = Propagation.NEVER)public void processAnalytics(String data) {// 模拟处理数据分析,不依赖事务环境System.out.println("Processing analytics data: " + data);}
}
5.4.6 NESTED 示例
PromotionService.java(NESTED)
在已有事务中启动嵌套事务,允许局部回滚;示例中模拟对偶数订单号抛出异常。
package com.example.demo.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class PromotionService {/*** 应用促销优惠,使用 NESTED 传播行为,在外层事务中建立保存点。* 模拟对偶数订单抛出异常,测试局部回滚。*/@Transactional(propagation = Propagation.NESTED)public void applyPromotion(Long orderId) {System.out.println("Applying promotion for order: " + orderId);// 模拟异常情况:偶数订单号失败if(orderId % 2 == 0) {throw new RuntimeException("Promotion failed for order: " + orderId);}}
}
OrderPromotionService.java
外层方法使用 REQUIRED 事务调用嵌套事务,捕获局部异常后继续提交外层事务。
package com.example.demo.service;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class OrderPromotionService {@Autowiredprivate PromotionService promotionService;/*** 外层事务处理订单逻辑,并调用嵌套事务应用促销。*/@Transactional(propagation = Propagation.REQUIRED)public void processOrderWithPromotion(Long orderId) {System.out.println("Start processing order: " + orderId);try {promotionService.applyPromotion(orderId);} catch (Exception e) {// 只回滚嵌套事务,不影响外层事务System.out.println("Promotion error handled: " + e.getMessage());}System.out.println("Complete processing order: " + orderId);}
}
5.5 Controller 模块(高并发模拟)
5.5.1 订单 Controller(订单下单示例)
OrderController.java
通过线程池模拟高并发订单创建,每个线程调用 OrderService#createOrder,在自己的线程内独立开启事务。
package com.example.demo.controller;import com.example.demo.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;@RestController
@RequestMapping("/orders")
public class OrderController {@Autowiredprivate OrderService orderService;/*** 模拟高并发订单创建请求(使用 REQUIRED 与 REQUIRES_NEW)。* 示例调用:POST /orders/create?product=Book&quantity=1*/@PostMapping("/create")public ResponseEntity<String> createOrder(@RequestParam String product, @RequestParam int quantity) {int threadCount = 10;ExecutorService executor = Executors.newFixedThreadPool(threadCount);CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {executor.submit(() -> {try {orderService.createOrder(product, quantity);} finally {latch.countDown();}});}try {latch.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error occurred");} finally {executor.shutdown();}return ResponseEntity.ok("Orders created concurrently");}
}
5.5.2 各传播行为测试 Controller
PropagationTestController.java
提供接口分别测试 SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED 这几种传播行为,并模拟高并发调用验证效果。
package com.example.demo.controller;import com.example.demo.service.OrderQueryService;
import com.example.demo.service.ExternalNotificationService;
import com.example.demo.service.FinancialService;
import com.example.demo.service.AnalyticsService;
import com.example.demo.service.OrderPromotionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;@RestController
@RequestMapping("/propagation")
public class PropagationTestController {@Autowiredprivate OrderQueryService orderQueryService;@Autowiredprivate ExternalNotificationService externalNotificationService;@Autowiredprivate FinancialService financialService;@Autowiredprivate AnalyticsService analyticsService;@Autowiredprivate OrderPromotionService orderPromotionService;/*** SUPPORTS 测试:并发查询订单(可在事务或非事务中执行)*/@GetMapping("/supports")public ResponseEntity<String> testSupports(@RequestParam Long orderId) {int threadCount = 10;ExecutorService executor = Executors.newFixedThreadPool(threadCount);CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {executor.submit(() -> {try {orderQueryService.getOrderById(orderId);} finally {latch.countDown();}});}try {latch.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in SUPPORTS test");} finally {executor.shutdown();}return ResponseEntity.ok("SUPPORTS propagation test completed");}/*** NOT_SUPPORTED 测试:并发调用外部通知(总在非事务环境下执行)*/@GetMapping("/notSupported")public ResponseEntity<String> testNotSupported(@RequestParam String message) {int threadCount = 10;ExecutorService executor = Executors.newFixedThreadPool(threadCount);CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {executor.submit(() -> {try {externalNotificationService.sendNotification(message);} finally {latch.countDown();}});}try {latch.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in NOT_SUPPORTED test");} finally {executor.shutdown();}return ResponseEntity.ok("NOT_SUPPORTED propagation test completed");}/*** MANDATORY 测试:在已有事务中调用财务更新(成功示例)*/@GetMapping("/mandatory/with")@Transactionalpublic ResponseEntity<String> testMandatoryWithTransaction(@RequestParam String description,@RequestParam double amount) {try {financialService.updateFinancialRecord(description, amount);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("MANDATORY with transaction failed: " + e.getMessage());}return ResponseEntity.ok("MANDATORY propagation test with transaction completed");}/*** MANDATORY 测试:在无事务调用时抛出异常(预期失败)*/@GetMapping("/mandatory/without")public ResponseEntity<String> testMandatoryWithoutTransaction(@RequestParam String description,@RequestParam double amount) {try {financialService.updateFinancialRecord(description, amount);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("MANDATORY test without transaction failed as expected: " + e.getMessage());}return ResponseEntity.ok("MANDATORY test without transaction unexpectedly succeeded");}/*** NEVER 测试:并发调用数据分析(必须在无事务环境下执行)*/@GetMapping("/never")public ResponseEntity<String> testNever(@RequestParam String data) {int threadCount = 10;ExecutorService executor = Executors.newFixedThreadPool(threadCount);CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {executor.submit(() -> {try {analyticsService.processAnalytics(data);} finally {latch.countDown();}});}try {latch.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in NEVER test");} finally {executor.shutdown();}return ResponseEntity.ok("NEVER propagation test completed");}/*** NESTED 测试:并发处理订单促销(外层 REQUIRED 事务调用嵌套事务)*/@GetMapping("/nested")public ResponseEntity<String> testNested(@RequestParam Long orderId) {int threadCount = 10;ExecutorService executor = Executors.newFixedThreadPool(threadCount);CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {executor.submit(() -> {try {orderPromotionService.processOrderWithPromotion(orderId);} finally {latch.countDown();}});}try {latch.await();} catch (InterruptedException e) {Thread.currentThread().interrupt();return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error in NESTED test");} finally {executor.shutdown();}return ResponseEntity.ok("NESTED propagation test completed");}
}
6. 总结
- 事务创建与存在
Spring 在调用标注了@Transactional
的方法时会自动判断当前线程是否存在事务。如果没有,则新建事务;如果已存在,则根据传播行为决定是否加入现有事务或启动新事务。 - 传播行为选择
- REQUIRED:适用于大多数核心业务流程,保证在同一事务内操作。
- REQUIRES_NEW:用于独立的辅助操作,如记录日志,不受主事务回滚影响。
- SUPPORTS:主要用于查询操作,既可在事务中也可在非事务中执行。
- NOT_SUPPORTED:适用于外部调用,确保不使用事务,以防影响性能。
- MANDATORY:强制要求在已有事务中调用,适合关键数据更新。
- NEVER:确保方法在无事务环境下执行,防止因事务管理引发锁竞争。
- NESTED:允许在外层事务中进行局部回滚,适合部分业务操作需要独立处理异常的场景。
- 高并发环境下的设计
通过线程池模拟高并发场景,每个并发请求在独立线程中调用事务方法,保证每个线程都有独立的事务环境。合理选择事务传播行为,可以降低锁竞争、提高性能,同时确保数据一致性。