前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring事务为什么会失效?

Spring事务为什么会失效?

作者头像
Java识堂
发布于 2022-05-19 04:34:00
发布于 2022-05-19 04:34:00
60100
代码可运行
举报
文章被收录于专栏:Java识堂Java识堂
运行总次数:0
代码可运行

不用Spring管理事务?

如果对AOP的实现不太熟悉的话可以看我之前的文章,或者到我网站www.javashitang.com上查看系列文章

2w字搞懂Spring AOP的前世今生

让我们先来看一下不用spring管理事务时,各种框架是如何管理事务的

使用JDBC来管理事务

使用Hibernate来管理事务

业务逻辑和事务代码是耦合到一块的,并且和框架的具体api绑定了。当我们换一种框架来实现时,里面对事务控制的代码就要推倒重写,并不一定能保证替换后的api和之前的api有相同的行为。

「统一的事务抽象」

基于这些问题,Spring抽象了一些事务相关的顶层接口。无论是全局事务还是本地事务,JTA,JDBC还是Hibernate,Spring都使用统一的编程模型。使得应用程序可以很容易的在全局事务与本地事务,或者不同事物框架之间进行切换。

「下图为Spring事物抽象的核心类」

常用api

接口

PlatformTransactionManager

对事务进行管理

TransactionDefinition

定义事务的相关属性,例如隔离级别,传播行为

TransactionStatus

保存事务状态

针对不同的数据访问技术,使用不用的PlatformTransactionManager类即可

数据访问技术

PlatformTransactionManager实现类

JDBC/Mybatis

DataSourceTransactionManager

Hibernate

HibernateTransactionManager

Jpa

JpaTransactionManager

Jms

JmsTransactionManager

编程式事务管理

当我们使用Spring的事务时,可以使用编程式事务或者声明式事务。

当使用编程式事务的时候,可以直接使用事务的顶层接口,也可以使用模版类TransactionTemplate

使用PlatformTransactionManager

使用TransactionTemplate

当我们直接使用PlatformTransactionManager来管理事务时,有很多模版代码。例如业务代码正常执行,提交事务,否则回滚事务。我们可以把这部分模版代码封装成一个模版类,这样使用起来就很方便了,如下所示

如下图所示,TransactionTemplate#execute方法就是一个典型的模版方法

我们可以传入如下2个接口的实现类来执行业务逻辑,TransactionCallback(需要返回执行结果)或TransactionCallbackWithoutResult(不需要返回结果)

声明式事务管理

为了让使用更加简洁,Spring直接把事务代码的执行放到切面中了,我们只需要在业务代码方法上加上一个@Transactional注解即可,这种方式我们最常用哈

使用@Transactional注解

此时事务相关的定义我们就可以通过@Transactional注解来设置了

属性名

类型

描述

默认值

value(和transactionManager互为别名)

String

当在配置文件中有多个PlatformTransactionManager ,用该属性指定选择哪个事务管理器

空字符串""

propagation

枚举:Propagation

事务的传播行为

REQUIRED

isolation

枚举:Isolation

事务的隔离度

DEFAULT

timeout

int

事务的超时时间。如果超过该时间限制但事务还没有完成,则自动回滚事务

-1

readOnly

boolean

指定事务是否为只读事务

false

rollbackFor

Class[]

需要回滚的异常

空数组{}

rollbackForClassName

String[]

需要回滚的异常类名

空数组{}

noRollbackFor

Class[]

不需要回滚的异常

空数组{}

noRollbackForClassName

String[]

不需要回滚的异常类名

空数组{}

在这里插入图片描述

源码解析

我们需要在配置类上加上@EnableTransactionManagement注解,来开启spring事务管理功能

「TransactionManagementConfigurationSelector#selectImports」

容器中注入AutoProxyRegistrar和ProxyTransactionManagementConfiguration这2个类,那这2个类有啥作用呢?(源码太多了,我就不贴代码一步一步分析了,主要是理清思路)

