Spring控制事务回滚

这篇具有很好参考价值的文章主要介绍了Spring控制事务回滚。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、说明

1、Spring中开启事务的方式主要有两种:编程式事务和声明式事务

2、事务是我们开发过程中经常会使用到的,为了在业务执行过程中出现异常时,回滚到原始状态。而事务的回滚在大多数情况下都是靠着 exception(异常)来触发回滚的,当事务机制捕捉到异常,就会开始回滚。

3、但往往也会出现情况:在业务代码中,需要对异常单独进行处理,异常不会抛出,但需要事务回滚的情况,这个时候就需要手动调用回滚

2、声明式性事务@Transacational

@Transactional 声明式事务,是开发过程中最常用的开启事务的方式。
也可以通过切面,对整个业务层的方法进行事务控制

优点:使用方便,而且对代码的侵入性低

这种方式,默认是通过 捕获 RunTimeException 来触发事务回滚,只要是 RuntimeException 以及 它的子类,都可以触发事务回滚。也可以修改触发事务回滚的异常范围,可以通过修改 @Transactional 中的 rollbackFor 属性,修改异常范围。比如:
@Transactional(rollbackFor = Exception.class),修改完成后,只要是 Exception 类的子类,都可以触发事务回滚

3、@Transactional不适用场景

有时候需要对异常进行特殊处理,异常被捕获无法抛出时,声明式事务就失效不可用。

示例:抛出异常被捕获,无法触发事务回滚。

    //业务代码
    try{ 
        //业务处理出现异常
    } catch (Exception e) {
        // 捕获异常,打印异常,或其他处理。但不抛出新的异常
        e.printStackTrace();
        //可以将捕获后的异常,封装为新的业务异常抛出(正常回滚)
        throw new xxxxException(e.getMessage);
    }
    //此时return语句能够执行
    return  xxx;

此处,可以将捕获后的异常,封装为新的业务异常抛出

4、@Transactional注解事务失效的几种场景及原因

在开发过程中,我们需要同时进行多个对数据的操作,这时候需要使用事务去控制多个操作的一致性。
通过声明式事务**@Transactional** 注解修饰方法的形式开启事务,需要注意到以下这些情况会出现事务失效。

4.1、数据库引擎不支持事务

例如我们使用Mysql数据,在 5.5 版本之前,Mysql 默认的数据引擎都是 MyISAM 的,而 MyISAM不支持事务的;在 5.5 版本之后,默认的数据引擎是 InnoDB 的,它是支持事务的。

Spring 对事务的管理实现又是基于数据库的,所以当数据库引擎不支持事务的时候,自然就不会有起作用的事务机制了,所有我们在选择数据库的时候,需要去选择支持事务的数据引擎。

4.2、添加事务的类没有被Spring管理

当添加了注解或者全局事务配置了路径,但是需要用到事务的方法所在的类没有注入到Spring容器中,这样事务也不会生效,通常我们都是添加到业务逻辑处理层,通常都是添加@Service,将当前类注入到Spring中。

4.3、@Transactional作用的方法不是public修饰的

@Transactional注解应用在非public修饰的方法上,Transactional将会失效。这是因为在Spring AOP代理时,事务拦截器只能拦截public方法,而非public方法将直接被执行,不会经过代理。因此,建议将@Transactional注解只应用在public方法上。

4.4、@Transactional的rollbackFor属性设置错误

rollbackFor是@Transactional注解的一个属性,用于指定能够触发事务回滚的异常类型。Spring默认只在遇到未检查unchecked异常(继承自RuntimeException的异常)或者Error时才回滚事务;其他异常不会触发回滚事务。如果想让其他异常也能触发回滚事务,可以在@Transactional注解中加上rollbackFor属性,设置Exception异常就回滚,这样不管是Exception还是RuntimeException,spring都能帮助我们去回滚数据了。

示例:当抛出Exception异常时,想要触发事务回滚,就要设置@Transactional(rollbackFor = Exception.class)

    @Transactional(rollbackFor = Exception.class)
    public void test() {
        // 业务代码
        if(1 == 1) {
            throw new Exception("Exception 异常");
        }
    }

4.5、@Transactional的propagation属性设置错误

