spring事务源码解析
1、 数据库事务特性
1.1 ACID特性
事务(Transaction)是数据库系统中一系列操作的一个逻辑单元,所有操作要么全部成功,要么全部失败。
事务是区分文件存储系统(关系型数据库)与Nosql数据库的重要特性之一,其存在的意义是为了保证即使在并发的情况下也能正确执行crud操作。怎样才算是正确呢?此时提出了事务需要保证的四个特性 ACID
l A:原子性(Atomicity)
原子性指 一个事务中的所有操作要么全部成功,要么全部失败,不会结束在中间的某个环节。事务在执行过程中发生错误,会回滚到事务开始之前的状态,就像这个事务从来没有执行一样;
l C:一致性(consistancy)
一致性指在事务开始之前和事务结束之后,数据库的完整性没有被破坏。这标识写入的资料必须完全符合所有的预设规则,这包含资料的精准度、串联性以及后续数据库可以自发的完成预定的工作。
l I:隔离性(isolation)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致的数据不一致。事务隔离分为不同的隔离级别:读未提交,读已提交(指一个事务是否能读到其他事务未提交的事务),可重复读(一个事务内按照同一个条件多次读取的时候,读到的数据是否一致);串行化(所有事务按照顺序,逐个执行)
l D:持久性(durability)
事务执行结束后,对数据的修改是永久的,即使系统故障也不会丢失。
1.2 事务的隔离级别
在高并发的情况下,要完全保证事务的ACID是非常困难的,除非把所有的事务串行化执行,但带来的负面影响是性能的大打折扣,很多时候我们的业务对事务的要求是不一样的,所以数据库设计了4中隔离级别,以满足不同的业务场景。
数据库默认的事务隔离级别:
Mysql默认的隔离级别是:可重复读,Repeatable Read
Oracle默认的隔离级别是:读已提交, read commited
不用的隔离级别可能存在的问题:
隔离级别 脏读 不可重复读 幻读
未提交读 可能 可能 可能
已提交读 不可能 可能 可能
可重复读 不可能 不可能 可能
串行化 不可能 不可能 不可能
脏读:一个事务读取到另外一个事务没有提交的事务
不可重复读:在同一个事务中多次读取同一个数据返回的结果不同;换句话说 后续读取操作读取到了 其他事务已提交的更新后的数据。
事务B修改数据导致当前事务A前后读取的结果不一致;侧重点在于事务B的修改。
当前事务读取到了其他事务已提交的事务;
幻读:查询表中一条数据,如果不存在就插入一条;并发的时候发现数据库中有2条相同的数据;
事务A修改表中的数据,此时事务B插入一条新的数据,事务A查询的时候发现表中还存在没有修改的数据,像是出现幻觉;
事务A读取到了事务B新增的数据,导致结果不一致,侧重点在于事务B新增数据。
2 spring事务及源码分析
2.1 spring事务相关的API
l 支持数据库原有事务的隔离级别加入了事务传播的概念
l 提供多个事务的合并和隔离的功能
l 提供声明式事务,让事务与业务代码分离,事务变得更简单易用
Spring提供了事务相关接口:
n TransactionDefinition
事务定义:定义了事务的隔离级别和事务的传播行为
n TransactionAttribute
事务属性:实现了对回滚规则的扩展(异常回滚,spring只支持 RuntimeException和error级别的异常回滚)
n PlatformTransactionManager(接口,有多个实现类)
事务管理器:
提供了3个方法:
public interface PlatformTransactionManager { //获取事务 TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; //提交事务 void commit(TransactionStatus status) throws TransactionException; //回滚事务 void rollback(TransactionStatus status) throws TransactionException; }
常用的实现类:
-
- DataSourceTransactionManager 基于数据库的事务管理器因为spring的事务是基于数据库的,所以在DataSourceTransactionManager事务管理器中,有数据库连接的相关代码;说明:如果一个事务中涉及到了A,B 跨库操作,A执行成功,B异常导致事务回滚,此时A库已经写入的数据也不会回滚。
- JtaTransactionManager 分布式事务管理器
n TransactionStatus 事务运行时状态
public interface TransactionStatus extends SavepointManager, Flushable { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); @Override void flush(); boolean isCompleted(); }
相关实现类:
TransactionInterceptor
事务拦截器,实现了MethodInterceptor
TransactionAspectSupport
事务切面支持, 内部类TransactionInfo封装了事务相关属性
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {}
该方法中完成了对方法的增强,以及对声明式事务和编程式事务的不同处理。
@Transactional:事务配置注解作用于类和方法上,
属性名 说明
Name 当配置文件中有多个事务管理器(TransactionManager)时,可以用该属性指定使用哪个事务管理器。
Propagation 事务传播行为,默认是 Required(如果存在事务,使用当前事务;如果不存在,创建一个事务)
Isolation 隔离级别,默认使用数据库的隔离级别
Timoout 事务的超时时间,默认为-1。如果超过该时间限制,事务还没有执行完成,则自动回滚事务。
Read-only 指定事务是否为只读事务,默认值为false;为了忽略那些不需要事务的方法,比如读数据,可以设置read-only为true
Rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各个类型之间可以用逗号分隔。
No-rollback-for 抛出no-rollback-for类型的异常,事务不会回滚
2.3 Spring事务失效问题
- Bean是否是代理对象 (声明式事务依赖于AOP)
- 入口函数是否是public的
详见 spring 事务源码:CglibAopProxy#intercept方法
- 数据库是否支持事务(Mysql的MyIsam不支持事务) ,行锁才支持事务
- 切点是否配置正确 (切点配置 add*,在insert方法上使用事务)
- 内部方法间调用导致事务失效
因为this不是代理对象,可以配置 expose-proxy="true" ,就可以通过 AopContext.currentProxy()获取 到当前类的代理对象。
proxy-target-class = true 指定使用cglib代理,默认为false,使用jdk动态代理;
<!-- expose-proxy="true" 类内部可以获取到当前类的代理对象 -->
<aop:aspectj-autoproxy proxy-target-class=”true” expose-proxy="true"/>
@EnableAspectJAutoProxy(exposeProxy = true) 也可以注入当前bean
-
异常类型是否配置正确
默认只支持 RuntimeException和Error ,不支持检查异常 想要支持检查异常需配置rollbackFor;
@Transactional(rollbackFor = Exception.class)
异常体系:
源码分析:
#找事务拦截器 TransactionInterceptor#invoke
# 事务相关的调用 TransactionAspectSupport#invokeWithinTransaction
#异常回滚的逻辑 TransactionAspectSupport#completeTransactionAfterThrowing
#异常回滚 txInfo.transactionAttribute.rollbackOn(ex)
#可以设置异常回滚规则 RuleBasedTransactionAttribute#rollbackOn
# 默认的异常回滚规则 DefaultTransactionAttribute#rollbackOn
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error); }
spring事务执行流程图:
事务的传播行为:
常用事务传播机制:
PROPAGATION_REQUIRED 这个也是默认的传播机制;
PROPAGATION_REQUIRES_NEW 总是新启一个事务,这个传播机制适用于不受父方法事务影响的操作,比如某些业务场景下需要记 录业务日志,用于异步反查,那么不管主体业务逻辑是否完成,日志都需要记录下来,不能因为主 体业务逻辑报错而丢失日志;
PROPAGATION_NOT_SUPPORTED 可以用于发送提示消息,站内信、短信、邮件提示等。不属于并且不应当影响主体业务逻辑,即使 发送失败也不应该对主体业务逻辑回滚
思考:
为什么在类或者方法上使用 @Transacational注解,事务就生效了??
思路: spring aop 切点的配置:
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)");aop会针对使用 @Transactional注解的方法生效;