首页 > Java > Java面试题 > 正文

spring 事务实现方式有哪些?

幻夢星雲
发布: 2025-09-08 08:00:06
原创
350人浏览过
Spring事务主要分为编程式和声明式两大类,前者通过PlatformTransactionManager或TransactionTemplate在代码中手动控制事务,后者通过@Transactional注解结合AOP实现事务管理,具有低侵入性和高可维护性,是现代Spring应用的首选方式。

spring 事务实现方式有哪些?

Spring事务的实现方式,说白了,主要就两大类:一种是编程式事务,另一种是声明式事务。在我个人的经验里,声明式事务因为其便捷性和低侵入性,几乎成了现代Spring应用的首选,但了解编程式事务的底层逻辑也绝不是坏事。

解决方案

Spring框架在事务管理上提供了强大的支持,核心在于抽象了底层事务API(如JDBC、JTA、JPA等),提供了一致的编程模型。

1. 编程式事务管理 这种方式需要你在代码中显式地调用事务API来管理事务的开始、提交和回滚。它提供了最细粒度的控制,但缺点是代码侵入性强,容易产生大量重复代码。

  • 使用

    PlatformTransactionManager
    登录后复制
    这是Spring事务抽象的核心接口。你需要注入一个
    PlatformTransactionManager
    登录后复制
    实例,然后手动创建
    TransactionStatus
    登录后复制
    对象,并在try-catch-finally块中进行事务的提交或回滚。

    @Service
    public class MyService {
    
        private final PlatformTransactionManager transactionManager;
    
        public MyService(PlatformTransactionManager transactionManager) {
            this.transactionManager = transactionManager;
        }
    
        public void doSomethingTransactional() {
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            TransactionStatus status = transactionManager.getTransaction(def);
            try {
                // 业务逻辑操作
                // 例如:dao.insertDataA(); dao.updateDataB();
                transactionManager.commit(status);
            } catch (Exception e) {
                transactionManager.rollback(status);
                throw new RuntimeException("Transaction failed", e);
            }
        }
    }
    登录后复制
  • 使用

    TransactionTemplate
    登录后复制
    这是Spring提供的一个模板类,它封装了事务的创建、提交、回滚等boilerplate代码,让你的业务逻辑更聚焦。它在内部还是使用了
    PlatformTransactionManager
    登录后复制

    @Service
    public class MyService {
    
        private final TransactionTemplate transactionTemplate;
    
        public MyService(PlatformTransactionManager transactionManager) {
            this.transactionTemplate = new TransactionTemplate(transactionManager);
        }
    
        public void doSomethingTransactionalWithTemplate() {
            transactionTemplate.execute(status -> {
                try {
                    // 业务逻辑操作
                    // 例如:dao.insertDataC(); dao.deleteDataD();
                    return "Success";
                } catch (Exception e) {
                    status.setRollbackOnly(); // 标记事务为回滚
                    throw new RuntimeException("Transaction failed", e);
                }
            });
        }
    }
    登录后复制

2. 声明式事务管理 这是Spring推荐的方式,通过AOP(面向切面编程)实现。你不需要在业务代码中显式编写事务管理逻辑,只需通过配置(XML或注解)来声明哪些方法需要事务支持。Spring会在运行时通过代理为这些方法织入事务管理功能。

  • 基于XML配置: 早期项目或一些特定场景下可能会用到。通过

    <tx:advice>
    登录后复制
    定义事务通知,然后用
    <aop:config>
    登录后复制
    将其织入到目标方法。这种方式配置相对繁琐,可读性也不如注解直观。

  • 基于

    @Transactional
    登录后复制
    注解: 这是目前最主流、最推荐的方式。你只需要在类或方法上添加
    @Transactional
    登录后复制
    注解,Spring就会自动为其创建事务代理。

    当你在方法上加上

    @Transactional
    登录后复制
    注解时,Spring会创建一个代理对象,在方法执行前开启事务,方法执行成功后提交事务,如果抛出运行时异常(
    RuntimeException
    登录后复制
    Error
    登录后复制
    )则回滚事务。你也可以通过注解的属性来控制事务的行为:

    • propagation
      登录后复制
      :事务的传播行为(如
      REQUIRED
      登录后复制
      ,
      REQUIRES_NEW
      登录后复制
      等)。
    • isolation
      登录后复制
      :事务的隔离级别(如
      READ_COMMITTED
      登录后复制
      ,
      REPEATABLE_READ
      登录后复制
      等)。
    • timeout
      登录后复制
      :事务的超时时间。
    • readOnly
      登录后复制
      :是否为只读事务,可以优化性能。
    • rollbackFor
      登录后复制
      :指定哪些异常类型需要回滚。
    • noRollbackFor
      登录后复制
      :指定哪些异常类型不需要回滚。
    @Service
    public class UserService {
    
        // 假设有用户数据访问对象
        // private final UserRepository userRepository;
    
        @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
        public void createUserAndAssignRole(User user, Role role) {
            // userRepository.save(user);
            // roleRepository.assign(user.getId(), role.getId());
            // 如果这里有任何异常,整个方法的操作都会回滚
        }
    
        @Transactional(readOnly = true)
        public User getUserById(Long id) {
            // return userRepository.findById(id);
            return null;
        }
    }
    登录后复制

    在我看来,

    @Transactional
    登录后复制
    注解的简洁性简直是开发者的福音,它让业务代码保持纯粹,极大地提升了开发效率和代码可维护性。