设置 @Transactional(propagation = xxxx)可以配置 Spring 的事务传播机制的,当事务传播机制设置为不支持事务是,事务也是不会生效的。propagation参数指定了事务的传播行为,即在一个事务方法中调用另一个事务方法时,两个事务如何关联。

示例:当设置为 propagation = Propagation.NOT_SUPPORTED 时,表示不以事务运行,当前若存在事务则挂起。

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void test1() {
        // 业务代码
    }

propagation有以下几种取值:

  • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则与REQUIRED类似,创建一个新的事务。

propagation的默认值是REQUIRED。

4.6、调用同类的方法,事务失效

调用本类方法导致传播行为失效,同一个 Service 的两个方法之间调用,就会出现这个问题,原因还是在代理对象这里,我们期待的调用是一个代理类的调用,但是我们若是直接在方法中内部调用,不好意思,被调用的方法的事务失效,没有被 AOP 增强。

**示例:**方法addStudents调用本类中的方法addStudent

@Service
public class StudentService {
    @Transactional
    public void addStudent(Student student) {
        // do something
    }

    public void addStudents(List<Student> students) {
        for (Student student : students) {
            // 这里直接调用同类的方法,事务不会生效
            addStudent(student);
        }
    }
}

解决方案一:自己调用自己,自己注入自己。你可能看见过这样的代码,就是为了解决这个问题的。这种解决方案最常见。

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
    public void a (){
        service.b();
    }

方案二:还有一种方法可以在同类调用的方法上,通过AOP代理调用,即使用AopContext.currentProxy()方法获取当前类的代理对象,然后通过代理对象调用目标方法,这样就可以执行事务切面,进行事务增强。

@Service
public class StudentService {
    @Transactional
    public void addStudent(Student student) {
        // do something
    }

    public void addStudents(List<Student> students) {
        for (Student student : students) {
            // 这里通过代理对象调用同类的方法,事务会生效
            ((StudentService)AopContext.currentProxy()).addStudent(student);
        }
    }
}

方案三:在同类调用的方法上,使用@Async注解,这样就可以在异步线程中执行目标方法,而不是在当前线程中执行,这样就可以避免事务失效的问题。

@Service
public class StudentService {
    @Transactional
    @Async
    public void addStudent(Student student) {
        // do something
    }

    public void addStudents(List<Student> students) {
        for (Student student : students) {
            // 这里在异步线程中调用同类的方法,事务会生效
            addStudent(student);
        }
    }
}

4.7、异常被捕获,事务无法回滚

事务机制的回滚,是 通过异常来触发事务回滚的。在开发过程中,会出现异常被捕获处理了,而且没有再抛出新的异常,就会导致异常丢失,无法触发回滚。

**示例1:**不会触发回滚的情况

    @Transactional
    public void test1() {
        // 演示直接抛出异常被捕获
        try {
            throw new  RuntimeException();
        } catch (RuntimeException e) {
            // 不做处理,虽然有异常,但异常丢失,无法触发事务回滚
        }
    }

**示例2:**catch 捕获异常后,再抛出

    @Transactional
    public void test1() {
        // test code
        try {
            throw new  RuntimeException();
        } catch (RuntimeException e) {
            // 不做处理,虽然有异常,但异常丢失,无法触发事务回滚
            throw e;
        }
    }

5、spring事务控制手动回滚

Spring事务控制手动回滚是指在某些情况下,我们需要主动触发事务的回滚,而不是依赖于Spring的默认回滚策略。Spring的默认回滚策略是对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。有两种常用的方法可以实现手动回滚:

  • 手动抛出一个运行时异常,例如throw new RuntimeException(),这样Spring就会捕获到这个异常,并执行事务回滚。
  • 通过Spring提供的事务切面支持类TransactionAspectSupport,调用其currentTransactionStatus().setRollbackOnly()方法,这样就可以标记当前事务为回滚状态,而不需要抛出异常。

使用手动回滚的方法时,需要注意以下几点:

  • 被回滚的方法必须使用@Transactional注解,否则无法开启事务管理,也就无法回滚。
  • 被回滚的方法不能在同一个类中直接调用,否则无法走代理类,也就无法回滚,需要通过AOP代理调用或者注入自身的bean来调用。
  • 被回滚的方法不能使用try catch来捕获异常,否则无法触发回滚,需要在方法签名上声明throws或者在catch块中重新抛出异常。

