在学 Spring boot 的时候,突然遇到了 Spring 的事务问题,由于之前没有认真的看过这块的内容,最近在看,就认真的整理下文档,留一个存档。

事务

什么是事务?

事务(transaction)是并发控制单元,逻辑上的一组操作,这组操作要么全部成功,要么全部失败。

数据库向用户提出保存当前程序状态的方法,叫事务提交(commit);当在执行过程中,使数据库忽略当前的状态并回到前面保存状态的方法叫事务回滚(rollback)。

事务的特性:

  • 原子性(atomicity)是指事务是一个不可分割的工作单位。事务总体的操作要么发生,要么都不发生。
  • 一致性(consistency)指事务前后数据的完整性必须保持一致。(比如张三给李四转2000元,那么张三就需要从账户中减去 2000,李四需要增加 2000,而不能说减少或者增加的数量不是 2000)。
  • 隔离性(isolation)值多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。事务查看数据时数据所处的状态,要么是被另一并发事务修改之前的状态,要么是被另一并发事务修改之后的状态,即事务不会查看由另一个并发事务正在修改的数据。这种隔离方式也叫可串行性。
  • 持久性(durability)是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响。

Spring 事务管理

所谓说的事务管理就是指:按照给定的事务规则来执行提交或者回滚操作

spring 事务管理高层抽象主要包括 3 个接口

  • PlatformTransactionManager: (平台)事务管理器
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus: 事务运行状态

PlatformTransactionManager 事务管理器

Spring 并不直接管理事务,而是提供多种事务管理器,具体实现对事务管理进行管理的是 hibernate 或者 JTA 等持久化机制提供的相关平台框架。Spring 事务管理器的接口是: org.springframework.transaction.PlatformTransactionManager ,通过这个接口,Spring 为各个平台 JDBC、Hibernate 等提供事务管理器,具体实现就是各个平台的事情了。

PlatformTransactionManager 接口的源码

Public interface PlatformTransactionManager()...{  
    // 根据指定的传播行为,返回当前活动的事务或创建一个新事务
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 

    // 使用事务目前的状态提交事务
    Void commit(TransactionStatus status) throws TransactionException;  
 
    // 对执行的事务进行回滚
    Void rollback(TransactionStatus status) throws TransactionException;  
}

TransactionDefinition 事务定义

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

  • 隔离级别
  • 传播行为
  • 回滚规则
  • 是否只读
  • 事务超时

一些事务的问题解释:

  •  脏读(Dirty read):一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。
  •  不可重复读(Unrepeatableread):在同一事务中,多次读取同一数据返回的结果有所不同。
  •  幻读(Phantom read):一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。

不可重复度和幻读区别:

不可重复读的重点是修改,幻读的重点在于新增或者删除。

例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。

 

例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

 

隔离级别说明(五种)

  • TransactionDefinition.ISOLATION_DEFAULT:后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ 隔离级别, Oracle 默认采用的 READ_COMMITTED 隔离级别。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:允许读取并发事务已经提交的数据,可以阻止脏读,但是不可重复读或者幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但是幻读仍有发生
  • TransactionDefinition.ISOLATION_SERIALIZABLE:最高级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就说,该级别可以防止脏读、不可以重复读以及幻读。但是这就严重影响程度的性能。通常情况下也不会用到该级别。

事务传播行为(七种):

事务的传播行为是解决业务层之间的相互调用的问题

支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入事务;如果当前没有事务,则抛出异常。

不支持当前事务的情况:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。

其他情况

  • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,当前的事务可以设置一个保存点,事务在需要回滚的时候可以回滚到保存点,或者可以回滚到初始状态;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

TransactionDefinition 接口的源码

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事务的传播行

    // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getIsolationLevel();

    String getName();  //返回事务的名字

    int getTimeout();  // 返回事务必须在多少秒内完成

   boolean isReadOnly();  // 返回是否优化为只读事务。
}

TransactionStatus 事务状态

TransactionStatus 接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息。

PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。返回的TransactionStatus 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。

TransactionStatus 接口接口内容如下:

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
}