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[] rollbackFor() default {};
    String[] rollbackForClassName() default {};
    Class[] 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将其装配到应用程序的上下文定义中:

    
        
    

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

Hibernate事务

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

    
        
    

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:

    
        
    

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

Java原生API事务(JTA)

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

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

    
        
    

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、事务中出现报错且抛出,事务会回滚。

你可能感兴趣的:(spring事务)