Spring 事务(Transactional)失效的七种原因及解决方案(含项目代码)

这篇具有很好参考价值的文章主要介绍了Spring 事务(Transactional)失效的七种原因及解决方案(含项目代码)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Spring 事务(Transactional)失效的七种原因及解决方案(含项目代码)

简介

“Spring框架提供了强大的事务管理功能,能够确保数据库操作的一致性和可靠性。然而,有时候我们可能会遇到Spring事务失效的情况,导致数据不一致或操作失败。本文将探讨Spring事务失效的原因,以及如何避免和解决这些问题。通过深入了解失效原因,我们可以更好地利用Spring事务管理功能,确保系统的稳定性和可靠性。”

项目搭建

代码仓库URL:https://gitee.com/itwenke/spring-boot-demo/tree/master/transactional
项目截图:
spring事务失效,spring,java,后端

pom配置

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

数据库配置

spring.datasource.url=jdbc:mysql://localhost:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

数据表结构

CREATE TABLE `bank_account` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `account` varchar(32) COLLATE utf8mb4_bin NOT NULL COMMENT '账户',
  `balance` bigint NOT NULL COMMENT '余额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='银行账户';

失效原因

私有方法private

Spring的事务代理通常是通过Java动态代理或CGLIB动态代理生成的,这些代理要求目标方法是公开可访问的(public)。私有方法无法被代理,因此事务将无效。

@Transactional(rollbackFor = Exception.class)
private void addBankAccount(BankAccount bankAccount) throws Exception {
    bankAccountService.addBankAccount(bankAccount);
    throw new Exception("测试事务回滚");
}

直接使用时,这种场景也不太容易出现,因为IDEA会有提醒。
解决方法是将目标方法改为public或protected。

@Transactional(rollbackFor = Exception.class)
public void addBankAccount(BankAccount bankAccount) throws Exception {
    bankAccountService.addBankAccount(bankAccount);
    throw new Exception("测试事务回滚");
}

目标类没有配置为Bean

Spring的事务管理需要在Spring容器中配置的Bean上才能生效。如果目标类没有被配置为Spring Bean,那么事务将无法被应用。

public class NonBeanDemo {

    @Transactional(rollbackFor = Exception.class)
    public void addBankAccount(BankAccount bankAccount) throws Exception {
        IBankAccountService bankAccountService = SpringBeanUtil.getBean(IBankAccountService.class);
        bankAccountService.addBankAccount(bankAccount);
        throw new Exception("测试事务回滚");
    }
}

解决方法是确保目标类被正确配置为Spring Bean。

@Component
public class NonBeanDemo {

    @Transactional(rollbackFor = Exception.class)
    public void addBankAccount(BankAccount bankAccount) throws Exception {
        IBankAccountService bankAccountService = SpringBeanUtil.getBean(IBankAccountService.class);
        bankAccountService.addBankAccount(bankAccount);
        throw new Exception("测试事务回滚");
    }
}

异常不匹配

@Transactional注解默认处理运行时异常,即只有抛出运行时异常,才会触发事务回滚。

@Transactional
public void addBankAccount(BankAccount bankAccount) throws Exception {
    bankAccountService.addBankAccount(bankAccount);
    throw new Exception("测试事务回滚");
}

解决方法是@Transactional设置为@Transactional(rollbackFor = Exception.class)。

@Transactional(rollbackFor = Exception.class)
public void addBankAccount(BankAccount bankAccount) throws Exception {
    bankAccountService.addBankAccount(bankAccount);
    throw new Exception("测试事务回滚");
}

跨越多个线程

如果您的应用程序在多个线程之间共享数据库连接和事务上下文,事务可能会失效,除非适当地配置事务传播属性。

  1. 子线程抛异常,主线程正常:
@Transactional(rollbackFor = Exception.class)
public void addBankAccount1() {
    new Thread(() -> {
        BankAccount bankAccount = new BankAccount();
        bankAccount.setAccount("11111111");
        bankAccount.setBalance(10000L);
        bankAccountService.addBankAccount(bankAccount);
    }).start();

    new Thread(() ->{
        BankAccount bankAccount = new BankAccount();
        bankAccount.setAccount("22222222");
        bankAccount.setBalance(10000L);
        bankAccountService.addBankAccount(bankAccount);
        throw new RuntimeException("测试事务回滚");
    }).start();
}
  1. 主线程抛异常,子线程正常:
