前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring事务(上)

spring事务(上)

作者头像
码农戏码
发布2021-03-23 10:22:19
7480
发布2021-03-23 10:22:19
举报
文章被收录于专栏:DDDDDD

前言

这篇其实也要归纳到《常识》系列中,但这重点又是spring的介绍,故归档在spring系列中。

工作很多年,除了学生时代学过,事务还真没有用过。过去开发游戏时,完全不用事务;现在互联网开发,也没有使用事务的场景,不要见怪。

概念

对于事务(Transaction)的概念,网上有各种版本,大同小异,

事务就是是由一系列对系统中数据进行读写的操作组成的一个程序执行单元,狭义上的事务特指数据库事务。

事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。

在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。

比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱;然后ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的。

事务的特性

大名鼎鼎的ACID

  1. 原子性(Atomicity),事务必须是一个原子的操作序列单元,一次事务只允许存在两种状态,全部成功或全部失败,任何一个操作失败都将导致整个事务失败
  2. 一致性(Consistency),事务的执行不能破坏系统数据的完整性和一致性,如果未完成的事务对系统数据的修改有一部分已经写入物理数据库,这时系统数据就处于不一致状态
  3. 隔离性(Isolation),在并发环境中,不同的事务操作相同的数据时,虚相互隔离不能相互干扰
  4. 持久性(Durability),事务一旦提交,对系统数据的变更就应该是永久的,必须被永久保存下来,即使服务器宕机了,只要数据库能够重新启动,就一定能够恢复到事务成功结束时的状态

事务并发处理问题

如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题。由于并发操作带来的数据不一致性包括:丢失数据修改、读”脏”数据(脏读)、不可重复读、产生幽灵数据:

假设数据库中有如下一张表:

第一类丢失更新(lost update)

回滚丢失

在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。

在T1时刻开启了事务1,T2时刻开启了事务2,

在T3时刻事务1从数据库中取出了id="402881e535194b8f0135194b91310001"的数据,

T4时刻事务2取出了同一条数据,

T5时刻事务1将age字段值更新为30,

T6时刻事务2更新age为35并提交了数据,

但是T7事务1回滚了事务age最后的值依然为20,事务2的更新丢失了,

这种情况就叫做"第一类丢失更新(lost update)"。

脏读(dirty read)

事务没提交,提前读取

如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读

在T1时刻开启了事务1,T2时刻开启了事务2,

在T3时刻事务1从数据库中取出了id="402881e535194b8f0135194b91310001"的数据,

在T5时刻事务1将age的值更新为30,但是事务还未提交,

T6时刻事务2读取同一条记录,获得age的值为30,但是事务1还未提交,

若在T7时刻事务1回滚了事务2的数据就是错误的数据(脏数据),

这种情况叫做" 脏读(dirty read)"。

虚读(phantom read)

一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的

在T1时刻开启了事务1,T2时刻开启了事务2,

T3时刻事务1从数据库中查询所有记录,记录总共有一条,

T4时刻事务2向数据库中插入一条记录,T6时刻事务2提交事务。

T7事务1再次查询数据数据时,记录变成两条了。

这种情况是"虚读(phantom read)"。

不可重复读(unrepeated read)

一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任

在T1时刻开启了事务1,T2时刻开启了事务2,

在T3时刻事务1从数据库中取出了id="402881e535194b8f0135194b91310001"的数据,此时age=20,

T4时刻事务2查询同一条数据,

T5事务2更新数据age=30,T6时刻事务2提交事务,

T7事务1查询同一条数据,发现数据与第一次不一致。

这种情况就是"不可重复读(unrepeated read)"

第二类丢失更新(second lost updates)

覆盖丢失

不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。

在T1时刻开启了事务1,T2时刻开启了事务2,

T3时刻事务1更新数据age=25,

T5时刻事务2更新数据age=30,

T6时刻提交事务,

T7时刻事务2提交事务,把事务1的更新覆盖了。

这种情况就是"第二类丢失更新(second lost updates)"。

并发问题总结

不可重复读的重点是修改 :

同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了

幻读的重点在于新增或者删除

同样的条件 , 第 1 次和第 2 次读出来的记录数不一样

第一类更新丢失(回滚丢失)

第二类更新丢失(覆盖丢失)

隔离级别

解决并发问题的途径是什么?答案是:采取有效的隔离机制。怎样实现事务的隔离呢?隔离机制的实现必须使用锁

一般在编程的时候只需要设置隔离等级

