【spring】04 彻底掌握spring事务

事务Transaction,它是一系列严密的操作动作,要么一起成功,要么一起失败都回滚撤销。Spring事务管理基于底层数据库本身的事务处理机制。

1. 事务的特性

在这里插入图片描述

  1. 原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做

  2. 一致性:数据不会因为事务的执行而遭到破坏,事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

  3. 隔离性:一个事物的执行,不受其他事务的干扰,即并发执行的事物之间互不干扰,对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。关于事务的隔离性数据库提供了多种隔离级别

  4. 持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

2. 事务的定义

首先看事务的定义
在这里插入图片描述
从事务的定义中可以看出,事务有:传播行为、隔离级别、超时时间、是否只读、名字等定义

2.1 事务的传播行为

什么是事务传播行为?
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。

public void methodA(){
    methodB();
    //doSomething
 }
 
 @Transaction(Propagation=XXX)
 public void methodB(){
    //doSomething
 }

代码中methodA()方法嵌套调用了methodB()方法,methodB()的事务传播行
为由@Transaction(Propagation=XXX)设置决定。这里需要注意的是
methodA()并没有开启事务,某一个事务传播行为修饰的方法并不是必须要在
开启事务的外围方法中调用。

源码解析
点进获取事务的传播行为的方法可以看到,默认返回的是PROPAGATION_REQUIRED参数在这里插入图片描述

Spring 在 TransactionDefinition 接口中规定了3类 7 种类型的事务传播行为

// TransactionDefinition.java

// ========== 支持当前事务的情况 ========== 

/**
 * 如果当前存在事务,则使用该事务。
 * 如果当前没有事务,则创建一个新的事务。
 */
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;

// ========== 其他情况 ========== 

/**
 * 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。
 * 如果当前没有事务,则等价于 {@link TransactionDefinition#PROPAGATION_REQUIRED}
 */
int PROPAGATION_NESTED = 6;

2.1.1实例验证

1.PROPAGATION_REQUIRED
  • 场景1:

    验证方法1:
    方法notransaction_exception_required_required中产生了异常
    在这里插入图片描述
    User1ServiceImpl中addRequired方法的事务传播级别是propagation = Propagation.REQUIRED,无异常
    在这里插入图片描述
    User2ServiceImpl中addRequired方法的事务传播级别是propagation = Propagation.REQUIRED,无异常
    在这里插入图片描述
    执行结果,外围方法报异常,
    在这里插入图片描述
    2个service插入数据成功
    在这里插入图片描述
    结论
    外围方法未开启事务,插入“张三”,“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”,“李四”方法独立的事务

    验证方法2:
    修改service2调用的方法
    在这里插入图片描述
    修改为有异常的addRequiredException方法,也就是说此时service1没有异常,service2有异常,外围方法没有异常
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    张三更新成功,插入“李四”的事务回滚
    在这里插入图片描述
    最终结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

  • 场景2
    验证方法1
    外围方法增加事务,并且外围方法报异常,两个有事务的service都没有报异常
    在这里插入图片描述
    执行结果
    在这里插入图片描述
    数据库并未插入数据在这里插入图片描述
    验证方法2
    外围方法没有报异常,内层第二个service报异常了
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    数据库没有数据,就是在外层方法有事务的情况下,虽然service1的事务并没有报错,但是,两个事务都回滚了。
    在这里插入图片描述
    验证方法3
    外围方法有事务,内层方法的service2报异常在这里插入图片描述
    执行结果:
    在这里插入图片描述
    打印方法回滚,数据并未插入
    在这里插入图片描述
    结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

2.PROPAGATION_REQUIRES_NEW

代码调整

User1ServiceImpl.java增加addRequiredNew方法,事务传播级别为: Propagation.REQUIRES_NEW
在这里插入图片描述
User2ServiceImpl.java增加addRequiredNew和addRequiredExceptionNew方法,事务传播级别为Propagation.REQUIRES_NEW
在这里插入图片描述

  • 场景1
    外围方法没有开启事务
    验证方法1:
    外围方法有异常,内层方法没有异常
    在这里插入图片描述
    执行结果:
    在这里插入图片描述
    数据插入成功
    在这里插入图片描述

验证方法2:
外层方法没有异常,内层service2有异常
在这里插入图片描述
执行结果:
在这里插入图片描述
张三插入成功,插入李四的事务插入回滚
在这里插入图片描述
结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

由于篇幅原因,其他传播级别请参考:https://github.com/TmTse/transaction-test

2.2. 事务的隔离级别

// TransactionDefinition.java

/**
 * 【Spring 独有】使用后端数据库默认的隔离级别
 *
 * MySQL 默认采用的 REPEATABLE_READ隔离级别
 * Oracle 默认采用的 READ_COMMITTED隔离级别
 */
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;
/**
 * 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
 *  * 但是这将严重影响程序的性能。通常情况下也不会用到该级别。
 */
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

简单点说事务的隔离级别有:
1.读未提交(READ UNCOMMITTED)

2.读提交(READ COMMITTED)

3.可重复读(REPEATABLE READ)

4.可串行化(SERIALIZABLE)

可能出现的问题

1.脏读
脏读即为事务1第二次读取时,读到了事务2未提交的数据。若事务2回滚,则事务1第二次读取时,读到了脏数据

2.不可重复读
不可重复读与脏读逻辑类似。主要在于事务2在事务1第二次读取时,提交了数据。导致事务1前后两次读取的数据不一致。

3.幻读
幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行,幻读仅专指“新插入的行”。

猜你喜欢

转载自blog.csdn.net/yujing1314/article/details/107310407
今日推荐