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 如果当前有事务则新建一个子事务加入当前事务(父事务),没有事务则新建事务。
@Transactional(rollbackFor=Exception.class),如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚。
(重要)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表中有数据插入
如果去掉serviceB中的事务注解,则testA testB 是属于同一个事物。
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;
});
事务的隔离级别
- 读未提交 read uncommit 最低的隔离级别
- 读已提交 read commited
- 可重复读 read repeat
- 串行化 最高的隔离级别
- default 默认以数据库的隔离级别为准。(mysql 默认是 可重复读)
脏读幻读不可重复读详解
问题场景复现 : 一个大事务中间有多次查询数据的操作,另外一个小事务,在大事务读取数据过程中,也在操作这些数据,由此造成大事务读取出现了问题。
- 脏读 大事务读取到了小事务未提交的数据,并且使用了这一错误数据。
- 幻读 大事务多次读取的数据总量不一致。强调数据总量。
- 不可重复读 大事务多次读取的数据内容不一致 强调数据内容。
mysql 数据库 默认的事务隔离级别是可重复读,使用的是MVCC 解决不可重复读的问题。
具体 : 每一行数据都有一个version 在查询和修改数据的时候,都会对版本进行过滤,不符合的数据版本则不会被查询到。
- 多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复。
- 读乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,如果没有就提交,如果有就放弃并重试。乐观并发控制类似自选锁。乐观并发控制适用于低数据争用,写冲突比较少的环境。