spring事务-说说Propagation及其实现原理
前言
spring目前已是java开发的一个事实标准,这得益于它的便利、功能齐全、容易上手等特性。在开发过程当中,操作DB是非常常见的操作,而涉及到db,就会涉及到事务。事务在平时的开发过程当中,就算没有注意到,程序正常执行不会有副作用,但如果出现了异常,而又没有处理好事务的话,可能就会出现意想不到的结果。spring在事务方面进行了各种操作的封装,特别是声明式事务的出现,让开发变得更加的舒心。spring对事务进行了扩展,支持定义多种传播属性,这也是本篇要说明的重点。
事务是什么
非严格的讲,一个事务是多个操作的简称,这些操作要么全部生效,要么一个都不生效(相当于没有执行过),一个通用的操作流程简化如下:
try{
Connection conn = getConnection();
// 执行一些数据库操作
}catch(Exception e){
conn.rollback();
}finally{
conn.close();
}
从以上代码可以看出一些问题:
- 太多无用的固定代码
- 如果一个请求需要调用多个服务接口,难以更精细的控制事务
- 跨多种底层数据层,如jdbc,mybatis,hibernate,jta,难以统一编码方式。
spring提供了声明式事务,使得我们不用关注底层的具体实现,屏蔽了多种不同的底层实现细节,为了支持多种复杂业务对事务的精细控制,spring提供了事务的传播属性,结合声明式事务,成就了一大事务利器。
spring事务传播属性示例分析
在TransactionDefinition类中,spring提供了6种传播属性,接下来分别用简单示例来说明。
温馨提醒:下文提到的加入当前事务,指的是底层使用同一个Connection,但是事务状态对象是可以重新创建的,并不影响。文章提到的当前只存在一个事务,表示的是共用底层的一个Connection,而不在乎创建了多少个事务状态对象(TransactionStatus)。
1、PROPAGATION_REQUIRED
说明: 如果当前已经存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
看一个小例子,代码如下:
@Transactional
public void service(){
serviceA();
serviceB();
} @Transactional
serviceA();
@Transactional
serviceB();
serviceA 和 serviceB 都声明了事务,默认情况下,propagation=PROPAGATION_REQUIRED,整个service调用过程中,只存在一个共享的事务,当有任何异常发生的时候,所有操作回滚。
2、PROPAGATION_SUPPORTS
说明:如果当前已经存在事务,那么加入该事务,否则创建一个所谓的空事务(可以认为无事务执行)。
看一个小例子,代码如下:
public void service(){
serviceA();
throw new RunTimeException();
} @Transactional(propagation=Propagation.SUPPORTS)
serviceA();
serviceA执行时当前没有事务,所以service中抛出的异常不会导致 serviceA回滚。
再看一个小例子,代码如下:
public void service(){
serviceA();
} @Transactional(propagation=Propagation.SUPPORTS)
serviceA(){
do sql 1
1/0;
do sql 2
}
由于serviceA运行时没有事务,这时候,如果底层数据源defaultAutoCommit=true,那么sql1是生效的,如果defaultAutoCommit=false,那么sql1无效,如果service有@Transactional标签,serviceA共用service的事务(不再依赖defaultAutoCommit),此时,serviceA全部被回滚。
3、 PROPAGATION_MANDATORY
说明:当前必须存在一个事务,否则抛出异常。
看一个小例子,代码如下:
public void service(){
serviceB();
serviceA();
}
serviceB(){
do sql
}
@Transactional(propagation=Propagation.MANDATORY)
serviceA(){
do sql
}
这种情况执行 service会抛出异常,如果defaultAutoCommit=true,则serviceB是不会回滚的,defaultAutoCommit=false,则serviceB执行无效。
4、PROPAGATN_REQUIRES_NEW
说明:如果当前存在事务,先把当前事务相关内容封装到一个实体,然后重新创建一个新事务,接受这个实体为参数,用于事务的恢复。更直白的说法就是暂停当前事务(当前无事务则不需要),创建一个新事务。 针对这种情况,两个事务没有依赖关系,可以实现新事务回滚了,但外部事务继续执行。
看一个小例子,代码如下:
@Transactional
public void service(){
serviceB();
try{
serviceA();
}catch(Exception e){
}
}
serviceB(){
do sql
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
serviceA(){
do sql 1
1/0;
do sql 2
}
当调用service接口时,由于serviceA使用的是REQUIRES_NEW,它会创建一个新的事务,但由于serviceA抛出了运行时异常,导致serviceA整个被回滚了,而在service方法中,捕获了异常,所以serviceB是正常提交的。 注意,service中的try … catch 代码是必须的,否则service也会抛出异常,导致serviceB也被回滚。
5、Propagation.NOT_SUPPORTED
说明:如果当前存在事务,挂起当前事务,然后新的方法在没有事务的环境中执行,没有spring事务的环境下,sql的提交完全依赖于 defaultAutoCommit属性值 。
看一个小例子,代码如下:
@Transactional
public void service(){
serviceB();
serviceA();
}
serviceB(){
do sql
}
@Transactional(propagation=Propagation.NOT_SUPPORTED)
serviceA(){
do sql 1
1/0;
do sql 2
}
当调用service方法的时候,执行到serviceA方法中的1/0代码时,抛出了异常,由于serviceA处于无事务环境下,所以 sql1是否生效取决于defaultAutoCommit的值,当defaultAutoCommit=true时,sql1是生效的,但是service由于抛出了异常,所以serviceB会被回滚。
6、 PROPAGATION_NEVER
说明: 如果当前存在事务,则抛出异常,否则在无事务环境上执行代码。
看一个小例子,代码如下:
public void service(){
serviceB();
serviceA();
}
serviceB(){
do sql
}
@Transactional(propagation=Propagation.NEVER)
serviceA(){
do sql 1
1/0;
do sql 2
}
上面的示例调用service后,若defaultAutoCommit=true,则serviceB方法及serviceA中的sql1都会生效。
7、 PROPAGATION_NESTED
说明: 如果当前存在事务,则使用 SavePoint 技术把当前事务状态进行保存,然后底层共用一个连接,当NESTED内部出错的时候,自行回滚到 SavePoint这个状态,只要外部捕获到了异常,就可以继续进行外部的事务提交,而不会受到内嵌业务的干扰,但是,如果外部事务抛出了异常,整个大事务都会回滚。
注意: spring配置事务管理器要主动指定 nestedTransactionAllowed=true,如下所示:
<bean id="dataTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataDataSource" />
<property name="nestedTransactionAllowed" value="true" />
</bean>
看一个小例子,代码如下:
@Transactional
public void service(){
serviceA();
try{
serviceB();
}catch(Exception e){
}
}
serviceA(){
do sql
}
@Transactional(propagation=Propagation.NESTED)
serviceB(){
do sql1
1/0;
do sql2
}
serviceB是一个内嵌的业务,内部抛出了运行时异常,所以serviceB整个被回滚了,由于service捕获了异常,所以serviceA是可以正常提交的。
再来看一个例子,代码如下:
@Transactional
public void service(){
serviceA();
serviceB();
1/0;
}
@Transactional(propagation=Propagation.NESTED)
serviceA(){
do sql
}
serviceB(){
do sql
}
由于service抛出了异常,所以会导致整个service方法被回滚。(这就是跟PROPAGATION_REQUIRES_NEW不一样的地方了,NESTED方式下的内嵌业务会受到外部事务的异常而回滚。)
实现浅析
前面举例说明了spring事务提供的几种传播属性,用于满足多种不同的业务需求,大家可以依业务而定。接着我们再来看看spring实现这些传播属性最重要的技术依赖是什么。本小节列举 PROPAGATION_REQUIRES_NEW 和 Propagation.NESTED 分别进行简要说明。
1、 PROPAGATION_REQUIRES_NEW 实现原理
如下的代码调用:
@Transactional
public void service(){
serviceB();
try{
serviceA();
}catch(Exception e){
}
} @Transactional(propagation=Propagation.REQUIRES_NEW)
serviceA(){
do sql 1
1/0;
do sql 2
}
serviceB(){
do sql
}
执行原理图如下:
a. 创建事务状态对象,获取一个新的连接,重置连接的 autoCommit,fetchSize,timeout等属性
b. 把连接绑定到ThreadLocal变量
c. 挂起当前事务,把当前事务状态对象,连接等信息封装成一SuspendedResources对象,可用于恢复
d. 创建新的事务状态对象,重新获取新的连接,重置新连接的 autoCommit,fetchSize,timeout等属性,同时,保存SuspendedResources对象,用于事务的恢复,把新的连接绑定到ThreadLocal变量(覆盖操作)
e. 捕获到异常,回滚ThreadLocal中的连接,恢复连接参数,关闭连接,恢复SuspendedResources
f. 提交ThreadLocal变量中的连接(导致serviceB被提交),还原连接参数,关闭连接,连接归还数据源
所以程序执行的结果就是 serviceA被回滚了,serviceB成功提交了。
2、 PROPAGATION_NESTED 实现原理
如下的代码调用:
@Transactional
public void service(){
serviceA();
try{
serviceB();
}catch(Exception e){
}
}
serviceA(){
do sql
}
@Transactional(propagation=Propagation.NESTED)
serviceB(){
do sql1
1/0;
do sql2
}
执行原理图如下:
a. 创建事务状态对象,获取一个新的连接,重置连接的 autoCommit,fetchSize,timeout等属性
b. 把连接绑定到ThreadLocal变量
c. 标记使用当前事务状态对象,获取ThreadLocal连接对象,保存当前连接的SavePoint,用于异常恢复,此时的SavePoint就是执行完serviceA后的状态
d. 捕获到异常,使用c中的SavePoint进行事务回滚,也就是把状态回滚到执行serviceA后的状态,serviceB方法所有执行不生效
e. 获取ThreadLocal中的连接对象,提交事务,恢复连接属性,关闭连接
其它
spring在底层数据源的基础上,利用 ThreadLocal,SavePoint等技术点实现了多种事务传播属性,便于实现各种复杂的业务。只有理解了传播属性的原理才能更好的驾驭spring事务。Spring回滚事务依赖于对异常的捕获,默认情况下,只有抛出RuntimeException和Error才会回滚事务,当然可以进行配置,更多信息可以查看 @Transactional 这个注解。
原文链接: https://blog.csdn.net/yanyan19880509/article/details/53041564
spring事务-说说Propagation及其实现原理的更多相关文章
- Spring事务管理机制的实现原理-动态代理
之前在做项目中遇到spring无法进行事务代理问题,最后发现是因为没有写接口,原因当时明白了,看到这篇文章写的清楚些,转过来 我们先来分析一下Spring事务管理机制的实现原理.由于Spring内置A ...
- Spring事务用法示例与实现原理
关于Java中的事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比 ...
- 手写spring事务框架, 揭秘AOP实现原理。
AOP面向切面编程:主要是通过切面类来提高代码的复用,降低业务代码的耦合性,从而提高开发效率.主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等. AOP实现原理:aop是通过cgli ...
- 2018.11.12 Spring事务的实现和原理
Spring事务用法示例与实现原理 关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性.一致性.隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而 ...
- Spring事务事件监控
前面我们讲到了Spring在进行事务逻辑织入的时候,无论是事务开始,提交或者回滚,都会触发相应的事务事件.本文首先会使用实例进行讲解Spring事务事件是如何使用的,然后会讲解这种使用方式的实现原理. ...
- Spring事务实现分析
一.Spring声明式事务用法 1.在spring配置文件中配置事务管理器 <bean id="baseDataSource" class="com.alibaba ...
- 关于Spring事务的原理,以及在事务内开启线程,连接池耗尽问题.
主要以结果为导向解释Spring 事务原理,连接池的消耗,以及事务内开启事务线程要注意的问题. Spring 事务原理这里不多说,网上一搜一大堆,也就是基于AOP配合ThreadLocal实现. 这里 ...
- Spring事务原理一探
概括来讲,事务是一个由有限操作集合组成的逻辑单元.事务操作包含两个目的,数据 一致以及操作隔离.数据一致是指事务提交时保证事务内的所有操作都成功完成,并且 更改永久生效:事务回滚时,保证能够恢复到事务 ...
- 一文带你深入浅出Spring 事务原理
Spring事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的.对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行: 获 ...
随机推荐
- ES6 随记(3.1)-- 字符串的拓展
上一章请见: 1. ES6 随记(1)-- let 与 const 2. ES6 随记(2)-- 解构赋值 4. 拓展 a. 字符串的拓展 有些字符需要 4 个字节储存,比如 \uD83D\uDE80 ...
- 快乐学习 Ionic Framework+PhoneGap 手册1-2{介绍Header,Content,Footer的使用}
*先运行第一个简单的APP,介绍Header,Content,Footer的使用 {2.1}运行一个简单的APP,效果如下 {2.2}Header代码 <ion-header-bar class ...
- Go panic recover
panic 1. 停止当前函数执行 2. 一直向上返回,执行每一层的defer 3. 如果没有遇到recover, 程序退出 recover 1. 仅在defer调用中使用 2. 获取panic的值 ...
- Windows Server 2008 架设 Web 服务器教程(图文详解)
Windows Server 2008 架设 Web 服务器教程(图文详解) 一.安装 IIS 7.0 : 虽然 Windows Server 2008 内置了I IS 7.0,但是默认情况下并没有安 ...
- TCP的三个接收队列
之前对于TCP接收过程中的三个队列的关系之前没搞清楚. 这几天,在同事邱的帮助下,终于把关系理清了,故特此做个笔记. 一.在软中断中加入数据包 tcp_v4_rcv()函数是tcp层收包的入口. in ...
- setWindowFlags的使用
setWindowFlags的使用 setWindowFlags(Qt::FramelessWindowHint); //隐藏标题栏 setWindowFlags(Qt::WindowCloseBu ...
- Linux ls命令参数详解
-a -- 全部(all).列举目录中的全部文件,包括隐藏文件(.filename).位于这个列表的起首处的 .. 和 . 依次是指父目录和你的当前目录. -l -- 长(long).列举目 ...
- DataGrid合并单元格(wpf)
在网上搜索wpf合并单元格,一直没搜索到,没办法,只能自己想办法搞定了.其实就是DataGrid套DataGrid,为了方便支持Column拖动,在合并的DataGridColumn那一列的Heade ...
- Memcached之缓存雪崩,缓存穿透,缓存预热,缓存算法
缓存雪崩 缓存雪崩可能是因为数据未加载到缓存中,或者缓存同一时间大面积的失效,从而导致所有请求都去查数据库,导致数据库CPU和内存负载过高,甚至宕机. 解决思路: 1,采用加锁计数,或者使用合理的队列 ...
- 安全清空废纸篓mac
在.bash_profile中加入: alias secureempty='sudo srm -rfvsz /Volumes/*/.Trashes/* ~/.Trash/*' 然后做终端输入:secu ...