在这里插入图片描述

AutoProxyRegistrar主要就是往容器中注入一个类InfrastructureAdvisorAutoProxyCreator,这个类有什么作用呢?

「看一下继承关系,原来是继承自AbstractAutoProxyCreator,用来实现自动代理没跑了!」

BeanFactoryTransactionAttributeSourceAdvisor主要就是往容器中注入了一个Advisor类,用来保存Pointcut和Advice

对应的Pointcut为TransactionAttributeSourcePointcut的实现类,是一个匿名内部类,即筛选的逻辑是通过TransactionAttributeSourcePointcut类来实现的

BeanFactoryTransactionAttributeSourceAdvisor

对应的Advice的实现类为TransactionInterceptor,即针对事务增强的逻辑都在这个类中。

筛选的逻辑我们就先不分析了,后面会再简单提一下

我们来看针对事务增强的逻辑,当执行被@Transactional标记的方法时,会调用到如下方法(TransactionInterceptor#invoke有点类似我们的@Around)

TransactionInterceptor#invoke

TransactionAspectSupport#invokeWithinTransaction

我挑出这个方法比较重要的几个部分来分析吧(上图圈出来的部分)

  1. 如果需要的话开启事务(和传播属性相关,我们后面会提到)
  2. 执行业务逻辑
  3. 如果发生异常则会滚事务
  4. 如果正常执行则提交事务

「所以当发生异常需要会滚的时候,我们一定不要自己把异常try catch掉,不然事务会正常提交」

TransactionAspectSupport#createTransactionIfNecessary

当开启事务的时候,可以看到各种传播属性的行为(即@Transactional方法调用@Transactional方法会发生什么?)

AbstractPlatformTransactionManager#getTransaction

Spring事务的传播行为在Propagation枚举类中定义了如下几种选择

「支持当前事务」

  • REQUIRED :如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务
  • SUPPORTS:如果当前存在事务,则加入该事务 。如果当前没有事务, 则以非事务的方式继续运行
  • MANDATORY :如果当前存在事务,则加入该事务 。如果当前没有事务,则抛出异常

「不支持当前事务」

  • REQUIRES_NEW :如果当前存在事务,则把当前事务挂起,创建一个新事务
  • NOT_SUPPORTED :如果当前存在事务,则把当前事务挂起,以非事务方式运行,
  • NEVER :如果当前存在事务,则抛出异常

「其他情况」

  • NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来执行 。如果当前没有事务,则该取值等价于REQUIRED

以NESTED启动的事务内嵌于外部事务中 (如果存在外部事务的话),此时内嵌事务并不是一个独立的事务,它依赖于外部事务。只有通过外部事务的提交,才能引起内部事务的提交,嵌套的子事务不能单独提交

事务失效的场景有哪些?

因为我们经常使用声明式事务,如果一步消息就会导致事务失效,所以我们就从源码角度来盘一下事务为什么失效

异常被你try catch了

首先就是我们上面刚提到的,「异常被你try catch了」。因为声明式事物是通过目标方法是否抛出异常来决定是提交事物还是会滚事物的

自调用

当自调用时,方法执行不会经过代理对象,所以会导致事务失效

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 事务失效
@Service
public class UserServiceV2Impl implements UserService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addUser(String name, String location) {
        doAdd(name);
    }

    @Transactional
    public void doAdd(String name) {
        String sql = "insert into user (`name`) values (?)";
        jdbcTemplate.update(sql, new Object[]{name});
        throw new RuntimeException("保存用户失败");
    }
}

我们可以通过如下三种方式来解决自调用失效的场景

「1.@Autowired注入代理对象,然后调用方法」

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

    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private UserService userService;

    @Override
    public void addUser(String name, String location) {
        userService.doAdd(name);
    }

    @Override
    @Transactional
    public void doAdd(String name) {
        String sql = "insert into user (`name`) values (?)";
        jdbcTemplate.update(sql, new Object[]{name});
        throw new RuntimeException("保存用户失败");
    }
}

