Java Spring事务管理超详细指南

 更新时间:2026年01月14日 10:14:32   作者:无名-CODING  
Spring的事务管理提供了声明式事务管理,支持声明式事务的控制,简化了事务管理的复杂性,下面这篇文章主要介绍了Java Spring事务管理的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

一、 引言:为什么我们需要事务?

1.1 一个经典的转账场景

想象一下,你正在使用手机银行给朋友转账 100 块钱。这个动作在代码层面通常分为两步:

  1. 你的账户扣款 100 元 (UPDATE account SET balance = balance - 100 WHERE id = A)
  2. 朋友的账户增加 100 元 (UPDATE account SET balance = balance + 100 WHERE id = B)

如果发生了意外会怎样?

  • 假如第一步执行成功了,你的钱扣了。
  • 紧接着,程序报错了(比如断网了、数据库崩了、或者代码抛异常了)。
  • 结果:你的钱没了,朋友也没收到钱!😱 这就是严重的数据不一致问题。

1.2 事务的作用

为了解决这个问题,数据库引入了 事务(Transaction) 的概念。
事务就是把一组数据库操作看作一个整体。这个整体内的操作,要么全部成功,要么全部失败(回滚),不允许出现 “做一半” 的情况。

二、 事务的四大特性(ACID)

这是面试必考题!理解这四个词,就理解了事务的灵魂。

特性英文解释通俗理解
原子性Atomicity事务是不可分割的最小单位,要么全做,要么全不做。要么大家都成功,要么大家一起"死"(回滚),不能有幸存者。
一致性Consistency事务执行前后,数据必须保持合规的逻辑状态。转账前 A+B=200,转账后 A+B 还是 200,钱不会凭空消失或变多。
隔离性Isolation多个事务并发执行时,互不干扰。我在操作这条数据时,你别来捣乱(具体看隔离级别)。
持久性Durability事务一旦提交,修改就是永久的,即使系统崩溃也不丢失。落子无悔,写进硬盘了,断电也没事。

三、 Spring 中的事务管理

在 Java 开发中,我们几乎都在使用 Spring 框架来管理事务。Spring 提供了两种方式:

  1. 编程式事务:在代码里手动写 commit(), rollback()(代码侵入性太强,现在很少用了)。
  2. 声明式事务:使用注解 @Transactional,把繁琐的事务逻辑交给 Spring 代理处理(推荐,最常用)。

3.1 快速入门代码示例

下面是一个典型的 Service 层代码,演示了如何使用 @Transactional

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.mapper.AccountMapper;

@Service
public class AccountService {

    @Autowired
    private AccountMapper accountMapper;

    /**
     * 转账方法
     * 
     * @Transactional 注解说明:
     * 1. 这是一个事务方法。
     * 2. Spring 会自动在方法开始前开启事务(Begin)。
     * 3. 如果方法正常执行结束,Spring 自动提交事务(Commit)。
     * 4. 如果方法抛出了 RuntimeException (运行时异常),Spring 自动回滚事务(Rollback)。
     */
    @Transactional(rollbackFor = Exception.class) // 建议:显式指定遇到任何异常都回滚
    public void transfer(Long fromId, Long toId, Double amount) {
        
        // 1. 扣钱 (可能发生异常的地方)
        accountMapper.decreaseBalance(fromId, amount);
        System.out.println("用户 " + fromId + " 扣款成功");

        // 模拟一个意外异常:例如除以零,或者空指针
        // int i = 1 / 0; 
        // 如果上面这行解开注释,整个事务会回滚,第一步扣的钱会加回去。

        // 2. 加钱
        accountMapper.increaseBalance(toId, amount);
        System.out.println("用户 " + toId + " 入账成功");
        
        // 方法结束 -> 提交事务
    }
}

四、 核心难点:事务的传播行为 (Propagation)

“传播行为” 是 Spring 特有的概念,解决的是 Service 方法互相调用 时,事务该怎么算的问题。
比如:方法 A 调用了 方法 B,由于 A 和 B 上都有 @Transactional 注解,那 B 是加入 A 的事务?还是自己新开一个?

Spring 定义了 7 种传播行为,最常用的有下面 3 种:

1. REQUIRED (默认值)

