spring事务Propagation及其实现原理详解

 更新时间:2018年02月05日 16:01:52   作者:那个天真的人  
这篇文章主要介绍了spring事务Propagation及其实现原理详解,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下

本文研究的主要是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 这个注解。

总结

spring的声明式事务给我们带来了极大的便利,为了用好这把利器,理解底层的原理还是有必要的,本篇只是spring事务的冰山一角,读者可以在此基础上自行深入探索。

以上就是本文关于spring事务Propagation及其实现原理详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

相关文章

  • Java程序员容易犯的10大低级错误

    Java程序员容易犯的10大低级错误

    这篇文章主要介绍了Java程序员容易犯的10大低级错误,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-09-09
  • 如何在Maven项目配置pom.xml指定JDK版本和编码

    如何在Maven项目配置pom.xml指定JDK版本和编码

    maven是个项目管理工具,如果我们不告诉它要使用什么样的jdk版本编译,它就会用maven-compiler-plugin默认的jdk版本来处理,这样就容易出现版本不匹配的问题,这篇文章主要给大家介绍了关于如何在Maven项目配置pom.xml指定JDK版本和编码的相关资料,需要的朋友可以参考下
    2024-01-01
  • Springboot项目启动找不到启动类的解决

    Springboot项目启动找不到启动类的解决

    这篇文章主要介绍了Springboot项目启动找不到启动类的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • java实现把两个有序数组合并到一个数组的实例

    java实现把两个有序数组合并到一个数组的实例

    今天小编就为大家分享一篇java实现把两个有序数组合并到一个数组的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-05-05
  • Springboot+Thymeleaf+Jpa实现登录功能(附源码)

    Springboot+Thymeleaf+Jpa实现登录功能(附源码)

    最近有学习到关于Springboot+Thymeleaf+Jpa的综合运用知识,因此想写一个简单的登录界面来尝试一下,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • Java毕业设计实战之校园一卡通系统的实现

    Java毕业设计实战之校园一卡通系统的实现

    这是一个使用了java+Springboot+Maven+mybatis+Vue+mysql+wd开发的校园一卡通系统,是一个毕业设计的实战练习,具有校园一卡通系统该有的所有功能,感兴趣的朋友快来看看吧
    2022-01-01
  • 浅析Java Mail无法解析带分号的收件人列表的问题

    浅析Java Mail无法解析带分号的收件人列表的问题

    JAVA MAIL严格按照RFC 822规范进行操作,没有对分号做处理。大多数邮件服务器都是严格遵循RFC 822规范的
    2013-08-08
  • Springboot之自定义全局异常处理的实现

    Springboot之自定义全局异常处理的实现

    这篇文章主要介绍了Springboot之自定义全局异常处理的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • Java简单实现定时器

    Java简单实现定时器

    这篇文章主要为大家详细介绍了Java简单实现定时器,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • SpringBoot缓存Ehcache的使用详解

    SpringBoot缓存Ehcache的使用详解

    EhCache、Redis比较常用,使用Redis的时候需要先安装Redis服务器,本文给大家介绍SpringBoot缓存Ehcache的使用详解,感兴趣的朋友跟随小编一起看看吧
    2022-03-03

最新评论