spring事务

1.事务的简介

什么是事务

事务是指是程序中一系列严密的逻辑操作,而且所有操作必须全部成功完成,否则在每个操作中所作的所有更改都会被撤消。通俗理解为:多件事当成一件事一起干,好比大家同在一条船,要不一起活,要不一起死

事务的四个基本要素(ACID)

  • 原子性(Atomicity)

    操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。

  • 一致性(Consistency)

    事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。

    或者说 事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

  • 隔离性(Isolation)

    隔离性指当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。 即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

    同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

  • 持久性(Durability)

    当事务正确完成后,对于数据的改变是永久性的。也就是说事务对数据库的所有更新将被保存到数据库,不能回滚。

2.Spring transaction 介绍

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
    @AliasFor("value")
    String transactionManager() default "";
    Propagation propagation() default Propagation.REQUIRED;
    Isolation isolation() default Isolation.DEFAULT;
    int timeout() default -1;
    boolean readOnly() default false;
    Class<? extends Throwable>[] rollbackFor() default {};
    String[] rollbackForClassName() default {};
    Class<? extends Throwable>[] noRollbackFor() default {};
    String[] noRollbackForClassName() default {};
}

2.1 事务注解简介

查看@Transactional 注解我们可以发现,spring的事务包含以下几个方面

事务管理

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

事务属性
隔离级别 isolation
传播行为 propagation
超时 timeout
是否只读 readOnly
回滚规则 rollbackFor

2.2 spring事务管理和数据库事务关系

事务这个概念,怎么实现的都是数据库层面的。spring的事务管理,方便了你写代码,只是把事务管理的代码交给了spring,完事的时候,也是由spring把事务的相关命令提交到数据库的。

3.事务管理 transactionManager

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

事务接口关系.jpg

事务接口关系.jpg)

如上图,Spring事务管理高层抽象主要有3个:

PlatformTransactionManager :事务管理器(用来管理事务,包含事务的提交,回滚)
TransactionDefinition :事务定义信息(隔离,传播,超时,只读)
TransactionStatus :事务具体运行状态

3.1事务管理器PlatformTransactionManager
public interface PlatformTransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}
3.2 事务定义信息TransactionDefinition
public interface TransactionDefinition {
   int PROPAGATION_REQUIRED = 0;
   int PROPAGATION_SUPPORTS = 1;
   int PROPAGATION_MANDATORY = 2;
   int PROPAGATION_REQUIRES_NEW = 3;
   int PROPAGATION_NOT_SUPPORTED = 4;
   int PROPAGATION_NEVER = 5;
   int PROPAGATION_NESTED = 6;
   int ISOLATION_DEFAULT = -1;
   int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
   int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
   int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
   int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
   int TIMEOUT_DEFAULT = -1;
   int getPropagationBehavior();
   int getIsolationLevel();
   int getTimeout();
   boolean isReadOnly();
   @Nullable
   String getName();
}
3.3 事务具体运行状态TransactionStatus
public interface TransactionStatus extends SavepointManager, Flushable {
   boolean isNewTransaction();
   boolean hasSavepoint();
   void setRollbackOnly();
   boolean isRollbackOnly();
   @Override
   void flush();
   boolean isCompleted();
}

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。

下面分别介绍各个平台框架实现事务管理的机制。

JDBC事务

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

    <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上下文定义中添加如下的``声明:

    <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()方法。

Java持久化API事务(JPA)

Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。

Java原生API事务(JTA)

JTA,即Java Transaction API,JTA允许应用程序执行分布式事务处理。

如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
        <property name="transactionManagerName" value="java:/TransactionManager" />
    </bean>

JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

4.事务属性

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

下面详细介绍一下各个事务属性。

4.1隔离级别(Isolation)

4.1.1 隔离级别种类

查看Isolation 源码可知,spring 隔离级别定义以下几种

public enum Isolation {
    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),//-1
    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),//1
    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),//2
    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),//4
    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);//8
}
DEFAULT (默认隔离级别)

使用数据库设置的隔离级别 (默认隔离级别)
MySQL默认采用的REPEATABLE_READ隔离级别,Oracle默认采用的READ_COMMITTED隔离级别。

READ_UNCOMMITTED (读未提交)