数据库系统提供四种事务隔离级别:

  1. 未提交读(READ UNCOMMITTED )

最低隔离级别,一个事务能读取到别的事务未提交的更新数据,很不安全,可能出现丢失更新、脏读、不可重复读、幻读;

  1. 提交读(READ COMMITTED)

一个事务能读取到别的事务提交的更新数据,不能看到未提交的更新数据,不会出现丢失更新、脏读,但可能出现不可重复读、幻读;

  1. 可重复读(REPEATABLE READ)

保证同一事务中先后执行的多次查询将返回同一结果,不受其他事务影响,不可能出现丢失更新、脏读、不可重复读,但可能出现幻读;

  1. 序列化(SERIALIZABLE)

最高隔离级别,不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。

隔离级别越高,数据库事务并发执行性能越差,能处理的操作越少。 所以一般地,推荐使用REPEATABLE READ级别保证数据的读一致性。 对于幻读的问题,可以通过加锁来防止

MySQL支持这四种事务等级,默认事务隔离级别是REPEATABLE READ。

Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别, 所以Oracle数据库不支持脏读

Oracle数据库默认的事务隔离级别是READ COMMITTED

不可重复读和幻读的区别是,不可重复读对应的表的操作是更改(UPDATE),而幻读对应的表的操作是插入(INSERT),两种的应对策略不一样。对于不可重复读,只需要采用行级锁防止该记录被更新即可,而对于幻读必须加个表级锁,防止在表中插入数据

乐观锁(Optimistic Lock)和悲观锁(Pessimistic Lock)

最重要的分类就是乐观锁(Optimistic Lock)和悲观锁(Pessimistic Lock),这实际上是两种锁策略

乐观锁,顾名思义就是非常乐观,非常相信真善美,每次去读数据都认为其它事务没有在写数据,所以就不上锁,快乐的读取数据,而只在提交数据的时候判断其它事务是否搞过这个数据了,如果搞过就rollback。乐观锁相当于一种检测冲突的手段,可通过为记录添加版本或添加时间戳来实现。

悲观锁,对其它事务抱有保守的态度,每次去读数据都认为其它事务想要作祟,所以每次读数据的时候都会上锁,直到取出数据。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性,但随之而来的是各种开销。悲观锁相当于一种避免冲突的手段。

选择标准:如果并发量不大,或数据冲突的后果不严重,则可以使用乐观锁;而如果并发量大或数据冲突后果比较严重(对用户不友好),那么就使用悲观锁。

分共享锁(S锁,Shared Lock)和排他锁(X锁,Exclusive Lock)

从读写角度,分共享锁(S锁,Shared Lock)和排他锁(X锁,Exclusive Lock),也叫读锁(Read Lock)和写锁(Write Lock)。 理解:

持有S锁的事务只读不可写。

如果事务A对数据D加上S锁后,其它事务只能对D加上S锁而不能加X锁。

持有X锁的事务可读可写。

如果事务A对数据D加上X锁后,其它事务不能再对D加锁,直到A对D的锁解除。

表级锁(Table Lock)和行级锁(Row Lock)

从锁的粒度角度,主要分为表级锁(Table Lock)和行级锁(Row Lock)。

表级锁将整个表加锁,性能开销最小

用户可以同时进行读操作。当一个用户对表进行写操作时,用户可以获得一个写锁,写锁禁止其他的用户读写操作。写锁比读锁的优先级更高,即使有读操作已排在队列中,一个被申请的写锁仍可以排在所队列的前列。

行级锁仅对指定的记录进行加锁

这样其它进程可以对同一个表中的其它记录进行读写操作。行级锁粒度最小,开销大,能够支持高并发,可能会出现死锁。

MySQL的MyISAM引擎使用表级锁,而InnoDB支持表级锁和行级锁,默认是行级锁。 还有BDB引擎使用页级锁,即一次锁定一组记录,并发性介于行级锁和表级锁之间。

三级锁协议

三级加锁协议是为了保证正确的事务并发操作,事务在读、写数据库对象是需要遵循的加锁规则。

一级封锁协议:事务T在修改数据R之前必须对它加X锁,直到事务结束方可释放。而若事务T只是读数据,不进行修改,则不需加锁,因此一级加锁协议下可能会出现脏读和不可重复读。

二级加锁协议:在一级加锁协议的基础上,加上这样一条规则——事务T在读取数据R之前必须对它加S锁,直到读取完毕以后释放。二级加锁协议下可能会出现不可重复读。

