spring 事务有两种 注解型事务和编程式事务。
事务不生效
1.同一个Service中的方法调用(编程式事务无此问题)
2. 多线程调用
3. 未被spring管理(类未加@Service注解)
4. 数据库表不支持事务
5. 项目未开启事务(springboot 项目 默认开启事务)
6. 出现的异常被java代码捕获到了,而没有被事务获取到异常
注解型事务 @Transactional (简单的场景可以使用)
在Spring中对于事务的传播行为定义了七种类型分别是:
(重要)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;
});
事务的隔离级别
脏读幻读不可重复读详解
问题场景复现 : 一个大事务中间有多次查询数据的操作,另外一个小事务,在大事务读取数据过程中,也在操作这些数据,由此造成大事务读取出现了问题。
mysql 数据库 默认的事务隔离级别是可重复读,使用的是MVCC 解决不可重复读的问题。
具体 : 每一行数据都有一个version 在查询和修改数据的时候,都会对版本进行过滤,不符合的数据版本则不会被查询到。
参考 spring 事务传播机制详解
spring事务失效场景及解决办法
spring事务实现原理