「2.从ApplicationContext获取代理对象,然后调用方法」

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

    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private ApplicationContext applicationContext;

    @Override
    public void addUser(String name, String location) {
        UserService userService = applicationContext.getBean(UserService.class);
        userService.doAdd(name);
    }

    @Override
    @Transactional
    public void doAdd(String name) {
        String sql = "insert into user (`name`) values (?)";
        jdbcTemplate.update(sql, new Object[]{name});
        throw new RuntimeException("保存用户失败");
    }
}

「3.进行如下设置@EnableAspectJAutoProxy(exposeProxy = true),从AopContext中获取代理对象,然后调用方法」

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

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void addUser(String name, String location) {
        UserService userService = (UserService) AopContext.currentProxy();
        userService.doAdd(name);
    }

    @Override
    @Transactional
    public void doAdd(String name) {
        String sql = "insert into user (`name`) values (?)";
        jdbcTemplate.update(sql, new Object[]{name});
        throw new RuntimeException("保存用户失败");
    }
}

非public方法导致事务失效

我们先来猜一下为什么非public方法会导致事务失效?

「难道是因为非public方法不会生成代理对象?」

我们给一个非public方法加上@Transactional,debug到如下代码看一下是否会生成代理对象

AbstractAutoProxyCreator#wrapIfNecessary

「结论是不会生成代理对象,那为什么不会生成代理对象呢?」

应该就是不符合Pointcut的要求了呗,我们在前面已经提到了事务对应的Pointcut为TransactionAttributeSourcePointcut

TransactionAttributeSourcePointcut#matches

matches方法返回false,为什么会返回false呢?

一直debug发现是如下代码导致的

AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

即public方法能正常生成代理对象,而非public方法因为不符合Pointcut的要求,根本就不会生成代理对象

异常类型不正确,默认只支持RuntimeException和Error,不支持检查异常

「为什么不支持检查异常呢?」

拿出我们上面分析过的代码

当执行业务逻辑发生异常的时候,会调用到TransactionAspectSupport#completeTransactionAfterThrowing方法

可以看到对异常类型做了判断,根据返回的结果来决定是否会滚事务,会调用到如下方法进行判断

RuleBasedTransactionAttribute#rollbackOn

如果用户指定了回滚的异常类型,则根据用户指定的规则来判断,否则用默认的规则

DefaultTransactionAttribute

默认的规则为只支持RuntimeException和Error

我们可以通过@Transactional属性指定回滚的类型,一般为Exception即可

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Transactional(rollbackFor = Exception.class)
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java识堂 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
Windows server——部署DNS服务(3)
本章将会继续讲解《Windows server——部署DNS服务》前期回顾Windows server——部署DNS服务,Windows server——部署DNS服务(2)
网络豆
2023/10/15
1.2K0
Windows server——部署DNS服务(3)
Windows server——部署DNS服务(2)
本章将会讲解Windows server 配置DNS服务。前期回顾:Windows server——部署DNS服务
网络豆
2023/10/15
1.6K0
Windows server——部署DNS服务(2)
实验十(课程资源)-DNS服务器配置与管理[通俗易懂]
课程实验报告: 一、实验目的: 1、了解DNS工作原理及域名解析过程 2、掌握DNS服务器的安装、配置与管理 二、实验目的: 在windows2003上搭建DNS服务器,并进行相关配置与功能测试 三、实验原理: DNS:是域名系统(Domain Name System)的缩写,指在Internet中使用的分配名字和地址的机制。域名系统允许用户使用友好的名字而不是难以记忆的数字——IP地址来访问Internet上的主机。 域名解析:就是将用户提出的名字变换成网络地址的方法和过程,从概念上讲,域名解析是一个自上而下的过程。 域名空间树形结构
全栈程序员站长
2022/09/05
3.1K0
Windows server——部署DHCP服务(2)
本章将会讲解如何配置DHCP服务,安装DHCP服务,配置DHCP客户端,维护DHCP,监视DHCP
网络豆
2023/10/15
4K0
Windows server——部署DHCP服务(2)
windows2003 DNS服务器的配置步驟
 目前很多企业事业单位都建立了单位内部的局域网,网络内部都配备相关的服务器(如web、ftp等服务器)。内部网络的用户都希望所有的服务器都用域名来访问,网络管理员可以采用在内部搭建DNS服务器的方式来实现