**示例:**手动回滚事务

    //假设这是一个service类的片段
    try{ 
        //出现异常
    } catch (Exception e) {
        // 捕获异常,打印异常,或其他处理。但不抛出新的异常
        e.printStackTrace();
        // 手动回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    //此时return语句能够执行
    return  xxx;

6、AOP配置全局事务管理

示例:文章来源地址https://www.toymoban.com/news/detail-490643.html

package com.gd.gd_service.config;

import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: hippoDocker
 * @Date:2021年12月21日20:51:27
 * @Description: TODO 全局事务配置
 */
@Configuration
@Aspect
public class TransactionManagerConfig {
    private static final Logger logger = LoggerFactory.getLogger(TransactionManagerConfig.class);
    /**
     * 配置方法过期时间,默认-1,永不超时,单位秒
     */
    @Value("${spring.application.transactiontimeout}")
    private int AOP_TIME_OUT;
    /**
     * 配置切入点表达式 : 指定哪些包中的类使用事务
     */
    private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.gd.gd_service.service.impl.*.*(..)))";
    //事务管理器
    @Autowired
    private TransactionManager transactionManager;
    /**
     * 全局事务配置
     * REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
     * SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
     * MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
     * REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
     * NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
     * NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
     * NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。
     * 指定方法:通过使用 propagation 属性设置,例如:@Transactional(propagation = Propagation.REQUIRED)
     */
    @Bean
    public TransactionInterceptor txAdvice(){
        /**
         * 配置事务管理规则
         * 这里配置只读事务
         * 查询方法, 只读事务,不做更新操作
         */
        RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
        readOnlyTx.setReadOnly(true);
        readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        /**
         * 增、删、改 需要的事务
         * 必须带事务
         * 当前存在事务就使用当前事务,当前不存在事务,就开启一个新的事务
         */
        RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
        // 设置回滚规则:什么异常都需要回滚
        requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        // 当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务
        requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 设置超时时间,超时则抛出异常回滚
        requiredTx.setTimeout(AOP_TIME_OUT);
        System.out.println("=====>>事务超时时间:"+requiredTx.getTimeout());
        /**
         * 无事务地执行,挂起任何存在的事务
         */
        RuleBasedTransactionAttribute noTx = new RuleBasedTransactionAttribute();
        noTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);

        /**
         * 设置方法对应的事务
         */
        Map<String, TransactionAttribute> methodMap = new HashMap<>();
        // 可以提及事务或回滚事务的方法
        //只读事务
        methodMap.put("get*", readOnlyTx);
        methodMap.put("query*", readOnlyTx);
        methodMap.put("find*", readOnlyTx);
        methodMap.put("list*", readOnlyTx);
        methodMap.put("count*", readOnlyTx);
        methodMap.put("exist*", readOnlyTx);
        methodMap.put("search*", readOnlyTx);
        methodMap.put("fetch*", readOnlyTx);
        //写事务
        methodMap.put("add*", requiredTx);
        methodMap.put("save*", requiredTx);
        methodMap.put("insert*", requiredTx);
        methodMap.put("update*", requiredTx);
        methodMap.put("modify*", requiredTx);
        methodMap.put("delete*", requiredTx);
        methodMap.put("creat*", requiredTx);
        methodMap.put("edit*", requiredTx);
        methodMap.put("remove*", requiredTx);
        methodMap.put("repair*", requiredTx);
        methodMap.put("binding*", requiredTx);
        methodMap.put("clean*", requiredTx);
        methodMap.put("upload*",requiredTx);
        //无事务
        methodMap.put("noTx*", noTx);
        // 其他方法无事务,只读
        methodMap.put("*", readOnlyTx);

        //声明一个通过方法名字配置事务属性的对象
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        source.setNameMap(methodMap);

        //返回事务拦截器
        TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
        return txAdvice;
    }

    @Bean(name = "txAdviceAdvisor")
    public Advisor txAdviceAdvisor(TransactionInterceptor txAdvice) {
        logger.info("=====>>创建txAdviceAdvisor");
        //配置事务切入点表达式
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        //增强事务,关联切入点和事务属性
        return new DefaultPointcutAdvisor(pointcut, txAdvice);
    }
}

