首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring的事务那些场景会失效?

Spring的事务那些场景会失效?

作者头像
猫头虎
发布于 2024-04-08 03:22:40
发布于 2024-04-08 03:22:40
13200
代码可运行
举报
运行总次数:0
代码可运行

Spring的事务10种常见失效场景总结

对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了。在某些业务场景下,如果同时有多张表的写入操作,为了保证操作的原子性(要么同时成功,要么同时失败)避免数据不一致的情况,我们一般都会使用spring事务。

没错,spring事务大多数情况下,可以满足我们的业务需求。但是今天我要告诉大家的是,它有很多坑,稍不注意事务就会失效。

不信,我们一起看看。

1.错误的访问权限

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    private void add(UserModel userModel) {
        userMapper.insertUser(userModel);
    }
}

我们可以看到add方法的访问权限被定义成了private,这样会导致事务失效,spring要求被代理方法必须是public的。

AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中有个判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }

    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if (txAttr != null) {
      return txAttr;
    }

    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
      return txAttr;
    }

    if (specificMethod != method) {
      // Fallback is to look at the original method.
      txAttr = findTransactionAttribute(method);
      if (txAttr != null) {
        return txAttr;
      }
      // Last fallback is the class of the original method.
      txAttr = findTransactionAttribute(method.getDeclaringClass());
      if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
      }
    }

    return null;
  }

2.方法被定义成final的

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public final void add(UserModel userModel) {
        userMapper.insertUser(userModel);
    }
}

我们可以看到add方法被定义成了final的,这样会导致spring aop生成的代理对象不能复写该方法,而让事务失效。

3.方法内部调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
        updateStatus(userModel);
    }

    @Transactional
    public void updateStatus(UserModel userModel) {
        // doSameThing();
    }
}

我们看到在事务方法add中,直接调用事务方法updateStatus。从前面介绍的内容可以知道,updateStatus方法拥有事务的能力是因为spring aop生成代理了对象,但是这种方法直接调用了this对象的方法,所以updateStatus方法不会生成事务。

4.当前实体没有被spring管理

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
    }
}

我们可以看到UserService类没有定义@Service注解,即没有交给spring管理bean实例,所以它的add方法也不会生成事务。

5.错误的spring事务传播特性

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.NEVER)
    public void add(UserModel userModel) {
        userMapper.insertUser(userModel);
    }

}

我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。只有这三种传播特性才会创建新事务:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。

6.数据库不支持事务

msql8以前的版本数据库引擎是支持myslam和innerdb的。我以前也用过,对应查多写少的单表操作,可能会把表的数据库引擎定义成myslam,这样可以提升查询效率。但是,要千万记得一件事情,myslam只支持表锁,并且不支持事务。所以,对这类表的写入操作事务会失效。

7.自己吞掉了异常

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    public void add(UserModel userModel) {
        try {
            userMapper.insertUser(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }
}

这种情况下事务不会回滚,因为开发者自己捕获了异常,又没有抛出。事务的AOP无法捕获异常,导致即使出现了异常,事务也不会回滚。

8.抛出的异常不正确

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;
    
    @Transactional
    public void add(UserModel userModel) throws Exception {
        try {
            userMapper.insertUser(userModel);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new Exception(e);
        }
    }

}

这种情况下,开发人员自己捕获了异常,又抛出了异常:Exception,事务也不会回滚。因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),不会回滚Exception。

9.多线程调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Slf4j
@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(() -> {
            roleService.doOtherThing();
        }).start();
    }
}

@Service
public class RoleService {

    @Transactional
    public void doOtherThing() {
        System.out.println("保存role表数据");
    }
}

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

如果看过spring事务源码的朋友,可能会知道spring的事务是通过数据库连接来实现的。当前线程中保存了一个map,key是数据源,value是数据库连接。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
private static final ThreadLocal<Map<Object, Object>> resources =
      new NamedThreadLocal<>("Transactional resources");

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

10.嵌套事务多回滚了

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {
        userMapper.insertUser(userModel);
        roleService.doOtherThing();
    }
}

@Service
public class RoleService {

    @Transactional(propagation = Propagation.NESTED)
    public void doOtherThing() {
        System.out.println("保存role表数据");
    }
}

这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser里的内容,即回滚保存点。。但事实是,insertUser也回滚了。

why?

因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。

怎么样才能只回滚保存点呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Slf4j
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

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

        userMapper.insertUser(userModel);
        try {
            roleService.doOtherThing();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

}

在代码中手动把内部嵌套事务放在try/catch中,并且不继续往抛异常。

介绍到这里,你会发现spring事务的坑还是挺多的~

