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 setters
public 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 setters
public 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 setters
public 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