Spring实现方式、隔离级别与传播机制全攻略

 更新时间:2026年04月01日 09:08:54   作者:Remember_993  
本文介绍了Spring事务的基础概念、实现方式、核心注解配置、隔离级别和传播机制,并通过实际场景详细解析了事务传播机制的7种行为,感兴趣的朋友跟随小编一起看看吧

在后端开发中,数据一致性是核心诉求之一。无论是转账时的金额流转,还是秒杀场景的库存扣减,稍有不慎就会导致数据错乱 —— 比如 A 账户扣款成功但 B 账户未到账,下单成功但库存未减少。而事务,正是解决这类问题的关键技术。Spring 框架对事务进行了高度封装,提供了灵活易用的事务管理能力。本文将从事务基础出发,深入拆解 Spring 事务的实现方式、核心注解配置、隔离级别,并重点剖析事务传播机制的 7 种行为与实际应用场景,帮你彻底掌握 Spring 事务的核心用法。

一、事务基础回顾:是什么?为什么需要?

在学习 Spring 事务之前,我们先回顾数据库事务的核心概念,这是理解 Spring 事务的基础。

1.1 事务的定义

事务是一组不可分割的数据库操作集合,这组操作要么全部成功执行并提交,要么全部失败并回滚,不存在 “部分成功” 的中间状态。就像快递发货,下单、扣库存、生成物流单这一系列操作,必须同时完成才算交易成功,任何一步失败都要回到初始状态。

1.2 为什么需要事务?

事务的核心价值是保证数据一致性,我们通过两个经典场景理解:

  • 转账场景:A 账户转出 100 元,B 账户转入 100 元。如果没有事务,A 账户扣款成功后,B 账户转入操作失败,会导致 100 元 “凭空消失”;
  • 秒杀场景:用户下单成功后,需要扣减对应商品库存。如果下单成功但库存扣减失败,会导致超卖(实际库存为 0 但仍有订单生成)。

事务通过 “原子性” 特性,确保这一系列操作要么全成,要么全败,从根本上避免数据不一致。

1.3 事务的核心操作

数据库层面,事务的操作有三步,Spring 事务本质也是对这三步的封装:

  1. 开启事务:start transaction / begin(操作执行前开启);
  2. 提交事务:commit(所有操作无异常时提交,数据永久生效);
  3. 回滚事务:rollback(任意操作异常时回滚,恢复到操作前状态)。

二、Spring 事务的两种实现方式

Spring 支持两种事务管理方式:编程式事务(手动控制)和声明式事务(注解自动控制)。实际开发中,声明式事务因简洁高效成为主流,编程式事务仅用于特殊场景。

2.1 编程式事务:手动控制事务生命周期

编程式事务需要开发者手动编写代码开启、提交、回滚事务,灵活性高但代码繁琐。

核心依赖与组件

SpringBoot 内置了DataSourceTransactionManager(事务管理器),无需额外引入依赖,核心组件:

  • DataSourceTransactionManager:负责事务的开启、提交、回滚;
  • TransactionDefinition:定义事务属性(如隔离级别、传播机制);
  • TransactionStatus:事务的当前状态(如是否活跃、是否需要回滚)。

代码实现(以用户注册为例)

  1. 准备工作:创建数据库表(用户表user_info、日志表log_info)、实体类、Mapper 接口(文档中已提供,此处省略);
  2. Controller 层手动控制事务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
    // 注入事务管理器
    @Autowired
    private DataSourceTransactionManager transactionManager;
    // 注入事务属性定义
    @Autowired
    private TransactionDefinition transactionDefinition;
    // 注入业务层
    @Autowired
    private UserService userService;
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        // 1. 开启事务
        TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
        try {
            // 2. 执行核心业务(用户注册)
            userService.registryUser(name, password);
            // 3. 无异常,提交事务
            transactionManager.commit(status);
            return "注册成功";
        } catch (Exception e) {
            // 4. 有异常,回滚事务
            transactionManager.rollback(status);
            return "注册失败";
        }
    }
}

优缺点

  • 优点:完全手动控制事务边界,灵活处理复杂场景;
  • 缺点:代码冗余,事务逻辑与业务逻辑耦合,不利于维护。

2.2 声明式事务:@Transactional 注解一键搞定

声明式事务基于 AOP 实现,通过@Transactional注解自动完成事务的开启、提交、回滚,无需编写额外事务代码,是 Spring 事务的推荐用法。