三级加锁协议:在一级加锁协议的基础上,加上这样一条规则——事务T在读取数据R之前必须对它加S锁,直到事务结束方可释放。三级加锁协议避免了脏读和不可重复读的问题

Spring事务

Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务

Spring事务管理器

Spring事务管理涉及的接口的联系如下:

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager, 通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,

但是具体的实现就是各个平台自己的事情了

代码语言:javascript
复制
/**
 * This is the central interface in Spring's transaction infrastructure.
 * Applications can use this directly, but it is not primarily meant as API:
 * Typically, applications will work with either TransactionTemplate or
 * declarative transaction demarcation through AOP.
 */
public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}

标准的jdbc处理事务代码

代码语言:javascript
复制
Connection conn = DataSourceUtils.getConnection();
 //开启事务
conn.setAutoCommit(false);
try {
    Object retVal = callback.doInConnection(conn);
    conn.commit(); //提交事务
    return retVal;
}catch (Exception e) {
    conn.rollback();//回滚事务
    throw e;
}finally {
    conn.close();
}

spring对应的TranstactionTemplate处理

代码语言:javascript
复制
public class TransactionTemplate extends DefaultTransactionDefinition
        implements TransactionOperations, InitializingBean {

    @Override
    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
            return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
        }
        else {
            TransactionStatus status = this.transactionManager.getTransaction(this);
            T result;
            try {
                result = action.doInTransaction(status);
            }
            catch (RuntimeException ex) {
                // Transactional code threw application exception -> rollback
                rollbackOnException(status, ex);
                throw ex;
            }
            catch (Error err) {
                // Transactional code threw error -> rollback
                rollbackOnException(status, err);
                throw err;
            }
            catch (Exception ex) {
                // Transactional code threw unexpected exception -> rollback
                rollbackOnException(status, ex);
                throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
            }
            this.transactionManager.commit(status);
            return result;
        }
    }

}

具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。

JDBC事务

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

代码语言:javascript
复制
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

Hibernate事务

如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的声明:

代码语言:javascript
复制
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

事务属性

事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。

代码语言:javascript
复制
public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事务的传播行为
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
}

那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面

传播行为

事务的第一个方面是传播行为(propagation behavior)。 当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

为什么需要定义传播?

在我们用SSH开发项目的时候,我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。 如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。 以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernate session传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。

Spring定义了七种传播行为:

PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。

PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

PROPAGATION_MANDATORY

使用当前的事务,如果当前没有事务,就抛出异常。

PROPAGATIONREQUIRESNEW

新建事务,如果当前存在事务,把当前事务挂起。

PROPAGATIONNOTSUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

PROPAGATION_NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

传播行为详细

通过实例尝试一下各个传播属性

代码语言:javascript
复制
ServiceA {

     void methodA() {
         ServiceB.methodB();
     }
}

ServiceB {

     void methodB() {
     }
}
PROPAGATION_REQUIRED

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

代码语言:javascript
复制
//事务属性 PROPAGATION_REQUIRED
methodA{
    ……
    methodB();
    ……
}
//事务属性 PROPAGATION_REQUIRED
methodB{
   ……
}

单独调用methodB方法:

代码语言:javascript
复制
main{ 
    metodB(); 
}

相当于

代码语言:javascript
复制
Main{ 
    Connection con=null; 
    try{ 
        con = getConnection(); 
        con.setAutoCommit(false); 

        //方法调用
        methodB(); 

        //提交事务
        con.commit(); 
    } Catch(RuntimeException ex) { 
        //回滚事务
        con.rollback();   
    } finally { 
        //释放资源
        closeCon(); 
    } 
}

Spring保证在methodB方法中所有的调用都获得到一个相同的连接。在调用methodB时,没有一个存在的事务,所以获得一个新的连接,开启了一个新的事务。

只是在ServiceB.methodB内的任何地方出现异常,ServiceB.methodB将会被回滚,不会引起ServiceA.methodA的回滚

单独调用MethodA时,在MethodA内又会调用MethodB.

执行效果相当于:

代码语言:javascript
复制
main{ 
    Connection con = null; 
    try{ 
        con = getConnection(); 
        methodA(); 
        con.commit(); 
    } catch(RuntimeException ex) { 
        con.rollback(); 
    } finally {    
        closeCon(); 
    }  
}

调用MethodA时,环境中没有事务,所以开启一个新的事务.

