你碰到过这8种Spring事务失效的场景的那几种?

news2025/2/27 6:16:18

前言

作为Java开发工程师,相信大家对Spring种事务的使用并不陌生。但是你可能只是停留在基础的使用层面上,在遇到一些比较特殊的场景,事务可能没有生效,直接在生产上暴露了,这可能就会导致比较严重的生产事故。今天,我们就简单来说下Spring事务的原理,然后总结一下spring事务失败的场景,并提出对应的解决方案。

Spring事务原理

大家还记得在JDBC中是如何操作事务的吗?伪代码可能如下:

//Get database connection
Connection connection = DriverManager.getConnection();
//Set autoCommit is false
connection.setAutoCommit(false);
//use sql to operate database
.........
//Commit or rollback
connection.commit()/connection.rollback

connection.close();

需要在各个业务代码中编写代码如commit()、close()来控制事务。

但是Spring不乐意这么干了,这样对业务代码侵入性太大了,所有就用一个事务注解@Transactional来控制事务,底层实现是基于切面编程AOP实现的,而Spring中实现AOP机制采用的是动态代理,具体分为JDK动态代理和CGLIB动态代理两种模式。

  1. Spring的bean的初始化过程中,发现方法有Transactional注解,就需要对相应的Bean进行代理,生成代理对象。

  2. 然后在方法调用的时候,会执行切面的逻辑,而这里切面的逻辑中就包含了开启事务、提交事务或者回滚事务等逻辑。

另外注意一点的是,Spring 本身不实现事务,底层还是依赖于数据库的事务。没有数据库事务的支持,Spring事务是不会生效的。

接下来我们进入正题,看看哪些场景会导致Spring事务失败。

Spring事务失效场景

1. 抛出检查异常

比如你的事务控制代码如下:

@Transactional
public void transactionTest() throws IOException{
    User user = new User();
    UserService.insert(user);
    throw new IOException();
}

如果@Transactional 没有特别指定,Spring 只会在遇到运行时异常RuntimeException或者error时进行回滚,而IOException等检查异常不会影响回滚。

public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}

解决方案:

知道原因后,解决方法也很简单。配置rollbackFor属性,例如@Transactional(rollbackFor = Exception.class)。

2. 业务方法本身捕获了异常

@Transactional(rollbackFor = Exception.class)
public void transactionTest() {
    try {
        User user = new User();
        UserService.insert(user);
        int i = 1 / 0;
    }catch (Exception e) {
        e.printStackTrace();
    }
}

这种场景下,事务失败的原因也很简单,Spring是否进行回滚是根据你是否抛出异常决定的,所以如果你自己捕获了异常,Spring 也无能为力。

看了上面的代码,你可能认为这么简单的问题你不可能犯这么愚蠢的错误,但是我想告诉你的是,我身边几乎一半的人都被这一幕困扰过。

写业务代码的时候,代码可能比较复杂,嵌套的方法很多。如果你不小心,很可能会触发此问题。举一个非常简单的例子,假设你有一个审计功能。每个方法执行后,审计结果保存在数据库中,那么代码可能会这样写。

@Service
public class TransactionService {

    @Transactional(rollbackFor = Exception.class)
    public void transactionTest() throws IOException {
        User user = new User();
        UserService.insert(user);
        throw new IOException();

    }
}

@Component
public class AuditAspect {

	@Autowired
	private auditService auditService;

    @Around(value = "execution (* com.alvin.*.*(..))")
    public Object around(ProceedingJoinPoint pjp) {
        try {
            Audit audit = new Audit();
            Signature signature = pjp.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            String[] strings = methodSignature.getParameterNames();
            audit.setMethod(signature.getName());
            audit.setParameters(strings);
            Object proceed = pjp.proceed();
            audit.success(true);
            return proceed;
        } catch (Throwable e) {
            log.error("{}", e);
            audit.success(false);
        }
        
        auditService.save(audit);
        return null;
    }

}

