Spring事务太强大了,兼容数据库同时给我们提供多种组合应对业务需求

2,537 阅读5分钟

事物的由来

在mysql中只有innodb存储引擎才支持事物,所以我们后续都是基于innodb来展开的

事物特性

  • 事物是用来保证数据的完整性的,保证批量sql执行的统一性;事物具有四个特性: A(Atomicity)、C(Consistency)、I(Isolation)、D(Durability)

  • 原子性

    一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  • 一致性

    在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

  • 隔离性

    数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。

  • 持久性

    事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

  • 基于四个特性,我们就需要在数据和性能进行权衡,需要比较下场景。在m y s q l中就存在四种隔离级别。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

隔离级别脏读(Dirty Read)不可重复读(NonRepeatable Read)幻读(Phantom Read)
未提交读(Read uncommitted)可能可能可能
已提交读(Read committed)不可能可能可能
可重复读(Repeatable read)不可能不可能可能
可串行化(Serializable )不可能不可能不可能
  • 之前我一直错误的认为mysql中默认的是读已提交。后来查阅资料才知道默认的是可重复读隔离级别。

什么事脏读、不可重复读、幻读

  • 上面提到了三种异常数据情况,作为一名合格的程序员我们需要知道不同的隔离级别会给我们造成具体什么样的伤害。

    脏读

    • 脏读指的是A事物在操作数据,还未进行提交此时B事物就已经感知到数据的变化了。这是我们最不能接受的。这里以个人举例,大学期间看中一款手机但是囊中羞涩需要爸妈支援下,打电话让爸妈赚钱这里我们理解成A事物,我着急用钱我是B事物,此时A事物向zxh这张表新增了money=5000元的一条数据。B事物在修改zxh这张表中money这个字段。如果我们是读未提交隔离级别。那么A事物刚新增完事物还未提交,B事物却已经可以修改了。那么B事物修改成功后就结束了。这个时候如果A事物因为别的原因出现异常了。那么就需要将add的5000进行回滚,回滚后余额出现负数。这个负数对于银行来说就是坏账。

image-20220123112618285.png

#### 不可重复读

-   顾名思义就是同一条数据多次读取并不能保证数据一致性。这是因为A事物第一次读完数据后B事物修改了相同的数据行。那么A事物在此读取该数据就会发生不可重复读。

#### 幻读

-   不可重复读是针对同一条数据内容的变化。而幻读针对的是表记录的变化。还是A事物第一次查询数据匹配到10条,第二次相同条件匹配到的是20条。这就是幻读。

查询

s q l功能
select @@tx_isolation;查询数据库默认隔离级别
select @@global.tx_isolation;查看系统当前隔离级别
set session transaction isolation level repeatable read;设置当前会话隔离级别
set global transaction isolation level repeatable read;设置系统当前隔离级别
  • mysql 四种隔离级别对应关系 ; 设置时候去掉破折号就行了

REPEATABLE-READ

READ-COMMITTED

READ-UNCOMMITTED

SERIALIZABLE

  • 我们也可以通过show来查看相关配置

show variables like '%iso%';

spring事物

 @Override
 public Map<String, Object> startTransaction(Transmodel transmodel) {
   int insert = transMapper.insert(transmodel);
   log.info("操作日志:" + insert);
   if ("zxhtom".equals(transmodel.getName())) {
     throw new RuntimeException("error");
   }
   return new HashMap<>();
 }
  • 上面代码逻辑非常的简单,当我们传送如下参数时就会报错
   {
     "name": "zxhtom",
     "code": "56",
     "age": 34
 }
  • 但是呢?我们再去数据库查看,发现该数据进入数据库了。

image-20220123165751122.png

  • 发生这种场景时不行的,在互联网中我们要求部分接口必须具有原子性,中途报错之前所有的操作就必须回滚。如果我们自己使用jdbc那么直接通过连接对象rollback就行了。
  • 在spring中因为连接session对接管了,我们就不必那么麻烦了,直接通过@Transactional标注我们的方法就可以实现事物了。
 @Transactional
 public Map<String, Object> startTransaction(Transmodel transmodel) {
   int insert = transMapper.insert(transmodel);
   log.info("操作日志:" + insert);
   if ("zxhtom".equals(transmodel.getName())) {
     throw new RuntimeException("error");
   }
   return new HashMap<>();
 }
  • 在此执行就会发现,数据并没有新增,这就是我们想要的效果

spring事物有哪些可配项

image-20220123170516789.png

  • 里面有几个属性我们需要关注下。已经圈出来了。

image-20220123171940651.png

字段作用
value事物管理器;用于提交和回滚的功能;默认的DataSourceTransactionManager内部时datasource来操作提交回滚的
transactionManager事物管理器;用于提交和回滚的功能;默认的DataSourceTransactionManager内部时datasource来操作提交回滚的
propagation传播属性;spring中方法内方法事物传递属性设置
isolation隔离级别,在数据库基础上新增一个默认以数据库隔离级别为准
timeout超时事物不提交
readOnly这个感觉没啥意义,被标注的方法无法执行更新操作,只能select
rollbackFor导致事务回滚的异常类数组
rollbackForClassName导致事务回滚的异常类名字数组
noRollbackFor不会导致事务回滚的异常类数组
noRollbackForClassName不会导致事务回滚的异常类名字数组

传播属性

  • 传播属性spring为我们建立了枚举
 public enum Propagation {
 ​
   REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
 ​
   
   SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
 ​
   
   MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
 ​
   
   REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
 ​
   
   NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
 ​
 ​
   NEVER(TransactionDefinition.PROPAGATION_NEVER),
 ​
   
   NESTED(TransactionDefinition.PROPAGATION_NESTED);
 ​
 ​
   private final int value;
 ​
 ​
   Propagation(int value) {
     this.value = value;
   }
 ​
   public int value() {
     return this.value;
   }
 ​
 }

山水有相逢,咱们【隔离级别】继续

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。