为什么阿里巴巴代码开发规范推荐@Transactional需要指定回滚的异常?

2025-12-12 0 872

Spring 事务 @Transactional 默认回滚与常见陷阱解析

在使用 Spring 框架进行数据库操作时,@Transactional 注解是实现声明式事务管理的核心工具。它极大地简化了事务控制的代码

然而,在实际应用中,一个常见的“坑”在于 @Transactional 注解的默认回滚行为。理解这一点对于避免数据不一致至关重要。

1. @Transactional 的默认回滚规则

Spring 的 @Transactional 注解有一个关键属性 rollbackFor,用于指定哪些异常会触发事务回滚。

  • 默认情况下(即未显式设置 rollbackFor,Spring 只会在遇到 RuntimeException 及其子类(也就是我们常说的运行时异常)时,才会自动回滚事务。
  • 对于 Exception 的另一个主要分支——检查型异常(Checked Exception),即使事务方法抛出了这些异常,默认情况下事务不会自动回滚。

2. Java 异常体系回顾

为了更好地理解上述规则,我们先快速回顾一下 Java 的异常体系:

tongyi-mermaid-2025-11-07-095824.png

  • Throwable: 所有错误和异常的超类。
  • Exception: 应用程序本身可以处理的异常情况。
    • RuntimeException: 运行时异常。这类异常通常由程序逻辑错误引起(如空指针、数组越界),编译器不要求强制处理。它们继承自 Exception
    • 检查型异常 (Checked Exception): 除了 RuntimeException 及其子类之外的所有 Exception 子类。这类异常在编译时就必须被 try-catch 处理或通过 throws 声明抛出,例如 IOException, SQLException 等。

3. 默认回滚行为导致的问题场景

让我们通过一个具体的例子来说明问题:

假设我们有一个转账服务,需要从账户A扣款,并向账户B加款。这个过程必须是原子性的。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException; // 检查型异常示例

@Service
public class TransferService {

    @Autowired
    private AccountRepository accountRepository;

    /**
     * 错误示例:此方法在默认 @Transactional 设置下可能不会按预期回滚
     */
    @Transactional // <--- 使用默认的 rollbackFor = RuntimeException.class
    public void transferMoney(String fromAccount, String toAccount, double amount) throws IOException {
        // 1. 从 fromAccount 扣除金额
        accountRepository.debit(fromAccount, amount);

        // 2. 向 toAccount 增加金额
        accountRepository.credit(toAccount, amount);

        // 3. 假设这里调用了一个外部系统,可能会抛出检查型异常 IOException
        callExternalSystem(); // 此方法声明 throws IOException
    }

    private void callExternalSystem() throws IOException {
        // 模拟一个 IO 操作失败
        throw new IOException(\"Failed to communicate with external system\");
    }
}

// Repository 层示意
interface AccountRepository {
    void debit(String accountNumber, double amount);
    void credit(String accountNumber, double amount);
}

问题分析:

在这个 transferMoney 方法中:

  1. 我们执行了数据库更新操作(扣款和加款)。
  2. 随后,我们调用了一个可能抛出 IOException(检查型异常)的方法 callExternalSystem()
  3. 因为 @Transactional 是默认配置,它只对 RuntimeException 回滚。
  4. IOException 被抛出时:
    • 如果我们在上层 try-catch 了它,或者让它继续向上抛出(因为方法签名声明了 throws IOException),事务并不会回滚
    • 结果是:数据库中的钱已经转走了,但外部系统调用却失败了。这就造成了严重的数据不一致!

4. 解决方案:明确指定 rollbackFor

为了避免这种陷阱,最推荐的做法是:在使用 @Transactional 时,总是显式地指定 rollbackFor 属性,确保事务在遇到所有你不希望提交的情况(包括检查型异常)时都能正确回滚。

修改后的代码如下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;

@Service
public class TransferServiceFixed {

    @Autowired
    private AccountRepository accountRepository;

    /**
     * 推荐做法:明确指定所有需要回滚的异常类型
     */
    @Transactional(rollbackFor = { Exception.class }) // <--- 明确指定,遇到任何 Exception 都回滚
    // 或者更具体一点:@Transactional(rollbackFor = { IOException.class, SQLException.class })
    public void transferMoney(String fromAccount, String toAccount, double amount) throws IOException {
        // 1. 从 fromAccount 扣除金额
        accountRepository.debit(fromAccount, amount);

        // 2. 向 toAccount 增加金额
        accountRepository.credit(toAccount, amount);

        // 3. 调用外部系统
        callExternalSystem();
    }

    private void callExternalSystem() throws IOException {
        // 模拟一个 IO 操作失败
        throw new IOException(\"Failed to communicate with external system\");
    }
}

改进点:

  • 通过 @Transactional(rollbackFor = { Exception.class }),我们告诉 Spring:无论方法抛出何种类型的 Exception(及其子类),都必须回滚事务。
  • 这样,即使 callExternalSystem 抛出了 IOException,事务也会被成功回滚,保证了数据的一致性。
  • 如果只想针对特定的检查型异常回滚,也可以像注释里那样写成 @Transactional(rollbackFor = { IOException.class, SQLException.class })

总结

@Transactional 注解虽然强大便捷,但其默认只对运行时异常 (RuntimeException) 回滚的行为是一个容易忽视的细节。在业务逻辑中涉及到检查型异常 (Checked Exception) 且这些异常的发生意味着事务应该被取消时,务必记得使用 rollbackFor 属性来覆盖默认行为。这是一种良好的实践,能有效防止因异常处理不当而导致的数据不一致问题。


收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 编程相关 为什么阿里巴巴代码开发规范推荐@Transactional需要指定回滚的异常? https://www.zuozi.net/35880.html

常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务