到了这里,关于Spring控制事务回滚的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Spring Boot 各种事务操作实战(自动回滚、手动回滚、部分回滚)

    事务定义 事务,就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执

    2024年04月17日
    浏览(18)
  • spring 事务回滚失败异常

    事务模板里抛异常,抛异常前的update操作成功,事务没有回滚成功,业务数据还是落db了。debug代码,发现GenericConnectionContext类中derivedConnectionMap是空的,导致回滚代码没有执行 保证事务内所有的 DAO 操作所涉及的数据源和事务模板所关联的数据源都是同一个对象, bundle 都是

    2024年02月13日
    浏览(32)
  • pymysql 上下文管理器控制事务提交和回滚

    示例: 从上面例子看出, 在with结构进入时,事务开始begin 在with结构退出时,会根据是否产生异常进行commit和rollback操作,并关闭连接

    2024年01月25日
    浏览(51)
  • SpringBoot项目中控制线程池、多线程事务提交、回滚的方式

    场景: 1、由于多线程每个线程都是一个异步任务,所以每个线程都是一个单独的事务,通常使用的声明式事务 @Transactional() 是无法控制多线程中事务的 2、所以只能另寻解决方式 解决: 一、基于TransactionStatus集合来控制多线程事务提交(推荐此方式) 1、代码案例

    2024年02月15日
    浏览(41)
  • 深入了解 Spring boot的事务管理机制:掌握 Spring 事务的几种传播行为、隔离级别和回滚机制,理解 AOP 在事务管理中的应用

    🎉🎉欢迎光临,终于等到你啦🎉🎉 🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀 🌟持续更新的专栏 《Spring 狂野之旅:从入门到入魔》 🚀 本专栏带你从Spring入门到入魔   这是苏泽的个人主页可以看到我其他的内容哦👇👇 努力的苏泽 http://suzee.blog.csdn

    2024年03月21日
    浏览(71)
  • Spring @Transactional注解事务传播机制propagation参数说明

    在SpringBoot项目中,我们通常使用 @Transactional 去进行事务控制,而 @Transactional 注解中,有个比较关键的属性就是 propagation 。在一个 多事务 的环境中,一个事务方法调用另一个事务方法时,就会涉及到事务的传播行为,该属性用来控制一段代码经过多个 @Transactional 注解生效(

    2024年02月11日
    浏览(32)
  • Spring事务控制

    目录 1、什么是事务控制 2、编程式事务控制 2.1、简介 2.2、相关对象 2.2.1、PlatformTransactionManager 2.2.2、TransactionDefinition 2.2.2.1、事务隔离级别 2.2.2.2、事务传播行为 2.2.3、TransactionStatus 3、声明式事务控制 3.1、简介 3.2、区别 3.3、⭐作用 3.4、🔺基于xml 3.4.1、引入tx命名空间 3.4

    2024年02月13日
    浏览(26)
  • 27.Spring的事务控制

    目录 一、编程式事务控制相关对象。 (1)事务管理器。 (2)事务定义信息对象(如隔离级别、传播行为)。 (3)事务状态对象。 (4) 知识要点。 二、声明式事务控制—基于xml或注解。 (1)声明式事务控制的定义。  (2)声明式事务控制——基于xml配置。 (2.1) x

    2023年04月26日
    浏览(24)
  • Spring——Spring的事务控制(1)基础篇

    1.1.什么是事务? 当你需要一次执行多条SQL语句时,可以使用事务。通俗一点说,如果这几条SQL语句全部执行成功,则才对数据库进行一次更新,如果有一条SQL语句执行失败,则这几条SQL语句全部不进行执行,这个时候需要用到事务。 刘德华《无间道》:去不了终点,回到原

    2024年01月16日
    浏览(27)
  • Spring JDBC和事务控制

    ​ Spring 框架除了提供 IOC 与 AOP 核心功能外,同样提供了基于JDBC 的数据访问功能,使得访问持久层数据更加方便。想要使用 Spring JDBC 环境,需要将JDBC整合到Spring中。 构建项目添加依赖坐标 构建项目:普通的java项目即可 添加 jdbc 配置文件 在src/main/resources目录下新建db.pro

    2024年02月04日
    浏览(21)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包