在上面的示例中,事务将失败。原因是Spring的事务切面优先级最低,所以如果异常被切面捕获,Spring自然不能正常处理事务,因为事务管理器无法捕获异常。

解决方案:

看,虽然我们知道在处理事务时业务代码不能自己捕获异常,但是只要代码变得复杂,我们就很可能再次出错,所以我们在处理事务的时候要小心,还是不要使用声明式事务, 并使用编程式事务— transactionTemplate.execute()。

3. 同一类中的方法调用

@Service
public class DefaultTransactionService implement Service {

    public void saveUser() throws Exception {
        //do something
        doInsert();
    }

    @Transactional(rollbackFor = Exception.class)
    public void doInsert() throws IOException {
        User user = new User();
        UserService.insert(user);
        throw new IOException();

    }
}

这也是一个容易出错的场景。事务失败的原因也很简单,因为Spring的事务管理功能是通过动态代理实现的,而Spring默认使用JDK动态代理,而JDK动态代理采用接口实现的方式,通过反射调用目标类。简单理解,就是saveUser()方法中调用this.doInsert(),这里的this是被真实对象,所以会直接走doInsert的业务逻辑,而不会走切面逻辑,所以事务失败。

解决方案:

方案一:解决方法可以是直接在启动类中添加@Transactional注解saveUser()

方案二:@EnableAspectJAutoProxy(exposeProxy = true)在启动类中添加,会由Cglib代理实现。

4. 方法使用 final 或 static关键字

如果Spring使用了Cglib代理实现(比如你的代理类没有实现接口),而你的业务方法恰好使用了final或者static关键字,那么事务也会失败。更具体地说,它应该抛出异常,因为Cglib使用字节码增强技术生成被代理类的子类并重写被代理类的方法来实现代理。如果被代理的方法的方法使用final或static关键字,则子类不能重写被代理的方法。

如果Spring使用JDK动态代理实现,JDK动态代理是基于接口实现的,那么final和static修饰的方法也就无法被代理。

总而言之,方法连代理都没有,那么肯定无法实现事务回滚了。

解决方案:

想办法去掉final或者static关键字

5. 方法不是public

如果方法不是public,Spring事务也会失败,因为Spring的事务管理源码AbstractFallbackTransactionAttributeSource中有判断computeTransactionAttribute()。如果目标方法不是公共的,则TransactionAttribute返回null。

// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
  return null;
}

解决方案:

是将当前方法访问级别更改为public。

6. 错误使用传播机制

Spring事务的传播机制是指在多个事务方法相互调用时,确定事务应该如何传播的策略。Spring提供了七种事务传播机制:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。如果不知道这些传播策略的原理,很可能会导致交易失败。

@Service
public class TransactionService {


    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AddressMapper addressMapper;


    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
    public  void doInsert(User user,Address address) throws Exception {
        //do something
        userMapper.insert(user);
        saveAddress(address);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public  void saveAddress(Address address) {
        //do something
        addressMapper.insert(address);
    }
}

在上面的例子中,如果用户插入失败,不会导致saveAddress()回滚,因为这里使用的传播是REQUIRES_NEW,传播机制REQUIRES_NEW的原理是如果当前方法中没有事务,就会创建一个新的事务。如果一个事务已经存在,则当前事务将被挂起,并创建一个新事务。在当前事务完成之前,不会提交父事务。如果父事务发生异常,则不影响子事务的提交。

事务的传播机制说明如下:

  • REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。

  • SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。

  • MANDATORY 如果当前上下文中存在事务,否则抛出异常。

  • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

  • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。

  • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。

  • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

解决方案:

将事务传播策略更改为默认值REQUIRED。REQUIRED原理是如果当前有一个事务被添加到一个事务中,如果没有,则创建一个新的事务,父事务和被调用的事务在同一个事务中。即使被调用的异常被捕获,整个事务仍然会被回滚。

7. 没有被Spring管理

// @Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
}

如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

解决方案:

需要保证每个事务注解的每个Bean被Spring管理。