Spring事务的传播行为有哪些,如何理解?

事务传播行为,这玩意儿听起来有点玄乎,但其实就是当一个方法调用另一个方法时,这两个方法的事务如何相互作用的规则。理解这些规则,对于避免一些难以察觉的事务问题至关重要。Spring定义了七种传播行为,但日常开发中常用的也就那么几种。

  • REQUIRED
    登录后复制
    (默认也是最常用):如果当前存在事务,就加入该事务;如果当前没有事务,就创建一个新的事务。

    • 理解: 想象你正在进行一项任务(事务A),这时你需要调用一个子任务(方法B)。如果子任务B也要求在事务中运行,那么它会“搭上”你当前任务A的事务这趟车。如果任务A没有事务,子任务B会自己开一辆新车。这是最安全、最常用的选择,确保方法总是在事务环境中运行。
  • SUPPORTS
    登录后复制
    :如果当前存在事务,就加入该事务;如果当前没有事务,就以非事务方式执行。

    • 理解: 子任务B对事务持“支持”态度。有事务就跟着走,没事务也不强求,自己干自己的活。适合那些可有可无事务支持的操作,比如查询。
  • MANDATORY
    登录后复制
    :如果当前存在事务,就加入该事务;如果当前没有事务,就抛出异常。

    • 理解: 子任务B是“强制”要求有事务的。如果没有事务,它就直接罢工(抛异常)。这适用于那些必须在事务环境下才能正确执行的关键操作。
  • REQUIRES_NEW
    登录后复制
    :总是创建一个新的事务,如果当前存在事务,就将当前事务挂起。

    • 理解: 子任务B是个“独立”的个体,它不关心你有没有事务,它每次都会开一辆新车,并且把你原来的车(事务)暂时停在一边。子任务B自己的事务提交或回滚,不会影响到它外部的事务。这在需要确保某个操作独立提交或回滚时非常有用,比如日志记录。
  • NOT_SUPPORTED
    登录后复制
    :以非事务方式执行操作,如果当前存在事务,就将当前事务挂起。

    • 理解: 子任务B“不支持”事务。如果它发现你带着事务来了,它会让你把事务先放一边,自己以非事务方式执行。适合那些不需要事务,甚至会干扰事务的操作。
  • NEVER
    登录后复制
    :以非事务方式执行操作,如果当前存在事务,就抛出异常。

    • 理解: 子任务B“绝不”允许有事务。如果你带着事务来了,它就直接报错。这比
      NOT_SUPPORTED
      登录后复制
      更严格。
  • NESTED
    登录后复制
    :如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则创建一个新的事务。

    火山方舟
    火山方舟

    火山引擎一站式大模型服务平台,已接入满血版DeepSeek

    火山方舟99
    查看详情 火山方舟
    • 理解: 嵌套事务,它有点像
      REQUIRED
      登录后复制
      ,但又不同。它会在父事务中创建一个“保存点”(savepoint)。如果嵌套事务回滚,它只会回滚到这个保存点,而不会影响到父事务的提交或回滚。但如果父事务回滚,它会连同嵌套事务一起回滚。这需要底层数据库支持保存点。我个人觉得,这个用得相对较少,而且理解起来也容易混淆,实际项目中我倾向于用
      REQUIRES_NEW
      登录后复制
      来明确隔离。

为什么我的Spring事务不生效?常见陷阱与排查。

说实话,