实现步骤

  1. 引入依赖(SpringBoot 项目已内置spring-tx,无需手动引入):
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>
  1. 在需要事务的方法 / 类上添加@Transactional注解:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/trans")
public class TransactionalController {
    @Autowired
    private UserService userService;
    // 添加入注解,该方法自动支持事务
    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        // 执行核心业务
        userService.registryUser(name, password);
        // 模拟异常(测试回滚)
        int a = 10 / 0;
        return "注册成功";
    }
}

核心原理

  • 当方法被@Transactional修饰时,Spring 会通过 AOP 动态生成代理对象;
  • 方法执行前,代理对象自动开启事务;
  • 方法执行无异常时,自动提交事务;
  • 方法抛出未捕获的异常时,自动回滚事务。

注意事项

  • @Transactional仅对public方法生效(修饰非 public 方法时不报错但无事务效果);
  • 若异常被try-catch捕获且未重新抛出,事务不会回滚(Spring 无法感知异常);
  • 建议在业务层(Service) 使用该注解(业务层通常包含多个数据操作,便于控制事务边界)。

异常捕获后的回滚方案

如果需要捕获异常且让事务回滚,有两种方式:

  1. 重新抛出异常:
@Transactional
public String registry(String name, String password) {
    try {
        userService.registryUser(name, password);
        int a = 10 / 0;
    } catch (Exception e) {
        e.printStackTrace();
        // 重新抛出异常,触发回滚
        throw e;
    }
    return "注册成功";
}
  1. 手动触发回滚:
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Transactional
public String registry(String name, String password) {
    try {
        userService.registryUser(name, password);
        int a = 10 / 0;
    } catch (Exception e) {
        e.printStackTrace();
        // 手动回滚事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return "注册成功";
}

三、@Transactional 注解详解:三大核心属性

@Transactional注解提供了多个属性,用于灵活配置事务行为,核心属性有 3 个:rollbackFor(异常回滚规则)、isolation(隔离级别)、propagation(传播机制)。

3.1 rollbackFor:指定回滚的异常类型

默认行为

Spring 事务默认仅对运行时异常(RuntimeException)和 Error 回滚,对非运行时异常(如IOExceptionSQLException)不回滚。

示例验证:

@Transactional
@RequestMapping("/r2")
public String r2(String name, String password) throws IOException {
    userService.registryUser(name, password);
    // 抛出非运行时异常(IOException)
    throw new IOException();
}

运行结果:事务未回滚,用户数据成功插入数据库。

配置 rollbackFor

若需要对所有异常都回滚,或指定特定异常回滚,通过rollbackFor配置:

// 对所有Exception子类都回滚
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/r2")
public String r2(String name, String password) throws IOException {
    userService.registryUser(name, password);
    throw new IOException();
}

运行结果:事务回滚,用户数据未插入。

扩展用法

  • 指定多个异常类型:@Transactional(rollbackFor = {IOException.class, SQLException.class})
  • 反向配置(不回滚的异常):noRollbackFor = XXXException.class(慎用,可能导致数据不一致)。

3.2 isolation:事务隔离级别

事务隔离级别解决的是 “多个事务同时操作同一批数据时的并发问题”,主要有三类并发问题:

  • 脏读:一个事务读取到另一个事务未提交的数据(可能回滚,导致读取的数据无效);
  • 不可重复读:同一事务内多次查询同一数据,结果不一致(其他事务修改并提交了该数据);
  • 幻读:同一事务内多次执行同一查询,返回的结果集行数不一致(其他事务新增 / 删除了数据)。

3.2.1 MySQL 的四种隔离级别(SQL 标准)

MySQL 支持 SQL 标准定义的四种隔离级别,默认隔离级别为可重复读(REPEATABLE READ)

隔离级别脏读不可重复读幻读说明
读未提交(READ UNCOMMITTED)最低级别,允许读取未提交数据,性能最高但一致性最差
读已提交(READ COMMITTED)只能读取已提交数据,避免脏读,Oracle 默认级别
可重复读(REPEATABLE READ)同一事务内多次查询结果一致,避免脏读和不可重复读,MySQL 默认级别
串行化(SERIALIZABLE)最高级别,事务串行执行,完全避免并发问题,但性能最差

3.2.2 Spring 的五种隔离级别

Spring 在 MySQL 隔离级别的基础上,增加了DEFAULT(默认值),即沿用数据库的隔离级别:

  1. Isolation.DEFAULT:默认值,使用数据库的隔离级别(MySQL 为 REPEATABLE READ);
  2. Isolation.READ_UNCOMMITTED:对应 MySQL 的读未提交;
  3. Isolation.READ_COMMITTED:对应 MySQL 的读已提交;
  4. Isolation.REPEATABLE_READ:对应 MySQL 的可重复读;
  5. Isolation.SERIALIZABLE:对应 MySQL 的串行化。

配置方式

// 设置隔离级别为读已提交
@Transactional(isolation = Isolation.READ_COMMITTED)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
}