允许读取尚未提交的的数据变更 ( 隔离级别最低 ,并发性能高)
可能会出现脏读、幻读、不可重复读。

READ_COMMITTED(读已提交)

允许读取并发事务已经提交的数据(锁定正在读取的行)
可以阻止脏读,但是幻读或不可重复读仍有可能发生

REPEATABLE_READ(可重复读)

对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改(锁定所读取的所有行)
可以阻止脏读和不可重复读,但幻读仍有可能发生。

SERIALIZABLE (串行)

串行操作,保证所有的情况不会发生(锁表,性能最差)
最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就说,该级别可以阻止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务的隔离级别有4种,由低到高分别为Read uncommittedRead committedRepeatable readSerializable 。在事务的并发操作中可能会出现脏读不可重复读幻读

隔离级别 脏读 不可重复读 幻读
READ_UNCOMMITTED Y Y Y
READ_COMMITTED N Y Y
REPEATABLE_READ N N Y
SERIALIZABLE N N N
4.1.2 并发事务导致的问题
脏读(无效数据读出)

一个事务读取另外一个事务还没有提交的数据叫脏读。
例如:事务T1修改了某个表中的一行数据,但是还没有提交,这时候事务T2读取了被事务T1修改后的数据,之后事务T1因为某种原因回滚(Rollback)了,那么事务T2读取的数据就是脏的(无效的)。

解决方案: 1、只能读取已事务提交的数据 READ_COMMITTED
​ 2、只要我读的时候,别人不能读,所以锁定当前行就可以了。REPEATABLE_READ

重点是:一次事务读出的数据是无效

不可重复读(多次读出不同的数据)

不可重复读是指在同一个事务内,两次相同的查询返回了不同的结果。

例如:事务T1会读取两次数据,在第一次读取某一条数据后,事务T2修改了该数据并提交了事务,T1此时再次读取该数据,两次读取便得到了不同的结果

解决方案:只要我读的时候,别人不能读,所以锁定当前行就可以了。REPEATABLE_READ

重点是:一个事务多次读取的数据内容不一致

幻读(多次操作记录数不同)

系统事务A将数据库中所有数据都删除的时候,但是事务B就在这个时候新插入了一条记录,当事务A删除结束后发现还有一条数据,就好像发生了幻觉一样。这就叫幻读。

解决办法:把数据库的事务隔离级别调整到SERIALIZABLE_READ(序列化执行),或者数据库使用者自己进行加锁来保证。

重点是:一个事务多次操作的数据个数不一致

  • 题外话:幻读的重点在于新增或者删除 (数据条数变化)。同样的条件, 第1次和第2次读出来的记录数不一样

4.2传播行为(Propagation behavior)

4.2.1传播行为定义

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 是重新创建事务还是使用父方法的事务?父方法的回滚对子方法的事务是否有影响?这些都是 可以通过事务传播机制来决定的。

例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

4.2.2 传播的类型

查看 Propagation类,可以发现spring的事务传播有下面7种

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
}

事务传播类型

事务传播行为 说明
REQUIRED 支持当前事务,假设当前没有事务。就新建一个事务
SUPPORTS 支持当前事务,假设当前没有事务,就以非事务方式运行
MANDATORY 支持当前事务,假设当前没有事务,就抛出异常
REQUIRES_NEW 新建事务,假设当前存在事务。把当前事务挂起
NOT_SUPPORTED 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起
NEVER 以非事务方式运行,假设当前存在事务,则抛出异常
NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作
REQUIRED(有就用,没有就新建)

默认的行为。支持当前事务,假设当前没有事务。就新建一个事务

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    serviceC.methodB();
     // do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
   // do something
}
  • 单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
  • 调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
SUPPORTS(有就用,没有就不用)

支持当前事务,假设当前没有事务,就以非事务方式运行

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    serviceC.methodB();
     // do something
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
   // do something
}
  • 单独调用methodB方法时,因为当前上下文不存在事务,就以非事务方式运行。
  • 调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
MANDATORY(有就用,没有就报错)

支持当前事务,假设当前没有事务,就抛出异常

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    serviceC.methodB();
     // do something
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
   // do something
}
  • 当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
  • 当调用methodA时,methodB则加入到methodA的事务中,事务地执行。