📝 口语解释:“有就加入,没有就新建。”

  • 场景:如果不指定,默认就是这个。
  • 行为
    • 如果 A 已经开启了事务,B 就加入 A 的事务(它俩是同一条船上的蚂蚱,要么一起成功,要么一起回滚)。
    • 如果 A 没有事务,B 就自己开启一个新的事务。

2. REQUIRES_NEW

📝 口语解释:“不管你有没有,我都自己玩。”

  • 场景:记录日志操作。不管业务逻辑成功还是失败,日志都必须记录下来,不能因为业务回滚了日志也就没了。
  • 行为
    • B 方法会挂起 A 的事务,自己开启一个全新的事务
    • B 的成功失败,不影响 A;A 的回滚,也不影响 B(前提是 B 已经提交了)。
    • 代码示例
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOperation() {
        // 这里的操作在一个完全独立的事务中
        logMapper.insertLog("操作发生");
    }
    

3. NESTED (嵌套事务)

📝 口语解释:“你是父,我是子。你挂我也挂,我挂你不一定挂。”

  • 行为
    • 基于数据库的 Savepoint(保存点)技术。
    • B 是 A 的一个子事务。如果 A 回滚,B 一定回滚。
    • 但是如果 B 异常回滚了,A 可以选择捕获异常,继续执行其他逻辑,不一定要回滚。

五、 核心难点:事务的隔离级别 (Isolation)

当很多用户同时操作数据库时(高并发),会产生一些奇怪的现象。通过设置隔离级别来权衡数据的准确性和性能。

5.1 并发可能导致的问题

  1. 脏读 (Dirty Read):读到了别人还没提交的数据。(最严重,绝对不允许)
    • 例子:A 没提交转账,B 读到了钱多了,结果 A 回滚了,B 读到的是假数据。
  2. 不可重复读 (Non-repeatable Read):在一个事务里,两次读取同一行数据,结果不一样(因为中间被别人改了)。
    • 例子:我看库存是 1,准备买,中间被别人买走了,我再看库存变成 0 了。
  3. 幻读 (Phantom Read):在一个事务里,两次查询通过同样的条件,结果条数不一样(因为中间别人插入/删除了数据)。

5.2 SQL 标准的四个隔离级别

级别名称解决的问题性能
READ_UNCOMMITTED读未提交啥也没解决,可能脏读最高(极不安全)
READ_COMMITTED读已提交解决了脏读 (Oracle/SQLServer 默认)较好
REPEATABLE_READ可重复读解决了脏读不可重复读 (MySQL 默认)一般
SERIALIZABLE串行化解决了所有问题 (完全排队执行)最差(像单线程)

💡 Spring 配置方式
@Transactional(isolation = Isolation.REPEATABLE_READ)

六、 常见坑点:为什么我的 @Transactional 失效了?

这是新手最容易遇到的问题,代码写了注解,但异常抛出时数据居然没有回滚!常见原因如下:

  1. 方法不是 public 的:Spring 默认只代理 public 方法。
  2. 同类内部调用
    • 错误示范:方法 A 调 方法 B,A 没有注解,B 有注解。在 Controller 调 A 时,B 的事务不会生效。
    • 原因:Spring 事务是基于 AOP 代理 的,同类内部调用 this.methodB() 是直接调用原始对象的方法,绕过了 Spring 的代理对象,所以事务没开启。
  3. 异常被你自己 catch 吃了
    • 错误示范:
      @Transactional
      public void method() {
          try {
              // 业务代码
          } catch (Exception e) {
              e.printStackTrace(); 
              // ❌ 错误!这里把异常捕获了,Spring 以为代码执行正常,就会提交事务!
          }
      }
      
    • 修正:在 catch 块里 throw new RuntimeException(e) 或者手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
  4. 数据库引擎不支持:例如 MySQL 的 MyISAM 引擎是不支持事务的,必须用 InnoDB。

七、 高频面试题 QA

Q1:Spring 事务默认回滚什么异常?

A:默认只回滚 RuntimeException(运行时异常)和 Error。对于 Exception(受检异常,如 IOException),默认是不回滚的。
追问:怎么让受检异常也回滚?
A:配置 @Transactional(rollbackFor = Exception.class)

Q2:REQUIRED 和 REQUIRES_NEW 的区别?