选择建议

  • 绝大多数场景:使用默认隔离级别(REPEATABLE READ),兼顾一致性和性能;
  • 高一致性要求(如金融场景):使用SERIALIZABLE,但需注意性能损耗;
  • 低一致性要求(如日志统计):可使用READ_COMMITTED,提升并发性能。

3.3 propagation:事务传播机制(核心重点)

多个被@Transactional修饰的方法相互调用时,事务如何在方法间传递,这就是传播机制。比如:方法 A(有事务)调用方法 B(有事务),B 是加入 A 的事务,还是新建独立事务?

传播机制的 7 种行为

Spring 定义了 7 种传播行为,通过@Transactional(propagation = 传播行为)配置,核心常用的是前 3 种:

传播行为中文说明核心逻辑通俗比喻(结婚买房)
REQUIRED(默认)必须有事务若当前存在事务,加入该事务;若不存在,新建事务结婚必须有房:你有房就一起住,没房就一起买
REQUIRES_NEW新建独立事务无论当前是否有事务,都新建独立事务,挂起当前事务必须买新房:不管你有没有房,都要重新买一套,各自独立
NESTED嵌套事务若当前有事务,创建嵌套事务(依赖保存点);若不存在,新建事务以房为基础:你有房就用你的房,在房里搞 “小项目”;没房就一起买
SUPPORTS支持事务若当前有事务,加入;若没有,以非事务方式运行可有可无:你有房就一起住,没房就租房
MANDATORY强制事务若当前有事务,加入;若没有,抛出异常必须有房才结婚:没房就不结
NOT_SUPPORTED不支持事务以非事务方式运行,若当前有事务,挂起事务不需要房:不管你有没有房,我都租房住
NEVER禁止事务以非事务方式运行,若当前有事务,抛出异常不能有房:你有房就不结婚

四、事务传播机制场景演示:实战理解核心行为

我们通过 “用户注册 + 记录操作日志” 的场景,演示 3 种核心传播行为的差异(用户注册和记录日志都是带事务的方法)。

准备工作

  • 业务逻辑:用户注册(UserService.registryUser)后,记录操作日志(LogService.insertLog);
  • 模拟异常:在日志记录方法中抛出10/0的运行时异常。

4.1 REQUIRED(默认):加入当前事务

代码配置

// UserService
@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public void registryUser(String name, String password) {
        userInfoMapper.insert(name, password); // 插入用户
    }
}
// LogService
@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertLog(String name, String op) {
        int a = 10 / 0; // 模拟异常
        logInfoMapper.insertLog(name, op); // 插入日志
    }
}
// Controller
@RestController
@RequestMapping("/propaga")
public class PropagationController {
    @Autowired
    private UserService userService;
    @Autowired
    private LogService logService;
    @Transactional(propagation = Propagation.REQUIRED)
    @RequestMapping("/p1")
    public String p1(String name, String password) {
        userService.registryUser(name, password); // 调用用户注册
        logService.insertLog(name, "用户注册"); // 调用日志记录
        return "操作成功";
    }
}

执行结果

数据库中无用户数据和日志数据插入

流程分析

  1. Controller 的p1方法开启事务;
  2. userService.registryUser加入p1的事务,插入用户数据成功;
  3. logService.insertLog加入p1的事务,抛出异常;
  4. 由于所有操作在同一个事务中,异常触发整体回滚,用户数据和日志数据均回滚。

4.2 REQUIRES_NEW:新建独立事务

代码配置

将两个 Service 方法的传播机制改为REQUIRES_NEW

// UserService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
}
// LogService
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertLog(String name, String op) {
    int a = 10 / 0;
    logInfoMapper.insertLog(name, op);
}

