Spring事务是不仅是面试中必考题,也是工作中经常用到的知识点。
学习Spring事务可以从@Transational注解开始,咱们可以先观察一下该注解包含哪些参数:
- transationManager:事务管理器
- Propagation:事务传播方式
- Isolation:事务隔离级别
- timeout:超时时间
- rollbackFor:捕捉到哪些异常回滚
- noRollbackFor:捕捉到哪些异常不回滚
从这些参数中我们可以看出,Spring事务的关键点就是事务管理器、传播方式、隔离界别上。
事务管理器
spring中定义了一个TransactionManager接口,以及数个实现类。
从spring5.2开始,支持了ReactiveTransactionManger,如果不使用响应式数据库驱动,用不上这个实现。
因此,我们主要先学习PlatformTransactionManger,Spring主要提了五个事务管理器实现:
- DataSourceTransactionManger
- HibernateTransactionManager
- JdbcTransactionManager
- JpaTransactionManager
- JtaTransactionManager
DataSourceTansactionManager和JdbcTransactionManager功能基本一致,并且从3.0开始Spring官方推荐使用DatasourceTansactionManger替换JdbcTransactionManger。其特点就是基于jdbc实现,轻量,缺点就是不支持JPA框架的一些特性。
HibernateTransactionManger使用sessionFactory实现,与Hibernate强相关。
JpaTransactionManger基于JPA标准实现,凡事实现JPA标准的ORM组件均可使用,包括Hibernate。因此,使用Spring data Jpa的时候,默认是用该事务管理器。
JtaTransactionManager, 用于支持分布式事务,支持XA协议。
什么是XA协议?
XA协议是一种分布式事务协议,其中定义了三个角色:
- AP:应用程序,就是发起事务的程序
- TM:事务管理器,协调全局事务,负责执行提交或者回滚任务
- RM:资源管理器,实现了XA协议的数据库、消息队列等资源服务
XA协议的核心是两段式提交(2PC):
- AP发起事务
- TM向所有RM发送准备提交的指令
- RM执行事务,但不提交,并返回“就绪”或者“错误”
- TM收到RM的消息,如果所有都是“就绪”,则进行提交
- 如果存在一个“错误”,则回滚事务
事务传播方式
事务传播方式是Spring的行为,与具体数据库无关,Spring提供了7类事务传播方式:
REUIRED:默认的传播行为,如果已经存在事务,则加入当前事务,否则创建新事务
SUPPORTED:如果存在事务,则加入当前事务,否则不以事务方式执行
MADITORY:如果存在事务,则加入事务,否则抛出异常
REQUIRE_NEW:不管是否存在事务,新建事务,挂起原事务
NOT_SUPPORTS:不管是否存在事务,都不使用事务,挂起原事务
NERVER:不使用事务方式执行,并且如果存在事务,抛出异常
NESTED:一般情况下,与REQUIRED一样。如果事务管理器支持“嵌入事务”,则当前事务存在时,会创建“嵌入事务”,嵌入事务可以独立提交回滚。
事务隔离级别
事务隔离级别是指数据库的事务隔离级别,Spring中的隔离级别实质上是数据库隔离级别的一种映射。
在@Transactional中指定隔离级别会在执行事务中映射为数据库支持的隔离级别,这里也就需要考虑数据库是否支持对应的隔离级别,否则可能会报错。
- Mysql默认支持Repeatable_read级别
- Postgresql默认支持Read_commited级别
在谈论事务隔离级别之前,需要先知道几个概念:
- 脏读:事务A新增或者更新了某个事务,但未提交。此时事务B读取到了数据,并进行相关业务;事务A回滚了,事务B读取到的数据则是无效数据。
- 不可重复读:是指同一事务中多次读取同一条数据,结果不一致的情况。例如:事务B先查询了某条数据,开始使用数据;然后事务A提交了,然后数据发生改变;事务B再次读取数据发现数据不一致的情况。
- 幻读:是指同一事务中多次查询数据结果条数不一致的情况,主要原因是在事务中,有其他事务对该数据范围进行了增加或者删除操作。
这三个问题着重点有所差异,脏读主要是关注事务回滚问题;不可重复读是关注事务中数据被其他事务修改的问题;幻读则是关注事务中其他事务新增或删除了数据。
接下来,就需要说一下数据库的事务隔离级别了:
- READ_UNCOMMITED:读未提交,这种级别会出现脏读、不可重复读、幻读的情况
- READ_COMMITED:读已提交,根据上述概念中可以得知,这种级别不会出现脏读的情况,但是任然无法解决不可重复度、幻读的情况。
- REPEATABLE_READ: 可重复读,解决了不可重复度的情况,但是依然无法解决幻读的情况。(mysql通过MVCC解决了幻读的问题)
- SERIALIZABEL:将一切数据库操作串行化,完全杜绝了脏读、不可重复读、幻读的情况。但是这种级别的性能极低,非特殊情况不考虑这种级别。
REAPETABLE_READ隔离级别如何解决不可重复读的问题?
主要通过锁或者MVCC来解决不可重复读的问题:
- 锁机制:阻止其他事务修改当前事务已读取的数据,以保证多次读取的一致性。读锁为共享锁,但是排斥写锁。
- MVCC:通过创建版本快照的方式,保证多次读取一致性。其特点是无锁机制,延迟可读。另外,可以解决幻读的问题。