一、前言
按照spring对事务处理的划分,数据库事务处理分为单数据库事务和分布式事务(JTA).而单数据库事务中又根据具体API的不同,区分了datasource、hibernate、jpa、jdo下的事务管理实现。限于篇幅,本文针对datasource的事务处理。
本文假设阅读的同学已经知晓spring事务的配置及使用方式,着重从源码角度分析事务的实现。
多数据源的处理会提到pamirs-transaction. pamirs-transaction是服务市场前辈自行开发并使用的类似于spring transaction的事务组件,与spring不同的是,pamirs transaction提供了对多数据库源(datasource)的支持,以应对分库分表的场景。
下文中的类图及代码片段,可能省略了非重点、或不属于datasource事务处理部分的代码和实现,请知悉。
二、jdbc事务API
说起数据库操作,日常编码中能接触到的最底层也是最常见的当属jdbc。如下是常见的JDBC操作事务的代码。
Connection conn = null; try { Class.forName(DRIVER); conn = DriverManager.getConnection(URL, USERNAME, PASSWORD); conn.setAutoCommit(false);//设置为手动提交事务 String sql1 = "insert into user(name,pwd)values(?,?)"; String sql2 = "update user set pwd=? where name=?"; PreparedStatement ps = conn.prepareStatement(sql1); ps.setString(1, "fulin"); ps.setString(2, "123456"); ps.executeUpdate(); ps = conn.prepareStatement(sql2); ps.setString(1, "654321"); ps.setString(2, "fulin"); ps.executeUpdate(); conn.commit(); //事务提交 ps.close(); conn.close(); } catch (Exception e) { conn.rollback();//只要有一个sql语句出现错误,则将事务回滚 }
jdbc事务api基本概念
通过上文"jdbc事务API",可知jdbc事务的基本概念有如下几点
一个connection代表着一次数据库会话连接,所有SQL操作都是基于connection完成
事务的边界是通过jdbc connection区分的。即对于单数据源事务来说,只有在同一个connection中执行的sql才能归为同一个事务。
事务的隔离级别的最小粒度也是connection(最大粒度是数据库全局配置)
事务的回滚需要手动调用。即使SQL执行中出现了SQL异常,也需要由调用者再外层捕获异常并调用 回滚方法。
jdbc api弊端
由于jdbc事务操作对于日常编码还是太过底层,因此有如下弊端
代码倾入性过高。 需要手动commit或者rollback
不支持跨方法调用。根据上文“jdbc事务的基本要素”可知,对于单数据源事务来说,只有在同一个connection中执行的sql才能归为同一个事务。如果多个业务方法需要加入同一个事务,则需要想办法将connection进行方法间的共享。
不支持嵌套和独立事务等复杂的业务场景。
三、spring transaction事务管理
spring通过对事务的抽象,并解决了上述问题。
spring解决的问题
代码倾入性过高 - 通过AOP配置或者注解方式,将事务处理代码包裹在业务方法周围,实现切面化的事务处理。
跨方法调用 - 将事务的操作抽象成事务管理器,将事务的定义抽象成事务对象。事务的开启、提交、回滚都基于同一个事务管理器。由事务管理器完成相同事务,不同业务方法间connection对象的共享。
嵌套和独立事务 - 提出了“事务传播行为”这一概念。通过不同的传播行为配置,不同的业务方法可通过事务AOP的配置,实现开启新事务、作为嵌套事务或者加入当前事务等行为。
spring事务的对象模型
spring中对事务的主要的抽象分为五个部分。
TransactionDefinition,即将事务的隔离级别、传播行为、超时等配置抽象成事务的配置定义对象。
TransactionObject,即事务对象,不同类型的事务管理器有不同的实现。对于jdbc datasource类型的事务,事务对象实际上是对connection对象的封装。
TransactionStatus,此对象定义了事务执行时的上下文。上下文中存储着当前事务对象、事务执行状态、被挂起的事务等事务执行时的上下文。
transactionManager,事务操作的抽象,事务的开启、挂起、恢复、提交和回滚都通过此事务管理器进行
TransactionInterceptor,事务拦截器。用于事务的AOP实现。
TransactionDefinition
熟悉spring tx的同学都知道,配置事务时,根据切面配置或者切面注解的不同,不同package下、不同class下甚至不同method下都可以单独定义事务的隔离级别、传播行为、超时等参数。TransactionDefinition便是这些配置的抽象。
TransactionObject
此对象代表着从"jdbc事务API"这一段中可知,jdbc类型的不同事务一定包含有不同的connection.此外,由于事务传播行为的不同,每一个事务还需要需要两个状态分别标识这个事务是否需要回滚以及当前事务是否是活跃状态。
spring中DataSourceTransactionObject是datasource方式事务对象的实现,并通过父类中的ConnectionHolder来持有Connection对象以及标识事务的活跃及回滚状态。
TransactionStatus
有了属性配置及事务对象,接下来需要一个事务执行的上下文。上下文中不仅需要包含当前当前事务的相关配置及事务对象,为了满足事务传播行为,还需要保存之前被挂起的事务资源,当前事务执行完毕后需要恢复上一个事务。
另外,事务上下文同样提供了回滚及获取事务活跃状态等操作,这部分操作实际上是委派给具体的transactionObject来实现。这里多说一句,粗看觉得如此设计多余,但细想之下这却是类职责内聚的典型体现。
transactionManager
最后是事务管理器。事务管理器提供了事务的开启、提交、回滚、挂起、恢复等入口,获取事务时传入TransactionDefinition,返回事务上下文TransactionStatus。开启新事务时通过suspendResource来保存挂起和恢复的之前的事务。
spring如何解决多个业务方法下共享同一个事务
之前提过,jdbc通过connection来作为事务的边界,即一个事务的开启和提交或者回滚操作,都是在一个connection中完成的。
整体实现思路其实可以概括为以下三个步骤
1.业务方法显式地调用、或者事务切面动态调用transactionManager开启事务,新建connection对象,并通过TransactionSynchronizationManager将connection置于当前线程变量中
2.业务方法在事务范围内进行数据库操作。数据库操作类可以是封装过的工具类,也可以是任意orm框架。唯一需要确保的是,进行数据库操作时,获取的connection对象是通过TransactionSynchronizationManager从当前线程变量中获取。
3.业务方法显式地调用、或者事务切面动态调用transactionManager的提交或回滚事务方法。提交或回滚事务时,从TransactionSynchronizationManager获取connection对象进行数据库操作。
TransactionSychronizationManager
我们先来看下connection线程对象的封装类TransactionSychronizationManager
resource是一个Map类型的线程变量,其中key是当前所使用的dataSource, value是当前事务的connection对象
spring集成ibatis时,是通过代理sqlMapClient的方式,通过层层封装,最终connection的获取都通过TransactionSynchronizationManager完成
spring对事务传播行为的实现
事务传播行为
最常用的就是“加入当前事务”和“开启新事务”。传播行为的不同,会影响事务的开启、提交和回滚。 由于整个事务调用过程涉及流程复杂,以下就以简单的流程图概述,省去时序图
事务开启
-事务是否存在主要根据当前线程中是否有数据连接
-当开启新事务时新建数据连接,并绑定到当前线程
-加入当前事务时,事务对象中isNewTransaction设为false
-开启新事务时,将当前数据连接从线程中清除,并将挂起资源放入事务上下文中,形成调用链
事务回滚
如果新事务,则直接回滚
如果非新事务,则只将当前事务的回滚标志位设为true
事务提交
假设对于方法A、B、C来说,三个方法都可能包裹了spring事务切面,并且调用顺序为A中调用B,B中调用C。当A、B、C使用了加入当前事务的因此当C或者B方法调用完毕后,切面会触发事务管理器进行提交
A、B、C方法隔离级别都为required.此时当B或C方法提交时,spring判断当前的事务并非新事务,即并非最上层事务,事务并非最上层事务,因此直到A方法执行完毕时才真正进行提交
A、B方法隔离级别为required, C方法为new时,因此A、B方法产生了事务1,C方法产生了事务2.当c提交时,由于发现c开启的是全新事务,因此进行提交操作,并将B方法的事务对象恢复。而事务2直到A方法正式执行完毕后才真正进行提交
小结
spring事务在简化了事务操作,提供了orm框架集成的同时,又实现了事务传播特性的概念。整体框架在做到合理抽象和封装的同时,又提供了合适的扩展性,有很强的学习意义。
实际使用中,经常会碰到不同数据库datasource的情况,很多情况下通过合理的业务校验,在非应用或网络故障的绝大多数情况下很少会出现多个datasource只有部分提交成功的情况。此时应用其实并不总是需要牺牲效率而获得真正的原子性(分布式事务)。更多时候,我们只需要在commit事务时将我们业务中使用的多个datasource按使用顺序commit,而commit失败时停止操作、将剩下的connection回滚、并抛出异常人工或自动补偿即可。pamirs-transaction就是基于这么一个目的的简单插件化的实现,限于篇幅,下次补充~
此文来自阿里巴巴同事:复临




