Transactional事务失效场景汇总

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

1、前言

作为后端程序员,在日常开发中,经常会遇到事务处理的场景,在Spring中,为了更好的支撑我们进行数据库操作,它提供了两种事务管理的方式:

  • 编程式事务
  • 声明式事务

那众所周知,我们平时用的最多的就是声明式事务,也就是使用**@Transactional**注解的方式了

但是在日常开发中,如果对注解@Transactional使用不当的话,可能会导致事务失效,所以今天我们一起来总结梳理一下常见的一些失效场景,我这里梳理了下面这些场景:

Transactional事务失效场景汇总

2、失效场景

2.1、Service没有被Spring管理

看如下代码:

package org.wujiangbo.service.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author bobo(weixin:javabobo0513)
 */
//@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;
    @Resource
    private SysOperLogMapper logMapper;

    @Override
    @Transactional
    public JSONResult addUser(User user, SysOperLog log) {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;//制造异常:发生算数异常
        return JSONResult.success("操作成功");
    }

}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

上面例子中, @Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理来支持事务。但是@Service被注释后,你的service类都不是spring管理的,那怎么创建代理类来支持事务呢,所以此种场景事务注解会失效,大家在开发过程中要仔细了,不要忘记,将@Transactional所在的类,交给Spring管理

2.2、事务方法被final、static关键字修饰

看如下代码:

package org.wujiangbo.service.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author bobo(weixin:javabobo0513)
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;
    @Resource
    private SysOperLogMapper logMapper;

    @Override
    @Transactional
    public final JSONResult addUser(User user, SysOperLog log) {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
        return JSONResult.success("操作成功");
    }

}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

如果一个方法被声明为final或者static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务

2.3、同一个类中,方法内部调用

看下面代码:

package org.wujiangbo.service.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author bobo(weixin:javabobo0513)
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;
    @Resource
    private SysOperLogMapper logMapper;

    @Override
    public JSONResult addUser(User user, SysOperLog log) {
        doSomething(user, log);
        return JSONResult.success("操作成功");
    }

    @Transactional
    public void doSomething(User user, SysOperLog log){
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
    }

}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用。即以上代码,调用目标doSomething方法不是通过代理类进行的,因此事务不生效

2.4、方法的访问权限不是public

看下面代码:

package org.wujiangbo.service.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author bobo(weixin:javabobo0513)
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;
    @Resource
    private SysOperLogMapper logMapper;

    @Override
    @Transactional
    private JSONResult addUser(User user, SysOperLog log) {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
        return JSONResult.success("操作成功");
    }

}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

spring事务方法addUser的访问权限不是public,所以事务就不生效了,因为Spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,也就是这时事务属性不存在了

大家可以看下AbstractFallbackTransactionAttributeSource的源码:

Transactional事务失效场景汇总

2.5、数据库的存储引擎不支持事务

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,必须要确认你的选择的存储引擎是支持事务的

比如下面的SQL创建用户表时,就采用的是InnoDB存储引擎:

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

2.6、@Transactional 注解配置错误

看如下代码:

@Transactional(readOnly = true)
public JSONResult addUser(User user, SysOperLog log) {
    //新增用户信息
    userMapper.insert(user);
    //新增日志记录
    logMapper.insert(log);
    int i = 1/0;
    return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

虽然使用了@Transactional注解,但是注解中的readOnly=true属性指示这是一个只读事务,因此在保存数据时会抛出如下异常:

Transactional事务失效场景汇总

我们使用@Transactional注解时,一般不需要跟后面的readOnly属性

2.7、使用了错误的事务传播机制

看如下代码:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public JSONResult addUser(User user, SysOperLog log) {
    //新增用户信息
    userMapper.insert(user);
    //新增日志记录
    logMapper.insert(log);
    int i = 1/0;
    return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

这里事务失效的原因是:Propagation.NOT_SUPPORTED表示传播特性不支持事务

我们一起来回顾下Spring提供了七种事务传播机制。它们分别是:

  • REQUIRED(默认):如果当前存在一个事务,则加入该事务;否则,创建一个新事务。该传播级别表示方法必须在事务中执行。
  • SUPPORTS:如果当前存在一个事务,则加入该事务;否则,以非事务的方式继续执行。
  • MANDATORY:如果当前存在一个事务,则加入该事务;否则,抛出异常。
  • REQUIRES_NEW:创建一个新的事务,并且如果存在一个事务,则将该事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。
  • NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
  • NESTED:如果当前存在一个事务,则在嵌套事务内执行。如果没有事务,则按REQUIRED传播级别执行。嵌套事务是外部事务的一部分,可以在外部事务提交或回滚时部分提交或回滚。

2.8、rollbackFor属性配置错误

看如下代码:

@Transactional(rollbackFor = Error.class)
public JSONResult addUser(User user, SysOperLog log) throws Exception {
    //新增用户信息
    userMapper.insert(user);
    //新增日志记录
    logMapper.insert(log);
    if(1 == 1){
        //模拟抛出异常
        throw new Exception();
    }
    return JSONResult.success("操作成功");
}

分析:

rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeExceptionError两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效

大家可以看一下Transactional注解源码:

Transactional事务失效场景汇总

2.9、异常被捕获并处理了,没有抛出

看如下代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log){
    try {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
    }
    catch (Exception e){
        e.printStackTrace();
    }
    return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚

我们可以从spring源码(TransactionAspectSupport这个类)中找到答案:

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

//省略其他代码,只留了下面核心代码
    
@Nullable
 protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {

  if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
  
   TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
   Object retVal;
   try {
        //Spring AOP中MethodInterceptor接口的一个方法,它允许拦截器在执行被代理方法之前和之后执行额外的逻辑。
        retVal = invocation.proceedWithInvocation();
   }
   catch (Throwable ex) {
    //用于在发生异常时完成事务(如果Spring catch不到对应的异常的话,就不会进入回滚事务的逻辑)
    completeTransactionAfterThrowing(txInfo, ex);
    throw ex;
   }
   finally {
    cleanupTransactionInfo(txInfo);
   }

   //用于在方法正常返回后提交事务。
   commitTransactionAfterReturning(txInfo);
   return retVal;
  }
}

invokeWithinTransaction方法中,当Spring catch到Throwable异常的时候,就会调用completeTransactionAfterThrowing()方法进行事务回滚的逻辑。但是在我们测试代码中,直接把异常catch住了,并没有重新throw出来,因此 Spring自然就catch不到异常啦,因此事务回滚的逻辑就不会执行,事务就失效了

解决方案

spring事务方法中,当我们使用了try-catch,如果catch住异常,记录完异常日志,一定要重新把异常抛出来,正例如下:

@Transactional
public JSONResult addUser(User user, SysOperLog log){
    try {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
    }
    catch (Exception e){
        e.printStackTrace();
        throw e;
    }
    return JSONResult.success("操作成功");
}

在catch中添加:throw e;

2.10、手动抛了别的异常

看下面代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log) throws Exception {
    //新增用户信息
    userMapper.insert(user);
    //新增日志记录
    logMapper.insert(log);
    if(1 == 1){
        //模拟抛出异常
        throw new Exception();
    }
    return JSONResult.success("操作成功");
}