执行结果

数据库中用户数据插入成功,日志数据未插入

流程分析

  1. Controller 的p1方法开启事务;
  2. userService.registryUser新建独立事务,插入用户数据后提交事务(不受后续异常影响);
  3. logService.insertLog新建独立事务,抛出异常,该事务回滚(日志数据未插入);
  4. 两个事务相互独立,日志事务的异常不影响用户事务。

4.3 NESTED:嵌套事务(支持局部回滚)

代码配置

将两个 Service 方法的传播机制改为NESTED

// UserService
@Transactional(propagation = Propagation.NESTED)
public void registryUser(String name, String password) {
    userInfoMapper.insert(name, password);
}
// LogService
@Transactional(propagation = Propagation.NESTED)
public void insertLog(String name, String op) {
    int a = 10 / 0;
    logInfoMapper.insertLog(name, op);
}

执行结果(未捕获异常)

数据库中无用户数据和日志数据插入

流程分析

  1. Controller 的p1方法开启事务(父事务);
  2. userService.registryUser创建嵌套事务(子事务 1),插入用户数据;
  3. logService.insertLog创建嵌套事务(子事务 2),抛出异常,子事务 2 回滚;
  4. 由于子事务 2 未捕获异常,异常向上传播,父事务回滚,子事务 1 也随之回滚。

局部回滚场景(捕获异常)

修改LogService,捕获异常并手动回滚当前嵌套事务:

