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配置全局事务管理

示例:

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);
    }
}
  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
是的,MySQL中的大事务可能会导致无法立即杀死一个SQL语句。当一个事务执行了大量的操作并且还没有提交或回滚时,这个事务被称为大事务。在这种情况下,您可能会遇到以下情况: 1. 当您尝试使用 `KILL` 命令终止一个正在执行的SQL语句时,MySQL将停止该语句的执行,但事务仍然保持活动状态。这意味着任何对数据库的更改都不会回滚,也不会释放由事务占用的资源。您可以使用 `SHOW PROCESSLIST` 命令来查看事务的状态。 2. 如果您使用 `KILL` 命令终止一个正在执行的大事务,MySQL将尝试回滚该事务中的所有更改。这可能需要一些时间,特别是如果事务涉及大量数据或复杂的操作。在此期间,您可能无法立即释放由该事务占用的资源。 为了避免这种情况,我们可以考虑以下几点: 1. 在执行大事务之前,请确保事务的操作是必要的,并尽量将事务拆分为较小的、更可管理的部分。 2. 使用合适的索引和优化查询,以减少事务执行时间和资源消耗。 3. 如果您必须终止一个正在执行的大事务,可以尝试使用 `KILL` 命令多次,以确保事务得到回滚并释放相关资源。 请注意,大事务可能会对数据库性能产生负面影响,并且在执行回滚操作时可能会导致数据库响应变慢。因此,在设计和执行事务时,需要谨慎考虑和测试,以确保数据库的稳定性和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hippoDocker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值