REQUIRES_NEW(有也不用,独立的)

新建事务,假设当前存在事务。把当前事务挂起

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    doSomeThingA();
    serviceC.methodB();
    doSomeThingB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
   // do something
}
  • 当单独调用methodB时,会开启一个新的事务。
  • 当调用methodA时,相当于调用
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之外的其它代码导致的结果却被回滚了。

NOT_SUPPORTED(有也不用)

总是非事务地执行,并挂起任何存在的事务。使用JtaTransactionManager作为事务管理器。

NEVER(有就报错)

总是非事务地执行,如果存在一个活动事务,则抛出异常。

NESTED(嵌套)

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

这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    doSomeThingA();
    serviceC.methodB();
    doSomeThingB();
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
   // do something
}
  • 单独执行methodB方法时,按REQUIRED属性执行。因为当前上下文不存在事务,所以会开启一个新的事务。

  • 当调用methodA时,相当于调用

    main(){
        Connection con = null;
        Savepoint savepoint = null;
        try{
            con = getConnection();
            con.setAutoCommit(false);
            doSomeThingA();
            savepoint = con2.setSavepoint();
            try{
                methodB();
            } catch(RuntimeException ex) {
                con.rollback(savepoint);
            } finally {
                //释放资源
            }
            doSomeThingB();
            con.commit();
        } catch(RuntimeException ex) {
            con.rollback();
        } finally {
            //释放资源
        }
    }
    

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

4.2.3 NESTED 与REQUIRES_NEW的区别

它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。
使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。

使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTATrasactionManager实现可能有不同的支持方式。

PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。

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

由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back。

注意:以上的方法,methodA,methodB在不同的类中。

readonly这个属性,是放在传播行为中的,一般书都这么归类,readonly并不能影响数据库隔离级别,只是配置之后,不允许在事务中对数据库进行修改操作,仅此而已。

5.事务传播用例

5.1.不嵌套调用

5.1.1 无事务
public void methodA1() {
    //更新数据操作
    updateOfficePhone("021-method1");
    System.out.println(1 / 0);
}
  • 结果
    数据库正常更新数据

    服务抛出java.lang.ArithmeticException: / by zero异常

5.1.2 有事务
@Transactional
public void methodA2() {
    //更新数据操作
    updateOfficePhone("021-methodA2");
    System.out.println(1 / 0);
}
  • 结果

    服务抛出java.lang.ArithmeticException: / by zero异常
    事务生效:数据库数据回滚,未更新。

5.1.3 同时调用多个事务方法
  • 调用方
@Controller
public class UserController {
    @Autowired
    private ServiceA serviceA;
    
   /**
     * 调用多个有事务的方法
     */
    @RequestMapping("/3")
    @ResponseBody
    public void my3() {
        serviceA.methodA3();
        serviceA.methodA2();
    }
}
  • 接口
@Service
public class ServiceA {
    @Transactional
    public void methodA2() {
        updateOfficePhone("021-method2");
        System.out.println(1 / 0);
    }
    @Transactional
    public void methodA3() {
        updateOfficePhone("021-method3");
    }
}
  • 结果
    服务抛出java.lang.ArithmeticException: / by zero异常
    methodA3正常执行;method2抛错,事务回滚

上面都是最基本的用法,不存在嵌套,事务之间彼此之前无影响。传播的行为的默认为Propagation.REQUIRED。

5.2 嵌套调用(针对事务方法在不同类)

5.2.1 REQUIRED 嵌套调用-都有事务
  • 调用方
@Controller
public class UserController {
    @Autowired
    private ServiceA serviceA;
    