@Service
public class LogService {
    @Autowired
    private LogInfoMapper logInfoMapper;
    @Transactional(propagation = Propagation.NESTED)
    public void insertLog(String name, String op) {
        try {
            int a = 10 / 0;
            logInfoMapper.insertLog(name, op);
        } catch (Exception e) {
            e.printStackTrace();
            // 手动回滚当前嵌套事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }
}

执行结果

数据库中用户数据插入成功,日志数据未插入

核心原理:保存点(Savepoint)

嵌套事务的局部回滚依赖数据库的保存点(Savepoint) 机制:

  • 父事务开启后,每个嵌套事务执行前会创建一个保存点;
  • 嵌套事务回滚时,仅回滚到当前保存点,不影响父事务和其他嵌套事务的已执行操作;
  • 父事务回滚时,会回滚所有嵌套事务(包括已提交的嵌套事务)。

4.4 NESTED vs REQUIRED:关键区别

对比维度REQUIREDNESTED
事务关系同一事务父 - 子嵌套事务(保存点隔离)
回滚范围要么全回滚,要么全提交支持局部回滚(子事务回滚不影响父事务)
异常传播子事务异常直接导致整体回滚子事务异常可捕获,仅回滚当前子事务
适用场景多个操作必须同时成功 / 失败(如转账)多个操作可独立回滚(如注册 + 送积分,积分失败不影响注册)

五、Spring 事务常见问题与避坑指南

5.1 @Transactional 注解不生效的场景

  1. 修饰非 public 方法(如privateprotected);
  2. 异常被try-catch捕获且未重新抛出;
  3. 数据源未配置事务管理器(SpringBoot 自动配置,手动配置时需注意);
  4. 同一个类中无事务方法调用有事务方法(AOP 无法拦截内部调用);
    @Service
    public class UserService {
        // 无事务方法
        public void test() {
            registryUser("admin", "123456"); // 内部调用,@Transactional不生效
        }
        @Transactional
        public void registryUser(String name, String password) {
            userInfoMapper.insert(name, password);
        }
    }
  5. 事务管理器配置错误(如多数据源时未指定对应事务管理器)。

5.2 性能优化建议

  1. 避免大事务:事务范围越小越好,不要在事务中执行非数据库操作(如调用第三方接口、文件 IO);
  2. 合理选择隔离级别:非核心场景避免使用SERIALIZABLE,减少锁竞争;
  3. 传播机制按需选择:无需独立事务时用REQUIRED,需独立事务时用REQUIRES_NEW,避免过度使用REQUIRES_NEW导致事务过多;
  4. 避免长事务:长事务会占用数据库连接,导致连接池耗尽,影响系统并发能力。

六、总结

Spring 事务是保证数据一致性的核心技术,本文从基础到实战,全面解析了 Spring 事务的核心知识点:

  1. 事务的本质是 “原子性操作集合”,解决数据一致性问题;
  2. Spring 事务有两种实现方式:编程式(灵活但繁琐)和声明式(@Transactional注解,推荐);
  3. @Transactional的三大核心属性:rollbackFor(控制回滚异常)、isolation(控制并发问题)、propagation(控制事务传播);
  4. 事务传播机制是重点,REQUIRED(默认)、REQUIRES_NEW(独立事务)、NESTED(局部回滚)是高频使用场景;
  5. 嵌套事务通过保存点机制实现局部回滚,适用于 “部分操作可独立失败” 的场景。

实际开发中,建议优先使用声明式事务,根据业务场景灵活配置隔离级别和传播机制:

  • 转账、支付等核心场景:用REQUIRED隔离级别,确保操作原子性;
  • 注册 + 日志、下单 + 库存等场景:用NESTEDREQUIRES_NEW,实现部分操作独立回滚;
  • 非核心查询场景:用READ_COMMITTED隔离级别,提升并发性能。

到此这篇关于Spring实现方式、隔离级别与传播机制全攻略的文章就介绍到这了,更多相关spring隔离级别与传播机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java高效实现Word转PDF的完整指南

    Java高效实现Word转PDF的完整指南

    这篇文章主要为大家详细介绍了如何用 Spire.Doc for Java 库实现Word到PDF文档的快速转换,并解析其转换选项的灵活配置技巧,希望对大家有所帮助
    2025-08-08
  • JDBC下Idea添加mysql-jar包的详细过程

    JDBC下Idea添加mysql-jar包的详细过程

    这篇文章主要介绍了JDBC下Idea添加mysql-jar包的详细过程,添加jar包首先到官网下载jar包,然后idea导入jar包,在就是检查,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-11-11
  • java使用java.io.File类和java.nio.file.Path类对文件重命名

    java使用java.io.File类和java.nio.file.Path类对文件重命名

    这篇文章主要给大家介绍了关于java使用java.io.File类和java.nio.file.Path类对文件重命名的相关资料,本文仅为日常操作记录,方便后期使用查找本地电脑文件太多了,又不想一个一个重命名,改名字什么的很麻烦,需要的朋友可以参考下
    2024-02-02
  • Maven插件构建Docker镜像的实现步骤

    Maven插件构建Docker镜像的实现步骤

    这篇文章主要介绍了Maven插件构建Docker镜像的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • java单例模式实现的方法

    java单例模式实现的方法

    这篇文章主要介绍了如何在JAVA中实现单例模式,文中代码简单易懂,供大家参考学习,感兴趣的小伙伴可以了解下
    2020-06-06
  • Java基础MAC系统下IDEA连接MYSQL数据库JDBC过程

    Java基础MAC系统下IDEA连接MYSQL数据库JDBC过程

    最近一直在学习web项目,当然也会涉及与数据库的连接这块,这里就总结一下在IDEA中如何进行MySQL数据库的连接,这里提一下我的电脑是MAC系统,使用的编码软件是IDEA,数据库是MySQL
    2021-09-09
  • java中BIO、NIO、AIO都有啥区别

    java中BIO、NIO、AIO都有啥区别

    这篇文章主要介绍了java中BIO、NIO、AIO都有啥区别,IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:BIO,NIO,AIO,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下
    2021-04-04
  • 关于SpringSecurity Context 中获取和更改当前用户信息的问题

    关于SpringSecurity Context 中获取和更改当前用户信息的问题

    SpringSecurityContext在异步线程中无法获取用户信息,因其与请求线程绑定;此外,用户信息更新后跳转页面时,身份会被降级为匿名,导致信息无法及时同步,本文给大家介绍SpringSecurity Context 中获取和更改当前用户信息的问题,感兴趣的朋友一起看看吧
    2024-09-09
  • Java数据结构之链表相关知识总结

    Java数据结构之链表相关知识总结

    今天给大家带来关于Java数据结构的相关知识,文章围绕Java链表展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • 浅谈JAVA 异常对于性能的影响

    浅谈JAVA 异常对于性能的影响

    Java的异常处理为什么会影响性能?异常开销很大。那么,这是不是就意味着您不该使用异常?当然不是。但是,何时应该使用异常,何时又不应该使用异常呢?不幸的是,答案不是一下子就说得清楚的,我们来详细探讨下。
    2015-05-05

最新评论