spring 事务详解

发布于:2024-04-29 ⋅ 阅读:(25) ⋅ 点赞:(0)

spring 事务有两种 注解型事务和编程式事务。

事务不生效

1.同一个Service中的方法调用(编程式事务无此问题)

根源: 直接调用了 this 对象的方法,无法通过Spring动态代理,所以相应的方法无事务支持。
解决方案 新加一个Service 因为spring事务是介于Service层之间的,ServiceA中的A方法调用ServiceB中的B方法,这样才能让事务生效。

2. 多线程调用

解决方案 不要在一个事物中使用多线程,也就是说 指令操作要在同一个数据库连接和一个主线程基础上

3. 未被spring管理(类未加@Service注解)

解决方案 加上@Service注解注解

4. 数据库表不支持事务

解决方案 选择合适的数据库表以支持事务

5. 项目未开启事务(springboot 项目 默认开启事务)
6. 出现的异常被java代码捕获到了,而没有被事务获取到异常

注解型事务 @Transactional (简单的场景可以使用)

在Spring中对于事务的传播行为定义了七种类型分别是:

REQUIRED(默认) 如果当前有事务,则加入,没有事务,则新建事务。
SUPPORTS 如果当前有事务,则加入,没有事务,则以非事务的方式执行。
MANDATORY 如果当前有事务,则加入,没有事务则抛出异常
REQUIRES_NEW 新建一个事务,如果当前有事务则挂起当前事务
NOT_SUPPORTED 以非事务的方式执行指令 如果当前有事务,则挂起事务
NEVER 以非事务的方式执行指令 如果当前有事务,则抛出异常。
NESTED 如果当前有事务则新建一个子事务加入当前事务(父事务),没有事务则新建事务

(重要)REQUIRED(Spring默认的事务传播类型)

如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务

serviceA中
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
    A(a1);  //调用A入参a1  向A表插入数据
    service.testB();    //调用另外一个serviceB 的 testB方法
}

serviceB中
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
    B(b1);  //调用B入参b1 向B表插入数据
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2 向B表插入数据
}

此时 因为 testB() 发生异常 并且 事务类型为 required类型 是属于同一个事务的,即A表和B表都不会插入数据,同一事物发生异常则回滚。

如果去掉 serviceA中 的 @Transactional(propagation = Propagation.REQUIRED)
则是testB()方法具有事务性,发生异常后无任何数据插入,而testA() 无事务 A表中有数据插入

SUPPORTS

当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

serviceA中
public void testMain(){
    A(a1);  //调用A入参a1  向A表插入数据
    service.testB();    //调用另外一个serviceB 的 testB方法
}

serviceB中
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
    B(b1);  //调用B入参b1 向B表插入数据
    throw Exception;     //发生异常抛出
    B(b2);  //调用B入参b2 向B表插入数据
}

这种情况下,执行testMain的最终结果就是,a1,b1存入数据库,b2没有存入数据库。由于testMain没有声明事务,且testB的事务传播行为是SUPPORTS,所以执行testB时就是没有事务的(如果当前没有事务,就以非事务方法执行),则在testB抛出异常时也不会发生回滚,所以最终结果就是a1和b1存储成功,b2没有存储。

那么当我们在testMain上声明事务且使用REQUIRED传播方式的时候,这个时候执行testB就满足当前存在事务,则加入当前事务,在testB抛出异常时事务就会回滚,最终结果就是a1,b1和b2都不会存储到数据库。

编程式事务

建议直接使用编程式事务,这样写出来的代码可读性好,可扩展性强,可维护性高 粒度小 可控性强,耦合度比较高,但是一定要注意spring事务的失效场景 spring事务失效场景及解决办法
例子:

Service@Resource
private TransactionTemplate transactionTemplate;

// 在需要有事务支持的代码中 一般是写入和更新 直接编程式事务写代码 例如
transactionTemplate.execute((s)->{
    // 需要事务支持的代码块  插入和更新操作
   return Boolean.TRUE;
});

事务的隔离级别

  1. 读未提交 read uncommit 最低的隔离级别
  2. 读已提交 read commited
  3. 可重复读 read repeat
  4. 串行化 最高的隔离级别
  5. default 默认以数据库的隔离级别为准。(mysql 默认是 可重复读)

脏读幻读不可重复读详解

问题场景复现 : 一个大事务中间有多次查询数据的操作,另外一个小事务,在大事务读取数据过程中,也在操作这些数据,由此造成大事务读取出现了问题。

  1. 脏读 大事务读取到了小事务未提交的数据,并且使用了这一错误数据。
  2. 幻读 大事务多次读取的数据总量不一致。强调数据总量。
  3. 不可重复读 大事务多次读取的数据内容不一致 强调数据内容。

mysql 数据库 默认的事务隔离级别是可重复读,使用的是MVCC 解决不可重复读的问题。
具体 : 每一行数据都有一个version 在查询和修改数据的时候,都会对版本进行过滤,不符合的数据版本则不会被查询到。

  1. 多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复。
  2. 读乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,如果没有就提交,如果有就放弃并重试。乐观并发控制类似自选锁。乐观并发控制适用于低数据争用,写冲突比较少的环境。

参考 spring 事务传播机制详解
spring事务失效场景及解决办法
spring事务实现原理