结语

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-04-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
tensorflow | 维度转换
学习维度转换 shape 计算维度 tf.shape(input,name = None) 案例1 a = tf.constant([i for i in range(20)],shape =[2,2,5]) with tf.Session() as sess: print (sess.run(tf.shape(a))) 结果:[2 2 5] size 计算元素个数 tf.size(input,name = None) 案例2 a = tf.constant([i for i in range
努力在北京混出人样
2018/05/14
1.2K0
TensorFlow基础:常量
例如 tf.zeros,tf.ones,tf.zeros_like,tf.diag ...
lyhue1991
2020/07/20
3640
通俗易懂!使用Excel和TF实现Transformer!
中文词表:[机、器、学、习] 英文词表[deep、machine、learning、chinese]
石晓文
2019/06/17
4860
通俗易懂!使用Excel和TF实现Transformer!
斯坦福tensorflow教程(二) tensorflow相关运算1.认识下TensorBoard2.常量op3. 数学运算数据类型
1.认识下TensorBoard TensorFlow不仅是一个软件库,而是一整套包括TensorFlow、TensorBoard、Tensor Serving在内的软件包。为了更大程度地利用TensorFlow,我们应该了解如何将它们串联起来应用。在和一部分,我们来探索下TensorBoard。 TensorBoard是一个图(graph)可视化软件,在(安装TensorFlow的时候会默认安装)。下面是谷歌的介绍: The computations you'll use TensorFlow for
致Great
2018/06/14
8100
基于TensorFlow的深度学习系列教程 2——常量Constant
在tensorflow中,数据分为几种类型: 常量Constant、变量Variable、占位符Placeholder。其中:
用户1154259
2019/07/02
7650
tensorflow编程: Constants, Sequences, and Random Values
  注意: start 和 stop 参数都必须是 浮点型;     取值范围也包括了 stop; tf.lin_space 等同于 tf.linspace。
JNingWei
2018/09/28
4510
TensorFlow2 一小时学会基本操作 2
https://iamarookie.blog.csdn.net/article/details/117651502
润森
2022/09/22
3520
TensorFlow2 一小时学会基本操作 2
tensorflow编程: Running Graphs
  A class for running TensorFlow operations.   这是一个类,执行 tensorflow 中的 op 。它里面定义了 run()、extend()、close()、__init__() 等方法。
JNingWei
2018/09/28
5530
使用一维数据构造简单卷积神经网络
神经网络对于一维数据非常重要,时序数据集、信号处理数据集和一些文本嵌入数据集都是一维数据,会频繁的使用到神经网络。我们在此利用一组一维数据构造卷积层-最大池化层-全连接层的卷积神经网络。希望给大家使用CNN处理一维数据一些帮助。
演化计算与人工智能
2020/08/14
1.7K0
教程 | 维度、广播操作与可视化:如何高效使用TensorFlow
选自GitHub 机器之心编译 参与:Nurhachu Null、李泽南 本文从 Tensorflow 基础、理解静态维度和动态维度、广播操作(Broadingcast 的好处和坏处)、使用 Python 操作的原型内核和先进的可视化等几个方面详细梳理如何高效使用TensorFlow。 Tensorflow 基础 TensorFlow 和其他诸如 numpy 之类的数学计算库的根本区别在于:在 TensorFlow 中,运算操作是符号化的。这是一个强大的思想,它能够让 TensorFlow 做任何事情(例如
机器之心
2018/05/09
1.5K0
Tensorflow技术点整理(二)
这里跟PyTorch不同的是序号定义的不同,PyTorch是上下定义位置,而Tensorflow是左右定义位置。
算法之名
2022/03/24
4670
Tensorflow技术点整理(二)
tensorflow运行mnist的一些
最近在tensorflow环境下用CNN来实现mnist,里面设计了一些tensorflow的函数,在之后的学习中肯定会经常使用,因此记录整理下来。
py3study
2020/01/20
5330
TensorFlow2.0(1):基本数据结构——张量
TensorFlow2.0版本已经发布,虽然不是正式版,但预览版都发布了,正式版还会远吗?相比于1.X,2.0版的TensorFlow修改的不是一点半点,这些修改极大的弥补了1.X版本的反人类设计,提升了框架的整体易用性,绝对好评!
Ai学习的老章
2019/12/23
1.6K0
使用二维数据构造简单卷积神经网络
使用二维数据构造简单卷积神经网络 图像和一些时序数据集都可以用二维数据的形式表现,我们此次使用随机分布的二位数据构造一个简单的CNN—网络卷积-最大池化-全连接 参考代码 # Implementing Different Layers # --------------------------------------- # # We will illustrate how to use different types # of layers in TensorFlow # # The layers of i
演化计算与人工智能
2020/08/14
8680
张量拼接_调整维度_切片
tf.concat的作用主要是将向量按指定维连起来,其余维度不变;而1.0版本以后,函数的用法变成:
狼啸风云
2019/07/01
1.3K0
TF入门02-TensorFlow Ops
我们首先介绍一下TensorBoard的使用,然后介绍TensorFlow的基本ops,之后介绍张量的数据类型,最后介绍一下如何将自己的输入导入模型。
公众号-不为谁写的歌
2020/07/23
1.7K0
[阿里DIN]从模型源码梳理TensorFlow的形状相关操作
本文基于阿里推荐 DIN 和 DIEN 代码,梳理了下深度学习一些概念,以及TensorFlow中的相关实现。
罗西的思考
2020/11/24
9180
【tensorflow2.0】张量的结构操作
张量数学运算主要有:标量运算,向量运算,矩阵运算。另外我们会介绍张量运算的广播机制。
西西嘛呦
2020/08/26
2.3K0
[阿里DIN] 从模型源码梳理TensorFlow的乘法相关概念
本文基于阿里推荐 DIN 和 DIEN 代码,梳理了下深度学习一些概念,以及TensorFlow中的相关实现。
罗西的思考
2020/11/11
1.8K0
推荐阅读
相关推荐
tensorflow | 维度转换
更多 >
交个朋友
加入HAI高性能应用服务器交流群
探索HAI应用新境界 共享实践心得
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档