8. 多线程

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {

        userMapper.insertUser(userModel);
        new Thread(() -> {
             try {
                 test();
             } catch (Exception e) {
                roleService.doOtherThing();
             }
        }).start();
    }
}

@Service
public class RoleService {

    @Transactional
    public void doOtherThing() {
         try {
             int i = 1/0;
             System.out.println("保存role表数据");
         }catch (Exception e) {
            throw new RuntimeException();
        }
    }
}

我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。

这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。

我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。

解决方案:

这里就有点分布式事务的感觉了,尽量还是保证在同一个事务中处理。

总结

本文简单阐述了下Spring中事务实现的原理,同时列举了8种Spring事务失败的场景,相信很多朋友可能都遇到过, 失败的原因也有详细说明。希望大家对Spring事务有一个新的认识。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/105301.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

产业智能化创新标杆 2022年度“飞桨产业应用创新奖”颁布

随着AI进入工业大生产阶段,更多垂直行业正在与AI深度融合,更多创新实践不断涌现。日前,WAVE SUMMIT 2022深度学习开发者峰会在线上召开,百度发布了飞桨产业级深度学习平台和文心大模型的生态成果和最新进展,重磅颁发了…

MySQL高级【索引分类】

目录 1:索引分类 1.1:索引分类 1.2:聚集索引&二级索引 2:索引语法 1:索引分类 1.1:索引分类 在MySQL数据库,将索引的具体类型主要分为以下几类:主键索引、唯一索引、常规索…

(2022年12月最新)spring-core-rce漏洞复现CVE-2022-22965

1、漏洞简介 2022年3月29日,Spring框架曝出RCE 0day漏洞。已经证实由于 SerializationUtils#deserialize 基于 Java 的序列化机制,可导致远程代码执行 (RCE),使用JDK9及以上版本皆有可能受到影响。 通过该漏洞可写入webshell以及命令执行。在…

数据结构C语言版 —— 二叉树的顺序存储堆的实现

二叉树顺序结构实现(堆) 1. 堆的概念 堆在物理上是一个一维数组,在逻辑上是一颗完全二叉树满足父亲节点小于等于孩子节点的叫做小堆或者小根堆满足父亲节点大于等于孩子节点的叫做大堆或者大根堆 堆的孩子和父亲的下标关系 已知父亲(parent)的下标 左孩子(left)下…

基于昇思MindSpore实现使用胶囊网络的图像描述生成算法

基于昇思MindSpore实现使用胶囊网络的图像描述生成算法 项目链接 https://github.com/Liu-Yuanqiu/acn_mindspore 01 项目描述 1.1 图像描述生成算法 人类可以轻易的使用语言来描述所看到的场景,但是计算机却很难做到,图像描述生成任务的目的就是教…

昇思MindSpore动静结合中list和dict方法实现

01 概述 静态图和动态图是神经学习框架中的重要概念,昇思MindSpore同时支持动态图和静态图两种模式,在动态图与静态图的结合方面做了很多工作。本文以昇思MindSpore框架中图模式下list和dict的实现方式为例,介绍昇思MindSpore框架中的动静结…

C与C++如何互相调用

个人主页:董哥聊技术我是董哥,嵌入式领域新星创作者创作理念:专注分享高质量嵌入式文章,让大家读有所得!文章目录1、为什么会有差异?2、extern "C"3、C调用C正确方式4、C调用C5、总结在项目开发过…

[第十二届蓝桥杯/java/算法]C——卡片

🧑‍🎓个人介绍:大二软件生,现学JAVA、Linux、MySQL、算法 💻博客主页:渡过晚枫渡过晚枫 👓系列专栏:[编程神域 C语言],[java/初学者],[蓝桥杯] &#x1f4d6…

中外法律文献查找下载常用数据库大盘点

中外法律文献查找下载常用数据库有: 一、Westlaw(法律全文数据库) 是法律出版集团Thomson Legal and Regulator’s于1975年开发的,为国际法律专业人员提供的互联网的搜索工具。 Westlaw International其丰富的资源来自法律、法规…

