Spring transaction - talk about Propagation and its implementation principle

foreword

Spring is now a de facto standard for java development, thanks to its convenience, complete functions, and ease of use. In the development process, operating DB is a very common operation, and when DB is involved, transactions are involved. In the normal development process of transactions, even if you do not notice it, the normal execution of the program will not have side effects, but if an exception occurs and the transaction is not handled properly, unexpected results may occur. Spring encapsulates various operations in terms of transactions, especially the emergence of declarative transactions, which makes development more comfortable. Spring extends transactions to support the definition of multiple propagation properties, which is also the focus of this article. 

what is business

Strictly speaking, a transaction is the abbreviation of multiple operations. Either all of these operations take effect, or none of them take effect (equivalent to no execution). A general operation process is simplified as follows:

try{
    Connection conn = getConnection();
    // 执行一些数据库操作
}catch(Exception e){
       conn.rollback();
}finally{
    conn.close();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Some problems can be seen from the above code:

  1. Too much useless fixed code
  2. If a request needs to call multiple service interfaces, it is difficult to control the transaction more finely
  3. It is difficult to unify the coding method across multiple underlying data layers, such as jdbc, mybatis, hibernate, and jta.

Spring provides declarative transactions, so that we do not need to pay attention to the specific implementation of the underlying layer, and shields a variety of different underlying implementation details. In order to support the fine control of transactions by a variety of complex businesses, spring provides transaction propagation properties, combined with declarative transactions , has achieved a major business weapon.


Example analysis of spring transaction propagation properties

In the TransactionDefinition class, spring provides 6 propagation properties, which are explained with simple examples.

Reminder: Joining the current transaction mentioned below refers to using the same Connection at the bottom layer, but the transaction status object can be recreated without affecting it. The article mentioned that there is only one transaction at present, which means that the underlying connection is shared, regardless of how many transaction status objects (TransactionStatus) are created. 

1、PROPAGATION_REQUIRED

Description: If a transaction already exists, join the transaction, if there is no transaction, create a transaction, which is the default propagation property value.

Look at a small example, the code is as follows:

@Transactional
public void service(){
    serviceA();
    serviceB();
}

@Transactional
serviceA();
@Transactional
serviceB();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

serviceA 和 serviceB 都声明了事务,默认情况下,propagation=PROPAGATION_REQUIRED,整个service调用过程中,只存在一个共享的事务,当有任何异常发生的时候,所有操作回滚。



2、PROPAGATION_SUPPORTS

说明:如果当前已经存在事务,那么加入该事务,否则创建一个所谓的空事务(可以认为无事务执行)。

看一个小例子,代码如下:

public void service(){
     serviceA();
     throw new RunTimeException();
}

@Transactional(propagation=Propagation.SUPPORTS)
serviceA();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

serviceA执行时当前没有事务,所以service中抛出的异常不会导致 serviceA回滚。

再看一个小例子,代码如下:

public void service(){
     serviceA();
}

@Transactional(propagation=Propagation.SUPPORTS)
serviceA(){
    do sql 1
    1/0;
    do sql 2
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

由于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 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这种情况执行 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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

当调用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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

当调用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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上面的示例调用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>
  • 1
  • 2
  • 3
  • 4
  • 5

看一个小例子,代码如下:

@Transactional
public void service(){
    serviceA();
    try{
        serviceB();
    }catch(Exception e){
    }
}
serviceA(){
    do sql
}
@Transactional(propagation=Propagation.NESTED)
serviceB(){
    do sql1
    1/0;
    do sql2
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

serviceB是一个内嵌的业务,内部抛出了运行时异常,所以serviceB整个被回滚了,由于service捕获了异常,所以serviceA是可以正常提交的。

再来看一个例子,代码如下:

@Transactional
public void service(){
     serviceA();
     serviceB();
     1/0;
}
@Transactional(propagation=Propagation.NESTED)
serviceA(){
    do sql
}
serviceB(){
    do sql
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

由于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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18



执行原理图如下:

这里写图片描述

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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17



执行原理图如下:

这里写图片描述

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事务的冰山一角,读者可以在此基础上自行深入探索。

转载来源:https://blog.csdn.net/yanyan19880509/article/details/53041564

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325855464&siteId=291194637