Spring事务详述

1、先来说一说事务是什么,在Spring中事务指的是对数据库进行增删改查的一系列过程,它具有以下几种特性:

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

2、 Spring事务的核心流程图如下:

è¿éåå¾çæè¿°

 3、Spring隔离级别

在Spring中,多个事务并发执行,隔离级别的话,主要是分为5种,分别有着不同的代表,且看下表:

ISOLATION_DEFAULT PlatformTranscationManager种默认的隔离级别,代表使用数据库的默认事务隔离级别。
ISOLATION_READ_UNCOMMITTED 最低的隔离级别,一个事务未提交前的结果可以被其他事务获取,可能会造成脏读、不可重复度、幻读的问题。
ISOLATION_READ_COMMITTED 一个事务再提交前不可以被其他事务获取结果,不会造成脏读,可能会造成不可重复读、幻读等问题。
ISOLATION_REPEATABLE_READ 可以防止脏读、不可重复读问题,但不会解决幻读问题。
ISOLATION_SERIALIZABLE 花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。

下面来介绍一下上边的名词:

  1. ( Dirty reads)脏读:指的是事务A读取到事务B的数据后,事务B可能会因为一些原因失败而进行回滚,导致事务A获取的值是不正确的值。
  2. (non-repeatable reads)不可重复读:比如事务A中有两次读取某一全局变量total的值,第一次读取为100,然后事务B对 total的值改成了200,事务A第二次读取total的值为200,造成事务A的数据混乱。
  3. (phantom reads)幻读数据:幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

4、Spring事务的7种传播行为:

 事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
PROPAGATION_SUPPORTS 如果存在一个事务,则支持当前事务。如果没有事务则以非事务的形式执行。
PROPAGATION_MANDATORY 如果存在一个事务,则支持当前事务。如果没有一个活动的事务,则抛出异常。
PROPAGATION_REQUIRED_NEW 总是开启新的事务。如果已有事务,则挂起已存在的事务。
PROPAGATION_NOT_SUPPORTED 总是非事务的执行,并挂起当前所有的事务。
PROPAGATION_NEVER 总是非事务的执行,如果存在事务则抛出异常。
PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

下边来对其中几种进行详细叙述:

1、PROPAGATION_REQUIRED,如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        ...
        fun2();
    }
    //PROPAGATION_REQUIRED
    public void fun2(){
        ...
    }
}

单独执行fun2相当于如下:

...
main{
    Connection c = null;
    try{
        c.getConnection();
        con.setAutoCommit(false); 
        fun2();
        c.commit();
    }catch(RuntimeException e){
        c.rollback();
    }finally{
        c.close();
    }
    
    
}
...

由于当前没有事务存在,故会创建一个新的事务去执行fun2();

执行fun1时,也会调用fun2,因此fun2方法会加入到fun1的事务中去执行:

...
main{
    Connection c = null;
    try{
        c.getConnection();
        con.setAutoCommit(false); 
        fun1();
        c.commit();
    }catch(RuntimeException e){
        c.rollback();
    }finally{
        c.close();
    }
    
    
}
...

 2、PROPAGATION_SUPPORTS,如果存在一个事务,则支持当前事务。如果没有事务则以非事务的形式执行。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        ...
        fun2();
    }
    //PROPAGATION_SUPPORTS
    public void fun2(){
        ...
    }
}

单独调用fun2时,如果当前没有事务可用也不会开启事务;如果有可用事务,fun2会加入到调用他的事务中去,就比如fun1执行时会调用fun2,因此此时fun2加入了fun1的事务中。

3、PROPAGATION_MANDATORY,如果存在一个事务,则支持当前事务。如果没有一个活动的事务,则抛出异常。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        ...
        fun2();
    }
    //PROPAGATION_MANDATORY
    public void fun2(){
        ...
    }
}

单独执行fun2方法会抛出异常,但是通过fun1去调用fun2,则不会抛出异常。

4、 PROPAGATION_REQUIRED_NEW,总是开启新的事务。如果已有事务,则挂起已存在的事务。需要使用JtaTransactionManager作为事务管理器。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        System.out.println("AAA_Begin");
        fun2();
        System.out.println("AAA_End");
    }
    //PROPAGATION_REQUIRED_NEW
    public void fun2(){
        ...
        System.out.println("BBB");
    }
}

调用方法fun1时,相当于如下:

...
main{
    TransactionManager tm = null;
    try{
       tm = getTranscationManager();
       tm.begin();
       Transaction ts1 = tm.getTransaction(); 
       System.out.println("AAA_Begin");
       tm.suspend();
       try{
           tm.begin();
           TransactionManager ts2 = tm.getTranscationManager();
           fun2();
           ts2.commit();
       }catch(RuntimeException e){
           ts2.rollback();
       }finally{
           //释放资源
       }
    tm.resume(ts1); 
    System.out.println("AAA_End");
    ts1.commit();
    }catch(RuntimeException e){
        tm.rollback();
    }finally{
        //释放资源
    }
    
    
}
...

在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。 

5、PROPAGATION_NESTED,表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务。

public class Test{
    //PROPAGATION_REQUIRED
    public void fun1(){
        ...
        System.out.println("AAA");
        fun2();
    }
    //PROPAGATION_NESTED
    public void fun2(){
        ...
        System.out.println("BBB");
    }
}

单独执行fun2则会开启新的事务,去支持fun2。

调用fun1方法,相当于如下:

...
main{
    Connection c = null;
    try{
        c.getConnection();
        con.setAutoCommit(false); 
        fun1();
        savepoint = c.setSavepoint();
        try{
            fun2();
        }catch(RuntimeException e){
            c.rollback(savepoint);
        }
        c.commit();
    }catch(RuntimeException e){
        c.rollback();
    }finally{
        c.close();
    }
    
    
}
...

 这里可以看到,在执行fun2之前,会使用savepoint去保留当前的执行状态,如果fun2执行失败,则回滚到savepoint的状态,而且fun2并没有执行commit方法,如果后续代码执行失败,则会回滚到起始状态。由此可见,外部事务回滚会影响到内层事务,而内层事务的回滚不会影响到外层事务。

参考自:https://www.cnblogs.com/csniper/p/5536633.htmlhttps://www.cnblogs.com/yixianyixian/p/8372832.html

猜你喜欢

转载自blog.csdn.net/qq_39429962/article/details/85220238