    @RequestMapping("/Q1")
    @ResponseBody
    public void myQ1() {
        serviceA.methodQ1();
    }
    @RequestMapping("/Q2")
    @ResponseBody
    public void myQ2() {
        serviceA.methodQ2();
    }
    
}
  • 接口
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @Transactional
    public void methodQ1() {
        updateOfficePhone("021-methodQ1");
        serviceB.methodB1Error();
    }
    
    @Transactional
    public void methodQ2() {
        updateOfficePhone("021-methodQ1");
        serviceB.methodB1();
        System.out.println(1 / 0);
    }
}
//内层
@Service
public class ServiceB {
    //有报错
    @Transactional
    public void methodB1Error() {
        updateMobile("138-methodB1Error");
        System.out.println(1 / 0);
    }
    //无报错
    @Transactional
    public void methodB1() {
        updateMobile("138-methodB1");
    }
}
  • 结果
    服务抛出java.lang.ArithmeticException: / by zero异常
    methodB1抛错,数据均未更新,事务都回滚

  • 分析

    由于使用的是默认传播行为,调用methodQ1方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB1时,methodB1发现当前上下文有事务,因此就加入到当前事务中来。

    对于上面方法任何一个位置报错,都会全部回滚

5.2.2 REQUIRED 嵌套调用-外部无事务

上面的方法改下:

    public void methodQ3() {
        updateOfficePhone("021-methodQ3");
        serviceB.methodB1Error();
    }
    public void methodQ4() {
        updateOfficePhone("021-methodQ4");
        serviceB.methodB1();
        System.out.println(1 / 0);
    }
  • 结果

    methodQ3 :外部方法正常更新,内部回滚

    methodQ4:正常更新,外部异常不影响内部事务

  • 分析

    由于外部无事务,内部事务完成后,不影响外部。外部异常也无法影响内部事务

5.2.3 REQUIRED 嵌套调用- 都有事务,外部try内部方法错误
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @Transactional
    public void methodQ5() {
        updateOfficePhone("021-methodQ5");
        try {
            serviceB.methodB1Error();
        }catch (RuntimeException e){
            System.out.println(e.getMessage());
        }
    }
    
}

  • 结果
    服务抛出 java.lang.ArithmeticException: / by zero异常
    服务抛出 Transaction rolled back because it has been marked as rollback-only
    methodC1抛错,数据均未更新,事务都回滚

  • 分析

    内层事务报错,会被标记rollback;外层事务正常执行,当外层事务准备提交事务时,发现内层被标记了,所以抛出Transaction rolled back because it has been marked as rollback-only。外层提交失败,所有操作都回滚

5.2.4 嵌套调用- 都有事务,外部try内部方法错误
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @Transactional
    public void methodQ6() {
        updateOfficePhone("021-methodQ6");
        serviceB.methodB1ErrorTry();
    }
}

@Service
public class ServiceB {
    @Transactional
    public void methodB1ErrorTry() {
        try{
            updateMobile("138-methodB1ErrorTry");
            System.out.println(1 / 0);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }

}

  • 结果

    两个数据都正常更新

  • 分析

    因为内存事务错误被抓住,没有往外抛,相当于没有错误,所以事务正常执行

5.2.5 REQUIRES_NEW 嵌套调用-都有事务

5.2.1的内部事务代码修改下:


@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    @Transactional
    public void methodQ7() {
        updateOfficePhone("021-methodQ7");
        serviceB.methodB1NewError();
    }

    @Transactional
    public void methodQ8() {
        updateOfficePhone("021-methodQ8");
        serviceB.methodB1New();
        System.out.println(1 / 0);
    }
}

@Service
public class ServiceB {
    //有报错
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB1NewError() {
        updateMobile("138-methodB1Error");
        System.out.println(1 / 0);
    }
    //无报错
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB1New() {
        updateMobile("138-methodB1");
    }
}
  • 结果
    Q7:服务抛出java.lang.ArithmeticException: / by zero异常
    methodB1Error抛错,数据均未更新,事务都回滚

    Q8:服务抛出java.lang.ArithmeticException: / by zero异常

    methodQ8 回滚,methodB1New正常提交

  • 分析

    调用methodQ7方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB1NewError时,由于使用的是REQUIRES_NEW传播行为,methodB1NewError又创建一个事务。由于报错事务回滚,错误抛出到外层。外层发现有错误,也进行事务回滚

    调用methodQ8方法时,当执行到methodB1New时,由于使用的是REQUIRES_NEW传播行为,methodB1New又新建一个事务,执行结束后提交。外层事务接着执行,外层有报错,外层回滚。

从上面用例可以看出

1、如果不同的事务,大家互相无关联,各自提交。

2、事务中出现报错且抛出,事务会回滚。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355