分析:

Spring默认只处理RuntimeException和Error或其子类,对于普通的Exception是不会回滚的,但是上面的代码例子中,手动抛了Exception异常,所以是不会回滚,除非用rollbackFor属性指定,如下:

@Transactional(rollbackFor = Exception.class)

2.11、多线程调用场景

看下面代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log){
    try {
        //新增用户信息
        userMapper.insert(user);
        //多线程调用
        new Thread(() -> {
            //新增日志记录
            logMapper.insert(log);
        }).start();

        //模拟异常
        int i = 1/0;
    }
    catch (Exception e){
        e.printStackTrace();
        throw e;
    }
    return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是日志数据还是会存到数据库之中,只有用户数据会回滚

分析:

这是因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效

我们可以进入到TransactionAspectSupport类的prepareTransactionInfo方法中看一下,有一个解释如下:

Transactional事务失效场景汇总

简单翻译:

Transactional事务失效场景汇总

从这里我们得知,事务信息是跟线程绑定的。

因此在多线程环境下,事务的信息都是独立的,将会导致Spring在接管事务上出现差异

3、总结

经过这样的总结梳理,相信你应该已经对@Transactional 注解使用的一些坑有所了解了,以后在开发过程中就要格外注意了文章来源地址https://www.toymoban.com/news/detail-428369.html

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

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

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

相关文章

  • @Transactional注解作用,不生效的场景,事务回滚

    声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 简而言之,@Transactional注解在代码执行出错的时候能够进行事务的回滚。 在启动类上添加@EnableTransac

    2024年02月10日
    浏览(48)
  • Spring 事务失效的八种场景

    原因:Spring 默认只会回滚非检查异常 解法:配置 rollbackFor 属性 @Transactional(rollbackFor = Exception.class) 原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉 解法1:异常原样抛出: 在 catch 块添加 throw new RuntimeExc

    2024年02月14日
    浏览(39)
  • Spring中事务失效的8中场景 对于一个事务开子线程

    1. 数据库引擎不支持事务 这里以 MySQL为例,MyISAM引擎是不支持事务操作的,一般要支持事务都会使用InnoDB引擎,根据MySQL 的官方文档说明,从MySQL 5.5.5 开始的默认存储引擎是 InnoDB,之前默认的都是 MyISAM,所以这一点要值得注意,如果底层引擎不支持事务,那么再怎么设置也

    2024年02月16日
    浏览(37)
  • Spring事务传播机制、实现方式、失效场景即原理

    贴一篇源码分析的好文章:https://blog.csdn.net/qq_30905661/article/details/114400417 一个事务对应一个数据库连接。 通过 this 来调用某个带有 @Transactional 注解的方法时,这个注解是失效的 spring事务底层是通过数据库事务和AOP实现的 首先对于使用@Transactional的注解的bean,spring会创建一个

    2024年02月14日
    浏览(36)
  • Spring高手之路-Spring事务失效的场景详解

    目录 前言 @Transactional 应用在非 public 修饰的方法上 同一个类中方法调用,导致@Transactional失效 final、static方法 @Transactional的用法不对 @Transactional 注解属性 propagation 设置不当 @Transactional注解属性 rollbackFor 设置错误 用错注解 异常被捕获 数据库引擎不支持事务 Spring中比较容易

    2024年01月18日
    浏览(39)
  • 揭秘Spring事务失效场景分析与解决方案

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

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

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

    2024年01月17日
    浏览(46)
  • 面试官让列举Spring的事务会失效的场景,我说了8个

    本文分享自华为云社区《哪些场景下Spring的事务会失效?》,作者:冰 河 。 在日常工作中,如果对Spring的事务管理功能使用不当,则会造成Spring事务不生效的问题。而针对Spring事务不生效的问题,也是在跳槽面试中被问的比较频繁的一个问题。 今天,我们就一起梳理下有哪

    2024年02月11日
    浏览(37)
  • 分段@Transactional 坑及失效问题

    @Transactional 背景:在某些情况下,我们需要分段transaction,在最外面没有transaction,里面分成几个transaction,保证分段是成功的。 问题代码: Service 在这种写法下,controller调用service的getOrder1方法, getOrder2锁了order,但是由于aop, 认为getOrder1是没有transaction的,所以getOrder2方法的

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

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

    2024年02月11日
    浏览(29)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包