习惯说一说
2019/04/15
7.7K0
DNS服务器搭建(Windows版本)
DNS服务器搭建(使用Windows server 2016环境演示) 本实验使用以虚拟机做演示。在VMware Workstation软件上安装一台Windows Server 2016的服务器,搭建DNS服务器。 Windows Server 2016服务器安装过程省略。 1、按Windows键,点击服务器管理器。 2、点击“添加角色和功能”。进行DNS配置。 3、直接点击“下一步”。 4、默认选择,点击“下一步”。 5、默认选择,点击“下一步”。 6、勾选“DNS服务器”。
宝耶需努力
2022/12/13
15K0
DNS服务器搭建(Windows版本)
架设邮件服务器-windows 2003 POP3服务,SMTP服务收发邮件「建议收藏」
1.默认安装的系统是没有安装POP3组件,SMTP组件,搞个盘过来,或从网上下载一个i386(下载地址:http://down.spdns.com/i386.rar ).
全栈程序员站长
2022/07/22
6.6K0
架设邮件服务器-windows 2003 POP3服务,SMTP服务收发邮件「建议收藏」
如何在Windows Server2012搭建DNS服务并配置泛域名解析
前面Fayson介绍过《如何在Windows Server2008搭建DNS服务并配置泛域名解析》和《如何利用Dnsmasq构建小型集群的本地DNS服务器》,这篇文章主要描述Windows Server2012服务器搭建DNS服务器及配置泛域名解析。也是为后面Fayson接下来要介绍的CDSW1.2安装做准备,属于前置条件之一。
Fayson
2018/03/29
13.2K0
如何在Windows Server2012搭建DNS服务并配置泛域名解析
域渗透基础之环境搭建(单域到组件域林)
转发:https://www.e-learn.cn/content/qita/2484245
墨文
2020/02/28
1.8K0
域渗透基础之环境搭建(单域到组件域林)
Windows server 2012之
关于辅助区域的相关创建与功能我将通过创建辅助服务器来进行详细讲解,下面就开始进行相关的准备工作了。首先要创建两台虚拟服务器,进行基本的配置(由于之前进行过相关知识的讲解,在这里就不再细讲了)例:计算机名:server01 IP:192.168.1.101
py3study
2020/01/07
1.6K0
基于Windows Server 2012 R2的DNS服务器搭建详解
在工作中,我们可能被要求在内网中建立一个DNS服务器,来让内网中的主机使用,通过对DNS服务器的解析更改,可以让内网中的主机无法访问某一个网址,亦或是访问内部网址,总之,建立企业内部的DNS服务器是及其重要的。接下来开始讲解如何基于Windows Server 2012 R2系统来建立DNS服务器。
小狐狸说事
2022/11/17
3.5K0
Windows Server服务器上DNS服务器配置方法[通俗易懂]
本篇经验将和大家介绍Windows Server服务器上DNS服务器配置的步骤,希望对大家的工作和学习有所帮助!
全栈程序员站长
2022/09/05
10.2K0
Windows server——部署web服务
 本章将介绍互联网上常用的服务——WWW服务。利用IIS 10部署Web站点及配置虚掠目录、虚拟主机等内容。
网络豆
2023/10/15
1.1K0
Windows server——部署web服务
Active Directory与域服务,介绍,安装
Active Directory是一种由微软开发的网络服务,用于管理用户、计算机和其他网络资源,是企业网络的核心目录服务。它提供了一种集中管理和控制企业网络资源的方法,包括用户、计算机、应用程序、安全策略等。通过Active Directory,管理员可以轻松地集中管理和控制网络上的所有资源,确保网络的高可用性、安全性和一致性。
网络豆
2023/10/17
1.5K0
Active Directory与域服务,介绍,安装
域的搭建和配置
在域架构中,最核心的就是DC(Domain Control,域控制器)。域控制器可分为三种:域控制器、额外域控制器和只读域控制器(RODC)。创建域环境首先要创建DC,DC创建完成后,把所有需要加入域的客户端加入到DC,这样就形成了域环境。网络中创建的第一台域控制器,默认为林根域控制器,也是全局编录服务器,FSMO操作主机角色也默认安装到第一台域控制器。 一个域环境中可以有多台域控制器,也可以只有一台域控制器。当有多台域控制器的时候,每一台域控制器的地位几乎是平等的,他们各自存储着一份相同的活动目录数据库。当你在任何一台域控制器内添加一个用户账号或其他信息后,此信息默认会同步到其他域控制器的活动目录数据库中。多个域控制器的好处在于当有域控制器出现故障了时,仍然能够由其他域控制器来提供服务。
谢公子
2023/09/01
3.2K0
域的搭建和配置
Windows server 2012 R2 部署WSUS补丁服务[通俗易懂]
对于多达 13000 个客户端的服务器,建议使用以下硬件: * 4 Core E5-2609 2.1GHz 的处理器 * 8 GB 的 RAM
全栈程序员站长
2022/09/06
4.4K0
Windows server 2012 R2 部署WSUS补丁服务[通俗易懂]
Windows Server 2016部署WDS服务图文详解
Windows Server 2016中的WDS服务,全称Windows 部署服务(Windows Deployment Services)主要用于大中型网络中的计算机操作系统的批量化部署。可以通过WDS服务可以管理映像及无人参与安装脚本,并提供人工参与安装和无人参与安装的方式,大大提高了部署操作系统的速度。
星哥玩云
2022/07/28
4.5K0
Windows Server 2016部署WDS服务图文详解
Windows Server 2016 搭建IIS(Web)服务
今天跟大家简单介绍一下如何在Windows Server 2016服务器上搭建IIS(Web)服务。 web服务即www服务(万维网服务),是指在网上发布,并可以通过浏览器观看图形化页面的服务。 下面我们简单介绍一下如何搭建web服务。 1.首先确定自己本机的IP地址,
星哥玩云
2022/07/28
4.4K1
Windows Server 2016 搭建IIS(Web)服务
轻松架设Windows 2003用户隔离FTP站点的注意事项
  架设FTP站点似乎已经不是什么困难的事情了,我们不需要借助任何外来工具的帮忙,只需要使用Windows服务器系统自带的IIS功能,就能轻易地架设一台FTP站点了。不过,用这种方法架设的FTP站点不
会长君
2023/04/26
1.7K0
Windows Server 2003 DNS服务器搭建
在如今卷王层出不穷的社会,哪怕你只是一个技术小白也要学习一定的技术,这一系列将带领小白搭建五大服务器:FTP服务器,Web服务器,DHCP服务器,DNS服务器,邮件服务器带你体验真实环境中的服务器的作用,让你清晰了解到你平常上网中访问的网站等等是如何而来,我将用我的知识让你领悟服务器的美妙.
可惜已不在
2024/10/17
5580
Windows Server 2003 DNS服务器搭建
推荐阅读
相关推荐
Windows server——部署DNS服务(3)
更多 >
LV.1
这个人很懒,什么都没有留下~
目录
  • 不用Spring管理事务?
  • 编程式事务管理
    • 使用PlatformTransactionManager
    • 使用TransactionTemplate
  • 声明式事务管理
    • 使用@Transactional注解
  • 源码解析
  • 事务失效的场景有哪些?
    • 异常被你try catch了
    • 自调用
    • 非public方法导致事务失效
    • 异常类型不正确,默认只支持RuntimeException和Error,不支持检查异常
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档