@Transactional(rollbackFor = Exception.class)
public void addBankAccount2() {
    new Thread(() -> {
        BankAccount bankAccount = new BankAccount();
        bankAccount.setAccount("11111111");
        bankAccount.setBalance(10000L);
        bankAccountService.addBankAccount(bankAccount);
    }).start();

    new Thread(() ->{
        BankAccount bankAccount = new BankAccount();
        bankAccount.setAccount("22222222");
        bankAccount.setBalance(10000L);
        bankAccountService.addBankAccount(bankAccount);
    }).start();

    throw new RuntimeException("测试事务回滚");
}

解决方法:参考分布式事务2PC(二阶段提交)方案,2PC是同步阻塞协议,需要等待各个线程执行完成才能进行”提交“还是”回滚”的操作。
spring事务失效,spring,java,后端

public class MultiThreadingTransactionManager {

    /**
     * 事务管理器
     */
    private final PlatformTransactionManager platformTransactionManager;

    /**
     * 超时时间
     */
    private final long timeout;

    /**
     * 时间单位
     */
    private final TimeUnit unit;

    /**
     * 主线程门闩:当所有的子线程准备完成时,通知主线程判断统一”提交“还是”回滚”
     */
    private final CountDownLatch mainStageLatch = new CountDownLatch(1);

    /**
     * 子线程门闩:count 为0时,说明子线程都已准备完成了
     */
    private CountDownLatch childStageLatch = null;

    /**
     * 是否提交事务
     */
    private final AtomicBoolean isSubmit = new AtomicBoolean(true);

    /**
     * 构造方法
     *
     * @param platformTransactionManager 事务管理器
     * @param timeout 超时时间
     * @param unit 时间单位
     */
    public MultiThreadingTransactionManager(PlatformTransactionManager platformTransactionManager, long timeout, TimeUnit unit) {
        this.platformTransactionManager = platformTransactionManager;
        this.timeout = timeout;
        this.unit = unit;
    }