图(Graph)详解 - 数据结构

文章目录:图的基本概念图的存储结构邻接矩阵邻接矩阵的实现邻接表邻接表实现图的遍历图的广度优先搜索(BFS)图的深度优先搜索(DFS)最小生成树Kruskal算法Prim算法最短路径单源最短路径 - Dijkstra算法单源最短路径 - B…

Linux学习-91-Discuz论坛安装

17.22 Discuz论坛安装 通过 Discuz! 搭建社区论坛、知识付费网站、视频直播点播站、企业网站、同城社区、小程序、APP、图片素材站,游戏交流站,电商购物站、小说阅读、博客、拼车系统、房产信息、求职招聘、婚恋交友等等绝大多数类型的网站。Discuz!自2…

《教养的迷思》

在读《穷查理宝典》时,查理芒格在有一讲,专门谈及《教养的迷思》一书,说到作者朱迪斯哈里斯。查理芒格认为哈里斯在探求真理的道路上走得很顺利,取得成功的因素之一就是她热衷于摧毁自己的观念。 朱迪斯在书的开端首先严肃地纠正了…

【案例教程】无人机生态环境监测、图像处理与GIS数据分析综合实践

【查看原文】无人机生态环境监测、图像处理与GIS数据分析综合实践技术应用 构建“天空地”一体化监测体系是新形势下生态、环境、水文、农业、林业、气象等资源环境领域的重大需求,无人机生态环境监测在一体化监测体系中扮演着极其重要的角色。通过无人机航空遥感技…

Fabric系列 - 多通道技术(Muti-channel)

可在节点,通道和联盟级别上配置。 一个Fabric网络中能够运行多个账本,每个通道间的逻辑相互隔离不受影响,如下图所示,每种颜色的线条代表一个逻辑上的通道,每个Peer节点可以加入不同的通道,每个通道都拥有…

AI编译器XLA调研

文章目录一、XLA简介二、XLA在TensorFlow中的应用2.1 XLA是什么?(tensorflow\compiler\xla)2.2 TensorFlow怎样转化为XLA (tensorflow\compiler\tf2xla)2.3 JIT(just in time) 即时编译 (tensorflow\compil…

【大数据技术Hadoop+Spark】Flume、Kafka的简介及安装(图文解释 超详细)

Flume简介 Flume是Cloudera提供的一个高可用、高可靠、分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;同时,Flume提供对数据进行简单处理,并写到各种数据接受方&…

NLP学习笔记(四) Seq2Seq基本介绍

大家好,我是半虹,这篇文章来讲序列到序列模型 (Sequence To Sequence, Seq2Seq) 本文写作思路如下: 从循环神经网络的应用场景引入,介绍循环神经网络作为编码器和解码器使用,最后是序列到序列模型 在之前的文章中&am…

微信消息收发与微信内部emoji表情转义

微信消息收发与微信内部emoji表情转义 目录 微信内部emoji表情转义与消息收发 一、概述 二、常用标准emoji表情字符、微信内部转义符、unicode对照表 1、比如 2、微信聊天窗口emoji表情字符 2.1、PC端表情选择,01~03排: 2.2、PC端表情选择&#…

华为IMC培训——通信基础

目录 一、华为设备图标 二、数据的传递 三、专业术语 四、网络设备及相关知识 五、OSI七层模型 六、TCP和UDP数据报格式 七、TCP的三次握手 八、 TCP窗口滑动机制 一、华为设备图标 AP:相当于家用路由器一般配和AC使用。 AC和AP的区别_wangzhibo_csdn的博客…

创意被盗用,这3个加水印方法,让照片刻上我们专属印记

一般我们为了保护自己的图片不被别人盗用,都会选择在图片上刻上专属印记。那么便是加水印方法,它包含两种:文字水印和图片水印。想知道怎么给图片添加水印吗?其实有很多种法子可以做到,下面就由我来分享这3个简单好用的…