Spring事务你知多少(来自图灵学院 公开课)
课程要点:
- Spring事务介绍
- 案例分析及本质挖掘
- 说一说跳过的坑
Spring事务介绍
事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败。
事务的特性:
- 原子性: 是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
- 一致性:是指事务前后数据的完整性必须保持一致
- 隔离性:是指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发事务之间数据要相互隔离
- 持久性:是指一个事务一旦被提交,它对数据库中数据的改变是永久性的,即使数据库发生故障也不应该对其有任何影响
接口介绍:
spring主要通过以下三个接口对事务进行管理:
1、PlatformTransactionManager 事务管理器
实现类:DataSourceTransactionManager(Spring JDBC或iBatis持久化数据时)
HibernateTransactionManager(Hibernate3.0持久化数据时)
JdoTransactionManager(持久化机制为Jdo时)
JpaTransactionManager (JPA持久化时)
....
Spring为不同的持久化框架提供了不同PlatformTransactionManager接口实现
2、TransactionDefinition 事务定义信息(隔离、传播、超时、只读)
3、TransactionStatus 事务具体运行状态
隔离级别
解决事物的安全性问题:脏读、不可重复读、幻读
1、DEFAULT 使用数据库默认的隔离级别(spring中的选择项)
2、READ_UNCOMMITTED 允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
3、READ_COMMITTED 允许在并发事务已经提交后读取。可防止脏读, 但幻读、不可重复读仍可能发生(Oracle默认)
4、REPEATABLE_READ 对相同字段的多次读取是一致的,除数据对事务本身改变。可防止脏、不可重复读,但幻读仍可能发生 (MySQL默认)
5、SERIALIZABLE 完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。
知识小贴士:
脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同
幻读:一个事务读取了几行记录后,另一个事务插入 一些记录,幻读就发生了,
再后来的查询中,第一个事务就会发现有些原来没有的记录
事务传播行为
1、PROPAGATION_REQUIRED
支持当前事务,如果不存在,就新建一个
2、PROPAGATION_SUPPORTS
支持当前事务,如果不存在,就不使用事务
3、PROPAGATION_MANDATORY
支持当前事务,如果不存在,就抛出异常
4、PROPAGATION_REQUIRES_NEW
如果当前线程有事务存在则挂起当前事务继续执行,然后创建一个新的事务继续执行,当这个事物执行完毕,在唤醒之前挂起的事物:如果当前不存在事物则新建事物
5、PROPAGATION_NOT_SUPPORTED
以非事务方式运行,如果有事务存在,挂起当前事务
6、PROPAGATION_NEVER
以非事务方式运行,如果有事务存在,抛出异常
7、PROPAGATION_NESTED
如果当前事务存在,则嵌套事务执行
案例分析及本质挖掘
经典案例
ServerA{
@事务
void methodA(){
serverB.methodB();
}
}
ServerB{
@事务
void methodB() { }
}
在该场景下,会产生几个事务?为什么?
答:这个是看是看事物的传播行为 ,默认只会创建一个事物,因为sping默认的传播行为为:PROPAGATION_REQUIRED
本质挖掘
事务传播行为的源码分析
AbstractPlatformTransactionManager.getTransaction()/handleExistingTransaction()
PROPAGATION_REQUIRES_NEW(会创建2个事务) 新建事务是否与外部事务互相独立?
AbstractPlatformTransactionManager.rollback()
说一说跳过的坑
场景一:B方法运行时异常。
ServerA{
@Transactional
void methodA(){
serverB.methodB();
}
}
ServerB{
@Transactional
void methodB() {
运行异常
}
}
处理方法:
1、不做处理
2、B方法抛出异常,由A方法内部处理(演示 REQUIRES_NEW ,再演示 Required)
(1)新建事物 如果里面那个事物(PROPAGATION_REQUIRES_NEW)抛异常的话,虽说每个事物都是独立的互不影响 但是这里两个事物都会回滚 看源码
(2)共享事物 如果里面那个事物(PROPAGATION_REQUIRED)抛异常的话,这样就会同时成功 同时失败 因为他们是同一个事物
3、B方法内部处理(演示 Required)
共享事物: 不会回滚 原因:因为我们这个事物是基于aop管理的 aop 会在业务方法的前后会代理到我们的这个事物而我们这个事物在这个方法的前后没有发现异常 (异常在方法内部处理了 异常被自己吞掉了 )所以就提交了事物
4、两个方法都抛出异常
场景二:A方法运行时异常。
ServerA{
@Transactional
void methodA(){
serverB.methodB();
运行异常
}
}
ServerB{
@Transactional
void methodB() {}
}
处理方法:
1、不做处理
2、A方法内部处理
3、A方法抛出异常
Spring事务处理总结
场景一
1、不做处理
新建事务:影响外部事务,影响自己
嵌套事务:影响外部事务,影响自己
共享事务:影响外部事务,影响自己
2、B方法抛出异常,由A方法内部处理
新建事务:不影响外部事务,影响自己(requires_new)
嵌套事务:不影响外部事务,影响自己(nested)
in(){
//savepoint 创建保存点
//创建新事务
out(){
}
//外部事务提交了,内部事务才提交
}
讲解此案例,与新建事务似乎一样,再引出场景二
共享事务:影响外部事务,影响自己
3、B方法内部处理
不影响任何事务
4、两个方法都抛出异常
等同不做处理
场景二
1、不做处理
新建事务:影响自己,不影响内部事务(后演示)
嵌套事务:影响自己,影响内部事务(先演示)
因为:我们内部事务提交是由外部事物决定的 外部事物提交,内部事务才提交
共享事务:影响自己,影响内部事务
2、A方法内部处理
新建事务:全部不影响
嵌套事务:全部不影响
共享事务:全部不影响
3、A方法抛出异常
等同不做处理
需要注意的坑
- 不要随意使用事务的传播属性
- 不要随意对事务中的代码进行try/catch操作
- 不要随意让事务方法抛出异常
文档地址:https://shimo.im/docs/5fV8mg6ai48jrKtq