    /**
     * 任务执行器
     *
     * @param tasks 任务列表
     * @param executorService 线程池
     * @return 是否执行成功
     */
    public boolean execute(List<Runnable> tasks, ThreadPoolTaskExecutor executorService) {
        // 排查null空值
        tasks.removeAll(Collections.singleton(null));

        // 属性初始化
        init(tasks.size());

        for (Runnable task : tasks) {
            // 创建线程
            Thread thread = new Thread(() -> {
                // 判断其它线程是否已经执行任务失败,失败就不执行了
                if (!isSubmit.get()) {
                    childStageLatch.countDown();
                }

                // 开启事务
                DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
                TransactionStatus transactionStatus = platformTransactionManager.getTransaction(defaultTransactionDefinition);
                try {
                    // 执行任务
                    task.run();
                } catch (Exception e) {
                    // 任务执行失败,设置回滚
                    isSubmit.set(false);
                }
                // 计数器减一
                childStageLatch.countDown();

                try {
                    // 等待主线程的指示,判断统一”提交“还是”回滚”
                    mainStageLatch.await();
                    if (isSubmit.get()) {
                        // 提交
                        platformTransactionManager.commit(transactionStatus);
                    } else {
                        // 回滚
                        platformTransactionManager.rollback(transactionStatus);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            // 线程池执行任务
            executorService.execute(thread);
        }

        try {
            // 主线程等待所有子线程准备完成,避免死锁,设置超时时间
            childStageLatch.await(timeout, unit);
            long count = childStageLatch.getCount();
            // 主线程等待超时,子线程可能发生长时间阻塞,死锁
            if (count > 0) {
                // 设置回滚
                isSubmit.set(false);
            }
            // 主线程通知子线程”提交“还是”回滚”
            mainStageLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 返回执行结果是否成功
        return isSubmit.get();
    }

    /**
     * 属性初始化
     * @param size 任务数量
     */
    private void init(int size) {
        childStageLatch = new CountDownLatch(size);
    }
}

注意事项1: 2PC是同步阻塞协议,各个任务会等待所有的任务完成准备阶段才能进一步执行,所以在使用中一定要给任务列表提供充足的空闲线程,比如任务列表长度为8,线程池最大线程数不能小于8,否则会使其中的几个任务得不到执行,而其他线程会一直进行等待。即使有一阶段超时处理,事务也始终得不到提交。

注意事项2: 如果你的任务是对数据库进行操作,需要考虑数据库连接是否充足,线程等待过程中不会释放数据库连接,如果Connection不够,即使任务被线程池调度执行,也会阻塞在获取数据库连接中,同样会发生“死锁”。

事务传播属性

事务传播属性定义了事务如何传播到嵌套方法或外部方法。如果事务传播属性设置不正确,可能会导致事务失效或不符合预期的行为。
以下是七种事务传播类型:

  1. REQUIRED: 如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。这是最常用的传播行为,也是默认的,适用于大多数情况。(默认事务:有就加入,没有就新建)
  2. REQUIRES_NEW: 无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起。适用于需要独立事务执行的场景,不受外部事务的影响。(独立事务:有没有,都新建)
  3. SUPPORTS: 如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务方式执行。适用于不需要强制事务的场景,可以与其他事务方法共享事务。(不强制事务:有就加入,没有就没有)
  4. NOT_SUPPORTED: 以非事务方式执行,如果当前存在事务,则将当前事务挂起。适用于不需要事务支持的场景,可以在方法执行期间暂时禁用事务。(非事务:有不加入,没有也不新建)
  5. MANDATORY: 如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常。适用于必须在事务中执行的场景,如果没有事务则会抛出异常。(强制事务:有就加入,没有就抛异常)
  6. NESTED: 如果当前存在事务,则在嵌套事务中执行,如果当前没有事务,则创建一个新的事务。嵌套事务是外部事务的一部分,可以独立提交或回滚。适用于需要在嵌套事务中执行的场景。(嵌套事务:有就嵌套,没有就新建)
  7. NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。适用于不允许在事务中执行的场景,如果存在事务则会抛出异常。(强制非事务:没有就没有,有就抛异常)

使用CGLIB动态代理

默认情况下,Spring的事务代理使用基于接口的JDK动态代理。如果您将@Transactional注解声明在接口上,而目标类是使用CGLIB代理的,事务将不会生效。

解决方法是将@Transactional注解移到目标类的方法上,或者配置Spring以使用CGLIB代理接口。

内部类访问

类内部非直接访问带注解标记的方法addBankAccount,而是通过类普通方法 testInnerClass,然后由 testInnerClass 调用 addBankAccount。

@Transactional(rollbackFor = Exception.class)
public void addBankAccount(BankAccount bankAccount) throws Exception {
    bankAccountService.addBankAccount(bankAccount);
    throw new Exception("测试事务回滚");
}

public void testInnerClass(BankAccount bankAccount) throws Exception {
    addBankAccount(bankAccount);
}

解决方法是使用SpringBeanUtil.getBean()获取代理对象。文章来源地址https://www.toymoban.com/news/detail-846064.html

@Transactional(rollbackFor = Exception.class)
public void addBankAccount(BankAccount bankAccount) throws Exception {
    bankAccountService.addBankAccount(bankAccount);
    throw new Exception("测试事务回滚");
}

public void testInnerClass(BankAccount bankAccount) throws Exception {
    InnerClassDemo innerClassDemo = SpringBeanUtil.getBean(InnerClassDemo.class);
    innerClassDemo.addBankAccount(bankAccount);
}

到了这里,关于Spring 事务(Transactional)失效的七种原因及解决方案(含项目代码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 揭秘Spring事务失效场景分析与解决方案

    在Spring框架中,事务管理是一个核心功能,然而有时候会遇到事务失效的情况,这可能导致数据一致性问题。本文将深入探讨一些Spring事务失效的常见场景,并提供详细的例子以及解决方案。 场景: 当一个事务方法内部调用另一个方法,而被调用的方法没有声明为 @Transact

    2024年02月02日
    浏览(38)
  • Spring事务失效场景之类内部方法调用及解决方案

    在日常开发中,经常有需要使用事务来保证数据一致性的情况。简单点的话直接在方法上面加@Transactional注解就可以了。 但这样存在一个问题,在整个业务方法层面加注解会把很多并不需要归入事务的操作也归入到了事务里面,这样会可能会出现大事务的情况,影响系统性能

    2024年01月17日
    浏览(42)
  • 面试好题:@Transactional声明式事务注解什么时候会失效?

    今天来分享一道比较有意思的面试题,“@Transactional声明式事务注解什么时候会失效?”。 对于这个问题,我们一起看看考察点和比较好的回答吧!     这个问题就是面试官想考察我们对@Transactional注解有没有深刻的认识,以及日常开发中是否善于积累,认真思考。 下面我

    2024年02月09日
    浏览(33)
  • Spring的事务(@Transactional)

    Spring事务的本质,其实就是通过 Spring AOP 切面技术 Spring事务支持2种使用方式 声明式事务(注解方式) 编程式事务(代码方式):代码需要手动控制,比较繁琐,一般不使用 SpringBoot 默认开启了事务 Spring Spring的事务是使用AOP来实现的,在执行目标方法的前和后,加上了事务

    2024年02月21日
    浏览(37)
  • Spring使用@Transactional 管理事务,Java事务详解。

    B站视频:https://www.bilibili.com/video/BV1eV411u7cg 技术文档:https://d9bp4nr5ye.feishu.cn/wiki/HX50wdHFyiFoLrkfEAAcTBdinvh 简单来说事务就是一组对数据库的操作 要么都成功,要么都失败。 事务要保证可靠性,必须具备四个特性:ACID。 A:原子性:事务是一个原子操作单元,要么完全执行,要么

    2024年02月11日
    浏览(25)
  • Spring @Transactional事务传播机制详解

    我们日常工作中极少使用事务传播级别,单纯只是使用事务和rollbackfor抛出异常来解决事务问题,但其实我们很多时候使用的是不正确的,或者说会造成事务粒度过大,本文详解一下事务传播级别,也让自己更好地处理事务问题。 1.什么是事务传播机制? 举个栗子,方法A是一

    2024年02月14日
    浏览(35)
  • Spring——事务注解@Transactional【建议收藏】

    在某些业务场景下,如果一个请求中,需要同时写入多张表的数据或者执行多条sql,为了保证操作的原子性(要么同时成功,要么同时失败),避免数据不一致的情况,我们一般都会用到事务;Spring框架下,我们经常会使用@Transactional注解来管理事务; 本篇介绍Spring的事务注

    2024年02月03日
    浏览(39)
  • Spring 事务(编程式事务、声明式事务@Transactional、事务隔离级别、事务传播机制)

    本篇重点总结: 在 Spring 项目中使用事务,有两种方式:编程式手动操作和声明式自动提交,声明式自动提交使用最多,只需要在方法上添加注解 @Transactional 设置事务的隔离级别 @Transactional(isolation = Isolation.SERIALIZABLE),Spring 中的事务隔离级别有5种 设置事务的传播机制 @Tra

    2024年02月03日
    浏览(36)
  • Spring 声明式事务 @Transactional(基本使用)

            声明式事务的实现很简单,只需要在需要事务的⽅法上添加 @Transactional 注解就可以实现了.⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了 没有处理的异常会⾃动回滚事务.         废话不多说,直接看代码实现,

    2024年01月23日
    浏览(46)
  • 【spring(四)】Spring事务管理和@Transactional注解

    🌈键盘敲烂,年薪30万🌈 目录 Spring中的事务管理 问题抛出: 解决方案: @Transactional注解: rollbackFor属性: propagation属性: 应用: 📕总结 知识回顾: ❓什么是事务 事务是对数据操作的集合,它是数据操作的最小执行单位,也就是说,要么一个事务中操作全部执行完毕,

    2024年01月17日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包