当在MethodA中调用MethodB时,环境中已经有了一个事务,所以methodB就加入当前事务

ServiceA.methodA或者ServiceB.methodB无论哪个发生异常methodA和methodB作为一个整体都将一起回滚

PROPAGATION_SUPPORTS

如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同

代码语言:javascript
复制
//事务属性 PROPAGATION_REQUIRED
methodA(){
  methodB();
}

//事务属性 PROPAGATION_SUPPORTS
methodB(){
  ……
}

单纯的调用methodB时,methodB方法是非事务的执行的。当调用methdA时,methodB则加入了methodA的事务中,事务地执行

PROPAGATION_MANDATORY

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

代码语言:javascript
复制
//事务属性 PROPAGATION_REQUIRED
methodA(){
    methodB();
}

//事务属性 PROPAGATION_MANDATORY
    methodB(){
    ……
}

当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);

当调用methodA时,methodB则加入到methodA的事务中,事务地执行

PROPAGATIONREQUIRESNEW

总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

代码语言:javascript
复制
//事务属性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事务属性 PROPAGATION_REQUIRES_NEW
methodB(){
    ……
}

调用A方法:

代码语言:javascript
复制
main(){
    methodA();
}

相当于

代码语言:javascript
复制
main(){
    TransactionManager tm = null;
    try{
        //获得一个JTA事务管理器
        tm = getTransactionManager();
        tm.begin();//开启一个新的事务
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//挂起当前事务
        try{
            tm.begin();//重新开启第二个事务
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二个事务
        } Catch(RunTimeException ex) {
            ts2.rollback();//回滚第二个事务
        } finally {
            //释放资源
        }
        //methodB执行完后,恢复第一个事务
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一个事务
    } catch(RunTimeException ex) {
        ts1.rollback();//回滚第一个事务
    } finally {
        //释放资源
    }
}

在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。

Ts2是否成功并不依赖于ts1

如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。

而除了 methodB之外的其它代码导致的结果却被回滚了

PROPAGATIONNOTSUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起,

代码语言:javascript
复制
//事务属性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事务属性 PROPAGATION_NOT_SUPPORTED
methodB(){
    ……
}

当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATIONREQUIRED , 而ServiceB.methodB的事务级别是PROPAGATIONNOT_SUPPORTED , 那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务

PROPAGATION_NEVER

不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATIONREQUIRED, 而ServiceB.methodB的事务级别是PROPAGATIONNEVER , 那么ServiceB.methodB就要抛出异常了。

PROPAGATION_NESTED

开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

比如我们设计ServiceA.methodA的事务级别为PROPAGATIONREQUIRED,ServiceB.methodB的事务级别为PROPAGATIONNESTED,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的子事务并设置savepoint,等待ServiceB.methodB的事务完成以后,他才继续执行

因为ServiceB.methodB是外部事务的子事务,那么

  1. 如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB也将回滚。
  2. 如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA的try..catch捕获并处理,ServiceA.methodA事务仍然可能提交;如果他抛出的异常未被ServiceA.methodA捕获处理,ServiceA.methodA事务将回滚。

理解Nested的关键是savepoint。

与PROPAGATIONREQUIRESNEW的区别

  1. RequiresNew每次都创建新的独立的物理事务,而Nested只有一个物理事务;
  2. Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,而 RequiresNew由于都是全新的事务,所以之间是无关联的;
  3. Nested使用JDBC 3的保存点实现,即如果使用低版本驱动将导致不支持嵌套事务。 使用嵌套事务,必须确保具体事务管理器实现的nestedTransactionAllowed属性为true,否则不支持嵌套事务,如DataSourceTransactionManager默认支持,而HibernateTransactionManager默认不支持,需要我们来开启。

在 spring 中使用 PROPAGATION_NESTED的前提:

  1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!!
  2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+
  3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0

完整的文章字数超限了,分成上下两篇

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农戏码 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 概念
    • 事务的特性
      • 事务并发处理问题
        • 第一类丢失更新(lost update)
        • 脏读(dirty read)
        • 虚读(phantom read)
        • 不可重复读(unrepeated read)
        • 第二类丢失更新(second lost updates)
      • 并发问题总结
        • 隔离级别
      • Spring事务
        • Spring事务管理器
          • JDBC事务
          • Hibernate事务
        • 事务属性
          • 传播行为
          • 完整的文章字数超限了,分成上下两篇
      相关产品与服务
      数据库
      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档