当前位置: 代码网 > it编程>编程语言>Java > Java Spring事务管理超详细指南

Java Spring事务管理超详细指南

2026年01月14日 Java 我要评论
一、 引言:为什么我们需要事务?1.1 一个经典的转账场景想象一下,你正在使用手机银行给朋友转账 100 块钱。这个动作在代码层面通常分为两步:你的账户扣款 100 元 (update account

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

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事务管理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com