A

  • REQUIRED:如果当前有事务,就加入;如果没有,就新建。多个方法共用一个物理事务,一起提交或回滚。
  • REQUIRES_NEW:无论当前有没有事务,都挂起当前事务,自己开启一个新事务。两个事务互不影响,是隔离的。

Q3:什么是 Spring 的事务失效?举个例子。

A:最经典的就是自调用问题。在同一个 Service 类中,非事务方法 A 调用事务方法 B,导致 B 的事务失效。因为 Spring AOP 生成代理对象时,只有通过代理对象调用方法才能拦截事务,内部 this 调用直接走了目标对象。

Q4:ACID 是靠什么保证的?(进阶)

A

  • A (原子性):靠 undo log (回滚日志) 保证,失败了可以回滚。
  • C (一致性):是最终目的,靠代码逻辑和 AID 共同保证。
  • I (隔离性):靠 MVCC (多版本并发控制) 和 机制保证。
  • D (持久性):靠 redo log (重做日志) 保证,断电也能恢复。

八、 总结

事务管理是后端开发的"安全带"。虽然 Spring Boot 让我们使用 @Transactional 一个注解就能搞定事务,但作为开发者,我们必须深入理解其背后的传播机制隔离级别,才能在复杂的业务场景下写出健壮的代码。

到此这篇关于Java Spring事务管理的文章就介绍到这了,更多相关Java Spring事务管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java 实现链栈存储的方法

    java 实现链栈存储的方法

    下面小编就为大家带来一篇java 实现链栈存储的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • Java 断言 assert的用法详解

    Java 断言 assert的用法详解

    Java assert断言机制是Java5中推出的新特性,它主要用于在程序运行时检查状态或假设的正确性,本篇文章将全面详细地讲解Java assert断言机制,包括断言概述、语法规则、工作原理、使用场景、注意事项以及示例代码等方面,需要的朋友可以参考下
    2023-05-05
  • Springboot集成Springbrick实现动态插件的步骤详解

    Springboot集成Springbrick实现动态插件的步骤详解

    这篇文章主要介绍了Springboot集成Springbrick实现动态插件的详细过程,文中的流程通过代码示例介绍的非常详细,感兴趣的同学可以参考一下
    2023-06-06
  • java将图片转为base64返回给前端

    java将图片转为base64返回给前端

    这篇文章主要为大家详细介绍了java将图片转为base64返回给前端,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-02-02
  • Java利用Geotools从DEM数据中读取指定位置的高程信息全过程

    Java利用Geotools从DEM数据中读取指定位置的高程信息全过程

    Geotools作为一款功能强大且开源的地理工具库,为地理数据的处理和分析提供了丰富的类库和便捷的接口,能够很好地满足从DEM数据中读取高程信息这一实战需求,本文将深入讲解如何利用Geotools从获取DEM数据到成功读取指定位置高程信息的全过程,需要的朋友可以参考下
    2025-03-03
  • java实现上传和下载工具类

    java实现上传和下载工具类

    这篇文章主要为大家详细介绍了java实现上传和下载工具类,文件上传到ftp服务工具类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • JavaCV实现照片马赛克效果

    JavaCV实现照片马赛克效果

    这篇文章主要介绍了如何通过JavaCV实现照片马赛克效果,文中的示例代码讲解详细,对我们学习JavaCV有一定的帮助,感兴趣的小伙伴可以跟随小编一起动手试一试
    2022-01-01
  • 简单实现Spring的IOC原理详解

    简单实现Spring的IOC原理详解

    这篇文章主要介绍了简单实现Spring的IOC原理详解,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • Java ApiPost请求返回406状态码问题的解决方案

    Java ApiPost请求返回406状态码问题的解决方案

    APIPost是一款专为开发者和测试人员设计的API测试工具,类似于Postman,但提供了更多的团队协作和文档管理功能,它可以帮助你更好地进行接口调试和集成测试,但遇到了请求后返回的是406状态,所以本文给大家介绍了Java ApiPost请求返回406状态码问题的解决方案
    2025-04-04
  • java学生管理系统界面简单实现(全)

    java学生管理系统界面简单实现(全)

    这篇文章主要为大家详细介绍了java学生管理系统界面的简单实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-01-01

最新评论