前言
了解整个事务的执行过程,那么就必须要要先了解事务的传播特性、隔离级别基础知识,以此为前提,才能知晓其下是如何贯通在一起使用的
传播特性简要
传播特性有以下七种,传播属性默认值为 REQUIRED:当前存在事务,就使用当前事务,否则创建一个新的事务
隔离级别简要
事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:
- 脏读:一个事务读到另一个事务未提交的更新数据,所谓脏读,就是指事务A读到了事务 B 还没有提交的数据,比如银行取钱,事务 A 开启事务,此时切换到事务 B,事务 B 开启事务–>取走100元,此时切换回事务 A,事务A读取的肯定是数据库里面的原始数据,因为事务 B 取走了 100 块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读
- 幻读:指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样
- 不可重复读:在一个事务里面的操作中发现了未被操作的数据,比方说在同一个事务中先后执行两条一模一样的 select 语句,期间在此次事务中没有执行过任何 DDL 语句,但先后得到的结果不一致,这就是不可重复读
Spring 中支持的隔离级别
- DEFAULT:使用数据库本身使用的隔离级别->ORACLE(读已提交)、 MySQL(可重复读)
- READ_UNCOMITTED:读未提交(脏读)最低的隔离级别,一切皆有可能
- READ_COMMITED:读已提交,ORACLE默认隔离级别,有幻读以及不可重复读风险
- REPEATABLE_READ:可重复读,解决不可重复读的隔离级别,但还是有幻读风险
- SERLALIZABLE:串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
再必须强调一遍,不是事务隔离级别设置得越高越好,事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,那么效率就要降低,因此实际开发中往往要在效率和并发正确性之间做一个取舍,一般情况下会设置为 READ_COMMITED,此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了
如何设计一个事务系统
如果交给我们自己来设计一个事务系统时,一般分以下几个批次进行处理
- 创建一个事务并且提前将事务的基本信息准备好,包括:事务属性、状态、管理器信息、数据库连接
- 执行我们程序内部的业务逻辑
- 判定在程序运行时是否发生了异常
- 如果没发生异常,事务正常执行,并且将事务相关信息给清除,避免资源的消耗,commit 提交事务
- 如果发生了异常,事务异常执行,清除事务的相关信息,并且恢复到事务前的一个状态,rollback 回滚事务
- 最终完成事务的整个过程,最终释放数据库连接
事务执行流程 source coding
在执行程序业务逻辑前,如果其被事务代理所修饰,会代理执行 DynamicAdvisedInterceptor#intercept 方法,获取到拦截器责任链 advisor 后,链式调用 proceed 方法;整个事务处理过程中,包含了以下 advisor
- ExposeInovacationInterceptor :为了方便责任链的调用,作为前驱,联系者,协调其他 advisor 的运行
- DefaultBeanFactoryPointcutAdvisor:其父类为 AbstractBeanFactoryPointcutAdvisor,内部拥有 advice,会在组装拦截器时调用 getAdvice 方法获取到 TransactionInterceptor 拦截器
- 最后组装完以后的拦截器链:ExposeInovacationInterceptor —> TransactionInterceptor
核心类 TransactionInfo
protected static final class TransactionInfo { @Nullable private final PlatformTransactionManager transactionManager; // 事务管理器 @Nullable private final TransactionAttribute transactionAttribute; // 事务属性:传播机制、隔离级别、超时时间、是否只读 private final String joinpointIdentification; @Nullable private TransactionStatus transactionStatus; // 事务状态:是否为新事务、是否是需要新同步、挂起的连接资源 @Nullable private TransactionInfo oldTransactionInfo; // 同一个线程内旧的事务信息 public TransactionInfo(@Nullable PlatformTransactionManager transactionManager, @Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) { this.transactionManager = transactionManager; this.transactionAttribute = transactionAttribute; this.joinpointIdentification = joinpointIdentification; } public PlatformTransactionManager getTransactionManager() { Assert.state(this.transactionManager != null, "No PlatformTransactionManager set"); return this.transactionManager; } @Nullable public TransactionAttribute getTransactionAttribute() { return this.transactionAttribute; } // 获取方法的全限定名称 public String getJoinpointIdentification() { return this.joinpointIdentification; } public void newTransactionStatus(@Nullable TransactionStatus status) { this.transactionStatus = status; } @Nullable public TransactionStatus getTransactionStatus() { return this.transactionStatus; } // 是否存在事务 public boolean hasTransaction() { return (this.transactionStatus != null); } private void bindToThread() { // 暴露当前的事务状态,将存在的事务进行挂起,等待当前事务完成后再将已存在的事务进行恢复 this.oldTransactionInfo = transactionInfoHolder.get(); transactionInfoHolder.set(this); } private void restoreThreadLocalStatus() { // 将老的事务状态进行重新设定 transactionInfoHolder.set(this.oldTransactionInfo); } @Override public String toString() { return (this.transactionAttribute != null ? this.transactionAttribute.toString() : "No transaction"); } }
调用事务方法的入口会到达 TransactionInterceptor#invoke#invokeWithinTransaction
(以事务的方式调用目标方法,在这埋了一个钩子函数,用来回调目标方法的)事务的整个处理逻辑如下:
- prepare 准备工作:作好事务需要的前期准备工作,获取当前的事务属性源信息以及事务管理器转换为
PlatformTransactionManager
类型「其提供三种能力:1、获取事务状态对象;2、提交事务;3、回滚事务」最后获取该方法的全限定方法名 - 创建事务信息:createTransactionIfNecessary,先根据当前的事务属性创建委托类「DelegatingTransactionAttribute」
- 根据事务属性委托类创建事务状态信息,分为以下几种情况
- doGetTransaction:获取事务对象信息,默认第一次进来的是没有值的;它主要是从 TransactionSynchronizationManager 事务同步管理器获取到连接持有器,设置
newConnectionHolder=false
并返回数据源事务对象 - isExistingTransaction(transaction):判断当前线程是否存在事务连接持有器,第一次进来的都是没有连接持有器的,所以第二次方法调用时才会进来;如果当前存在事务,就处理存在事务的逻辑,先判别内层事务的传播机制
1、
NERVER(从不使用事务,则存在抛出异常)
,则抛出异常结束2、
NOT_SUPPORTED(不支持事务)
挂起当前事务:将外层事务相关的连接持有器和属性封装为 SuspendedResourcesHolder 返回,并且创建的一个新的非事务的状态,同时将外层事务的挂起资源持有器作为参数一起进行实例化3、
REQURES_NEW(挂起已经存在的事务,开启一个新的事务)
:挂起当前事务后「1.清空线程本地的连接持有器;2.清空线程本地资源的所有资源信息;3.将之前的事务属性和连接器这些信息保存到 oldTransaction 变量里面;4.返回挂起的事务信息」,startTransaction:开启一个新的事务状态,同时将外层事务的挂起资源作为参数一起实例化,newTransaction、newSynchronization 属性值都为 true,开辟一个新的连接、设置好事务同步管理器中的线程本地变量「事务是否激活状态、事务的隔离级别、是否为只读事务、事务名称」4、
NESTED(存在事务就使用,不存在就创建一个新的,并且设置一个保存点)
:通过当前的事务再次构建一个 DefaultTransactionStatus 对象,newTransaction、newSynchronization 属性值都改为 false,并为当前创建好的事务状态对象设置一个保存点.5、
其他类型的传播机制:SUPPORTED、REQURED、MANDATORY
,通过当前的事务再次构建一个 DefaultTransactionStatus 对象,newTransaction、newSynchronization 属性值都改为 false 后返回
commit 流程
如果整个事务正常执行完成,事务就需要正常提交了,执行方法:commitTransactionAfterReturning->AbstractPlatformTransactionManage#commit
「处理提交,先处理保存点,然后处理新事务,如果不是新事务不会真正提交,要等外层是新事务的才提交,最后根据条件执行数据清除、线程的私有资源解绑,重置连接自动提交、隔离级别、是否只读、释放连接、恢复挂起事务等」,以下是处理提交的源码:
private void processCommit(DefaultTransactionStatus status) throws TransactionException { try { boolean beforeCompletionInvoked = false; try { boolean unexpectedRollback = false; // 预留 prepareForCommit(status); // 添加 TransactionSynchronization 中的对应方法的调用 triggerBeforeCommit(status); // 提交完成前回调 triggerBeforeCompletion(status); beforeCompletionInvoked = true; // 有保存点 if (status.hasSavepoint()) { if (status.isDebug()) { logger.debug("Releasing transaction savepoint"); } // 是否有全局回滚标记 unexpectedRollback = status.isGlobalRollbackOnly(); // 如果存在保存点则清除保存点信息 status.releaseHeldSavepoint(); } // 当前状态是新事务 else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction commit"); } unexpectedRollback = status.isGlobalRollbackOnly(); // 如果是独立的事务则直接提交 doCommit(status); } else if (isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback = status.isGlobalRollbackOnly(); } // 有全局回滚标记就报异常 if (unexpectedRollback) { throw new UnexpectedRollbackException( "Transaction silently rolled back because it has been marked as rollback-only"); } } catch (UnexpectedRollbackException ex) { // can only be caused by doCommit triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); throw ex; } catch (TransactionException ex) { // can only be caused by doCommit if (isRollbackOnCommitFailure()) { doRollbackOnCommitException(status, ex); } else { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); } throw ex; } catch (RuntimeException | Error ex) { if (!beforeCompletionInvoked) { triggerBeforeCompletion(status); } // 提交过程中出现异常则回滚 doRollbackOnCommitException(status, ex); throw ex; } try { // 提交后回调 triggerAfterCommit(status); } finally { // 提交后清除线程私有同步状态 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); } } finally { //根据条件,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等 cleanupAfterCompletion(status); } }
无论是正常提交或者发生了异常,一些基本的资源应该被释放,清除当前的事务信息,恢复线程本地老的事务信息
private void restoreThreadLocalStatus() { transactionInfoHolder.set(this.oldTransactionInfo); }