@Transactional
登录后复制
注解虽然好用,但有时它就是不生效,这真是让人抓狂。遇到这种情况,别急着骂Spring,多半是有些细节没注意到。这里我总结几个常见的“坑”和排查思路:

  1. 方法不是

    public
    登录后复制
    的: 这是最常见的。Spring的AOP代理(无论是JDK动态代理还是CGLIB代理)默认只对
    public
    登录后复制
    方法生效。如果你把
    @Transactional
    登录后复制
    放在
    private
    登录后复制
    protected
    登录后复制
    或默认(包可见)方法上,它是不会生效的。

    • 排查: 检查你的事务方法是不是
      public
      登录后复制
      的。
  2. 同一个类中方法A调用方法B(自调用问题): 这是一个非常经典的陷阱。当你在同一个Service类中,一个没有

    @Transactional
    登录后复制
    注解的方法A调用了另一个有
    @Transactional
    登录后复制
    注解的方法B时,事务可能不会生效。

    • 原因: Spring的事务是通过代理实现的。当你通过

      this
      登录后复制
      关键字调用同一个类中的方法时,实际上是绕过了Spring生成的代理对象,直接调用了目标对象的方法。这样一来,代理在方法B上织入的事务逻辑就无法生效了。

    • 排查:

      @Service
      public class MyService {
      
          @Transactional // 这个事务不会生效
          public void methodB() {
              System.out.println("Method B executed.");
              // ... 业务逻辑 ...
          }
      
          public void methodA() {
              System.out.println("Method A executed.");
              this.methodB(); // 直接通过this调用,绕过了代理
          }
      }
      登录后复制
    • 解决方案:

      • methodB
        登录后复制
        移到一个独立的Service类中。
      • 通过Spring上下文获取当前Service的代理对象来调用(不推荐,代码丑陋)。
      • 使用
        AopContext.currentProxy()
        登录后复制
        获取当前代理对象,然后调用
        ((MyService) AopContext.currentProxy()).methodB();
        登录后复制
        (需要开启
        exposeProxy = true
        登录后复制
        )。
      • 我个人更倾向于第一种,职责分离也更清晰。
  3. 异常类型不对,或者异常被捕获了: 默认情况下,Spring事务只对运行时异常(

    RuntimeException
    登录后复制
    及其子类)和
    Error
    登录后复制
    进行回滚。如果你抛出的是受检异常(Checked Exception,如
    IOException
    登录后复制
    SQLException
    登录后复制
    等),而又没有明确配置
    @Transactional(rollbackFor = MyCheckedException.class)
    登录后复制
    ,事务是不会回滚的。

    • 排查: 检查你的业务逻辑中抛出的异常类型。如果捕获了异常,确保在捕获后重新抛出运行时异常,或者手动调用
      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
      登录后复制
      来标记回滚。
  4. 数据库不支持事务: 某些数据库存储引擎(比如MySQL的MyISAM)是不支持事务的。虽然Spring的事务配置没问题,但底层数据库不支持,自然也就无法实现事务。

    • 排查: 检查你的数据库表使用的存储引擎(例如MySQL的InnoDB是支持事务的)。
  5. 没有启用Spring的事务管理: 忘记在Spring配置中启用事务管理,比如没有

    @EnableTransactionManagement
    登录后复制
    注解(在Spring Boot中通常会自动配置),或者XML配置中没有
    <tx:annotation-driven/>
    登录后复制

    • 排查: 检查你的Spring配置类或XML文件。
  6. 事务方法内部调用了非事务方法,且非事务方法抛出了异常: 如果一个事务方法内部调用了一个没有

    @Transactional
    登录后复制
    注解的方法,而这个非事务方法抛出了异常,并且这个异常没有被事务方法捕获,那么事务会回滚。但如果异常被事务方法捕获了,且没有再次抛出运行时异常或标记回滚,事务就不会回滚。

    • 排查: 仔细审查异常的传播路径和捕获逻辑。

编程式事务与声明式事务,我该如何选择?

这其实是一个经典的取舍问题,但现代应用开发中,答案往往偏向一边。

在我看来,绝大多数情况下,你应该选择声明式事务,特别是基于

@Transactional
登录后复制
注解的方式。 理由很简单:

  • 代码整洁度: 声明式事务将事务管理逻辑与业务逻辑彻底分离。你的业务代码只关心业务本身,不需要掺杂任何事务相关的API调用。这让代码看起来更干净、更易读、更专注于核心业务。
  • 开发效率: 只需要一个注解,Spring就会为你处理所有事务的开启、提交、回滚,大大减少了重复代码的编写。这简直是生产力工具
  • 维护性: 当事务策略需要调整时(比如改变传播行为、隔离级别),你只需要修改注解的属性,而不需要深入到业务代码中去修改。这降低了维护成本和出错的可能性。

那么,什么时候会考虑编程式事务呢? 说实话,这种情况非常少见,但也不是没有。

  • 极度细粒度的控制: 比如你需要在同一个方法内部,对不同的操作块应用不同的事务策略,甚至在某些特定条件下手动控制提交或回滚,而这些条件又非常复杂,无法通过声明式事务的属性来表达。但即使是这种场景,我也会先思考能否通过拆分方法或调整架构来用声明式实现。
  • 非Spring环境的事务集成: 比如你正在一个混合项目中,需要与一些非Spring管理的事务系统进行交互,或者你需要完全脱离Spring的事务抽象,直接操作底层的JDBC
    Connection
    登录后复制
    事务。
  • 遗留系统改造: 某些老旧系统可能已经有了大量的编程式事务代码,在不进行大规模重构的情况下,继续沿用编程式可能是成本最低的选择。

总的来说,如果你正在开发一个新的Spring应用,或者对现有应用进行现代化改造,请毫不犹豫地拥抱

@Transactional
登录后复制
。它不仅能让你的代码更优雅,也能让你从繁琐的事务管理细节中解脱出来,把精力放在真正有价值的业务逻辑上。编程式事务更像是一个“备用方案”,在极少数特殊场景下才需要考虑。

以上就是spring 事务实现方式有哪些?的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号