前言
本文基本算第19章 Spring事务王国的架构的重新整理。
Spring事务分析(2)–基于声明式的事务管理实现分析对源码的分析也比较好,注意学习其分析源码的方式:通过画UML图来分析,更重要的是,这些UML图位于逻辑主线的关键节点上。
Transaction Management 官方文档不可不看,几张图还是非常精到的。
spring 事务重点就两块:
- 通过 TransactionDefinition、PlatformTransactionManager、TransactionStatus 提供基本抽象
- 通过代理模式将第一点提到的抽象加入到业务代码中,通过注解简化编程式事务代码。代码技巧上小结一下就是:通过注解隐藏代理模式
先从编程式事务说起
public void serviceMethod(){
TransactionDefinition definition = ...;
PlatformTransactionManager transactionManager = ...;
TransactionStatus txStatus = transactionManager.getTransaction(definition);
try {
// dao1.doDataAccess();
// dao2.doDataAccess();
// ...
}catch(DataAccessException e){
transactionManager.rollback(txStatus);
throw e;
}catch(OtherNecessaryException e){
transactionManager.rollback(txStatus);
throw e;
}
transactionManager.commit(txStatus);
}
TransactionDefinition、PlatformTransactionManager、TransactionStatus是spring tx提供的三个基本抽象。
Spring的事务框架设计理念的基本原则是:让事务管理的关注点与数据访问关注点相分离。如上图,try代码块数据还按照原来的方式访问,不管是datasource、hibernate还是其他数据源,亦或是rpc服务,怎么变化,try代码块之外的事务代码可以岿然不动。
通过注解简化编程式事务代码
声明式事务,如何从代码上将事务与数据访问分离?
从常规路线来说,一般从配置解析开始,spring tx将配置存到一些数据类中。
<!-- 声明事务管理器,并支持事务注解 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
宏观上,spring aop 一般涉及到ProxyFactoryBean,spring启动时,识别标记有@transactional的方法的类,并将其关联到TransactionProxyFactoryBean。使得 ioc 返回带@Transactional 方法的类的实例时,返回的是代理类实例。
如何识别@transactional?
-
对于spring aop, spring源码分析之——spring aop原理 从代码上看,Spring AOP 实现一个InstantiationAwareBeanPostProcessor接口的bean。在每次bean初始化的时候找到所有advisor(spring ioc启动时,会采集类信息存储在BeanDefinition中),根据pointcut 判断是不是需要为将实例化的bean生成代理,如果需要,就把advice编制在代理对象里面。
而spring tx 在解析
<tx:annotation-driven transaction-manager="txManager" />
会向ioc 注册 advisor 的BeanDefinition。也就是说,使用spring aop,额外支持一个注解时,向ioc注册一个advisor的BeanDefinition即可。当然,自己定义BeanPostProcessor,直接处理自定义注解也是可以的。 -
对于aspectj,定义切点就比较直接了。aspectj启动优化
public aspect AnnotationTransactionAspect extends AbstractTransactionAspect { private pointcut executionOfTransactionalMethod() :execution(@Transactional * *(..)); }
TransactionProxyFactoryBean 抛开ProxyFactoryBean功能,其功能点转向了其成员TransactionInterceptor(事务相关的拦截器),TransactionInterceptor实现了MethodInterceptor。
public interface MethodInterceptor extends Interceptor {
/**
* Implement this method to perform extra treatments before and
* after the invocation.
* /
Object invoke(MethodInvocation invocation) throws Throwable;
}
其invoke方法,在实际方法invocation 执行的前后,做了事务相关的处理。基本套路就是构造一个大杂烩的”能力对象”TransactionInfo,然后在各个场景应用它。逻辑比我们想的复杂,用于实现各种”传播”行为。
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
retVal = invocation.proceedWithInvocation();
}catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
return retVal;
TransactionAspectSupport==> | PlatformTransactionManager==> | 子类 | 含义 | |
---|---|---|---|---|
涉及到的对象 | TransactionInfo | TransactionStatus、TransactionAttribute | Object | TransactionStatus是一系列状态对象组成的聚合类;到子类时,则不用返回TransactionStatus,只需返回其中一个transaction(Object类型)成员即可 |
方法 | invokeWithinTransaction | TransactionStatus getTransaction(TransactionDefinition definition); | Object doGetTransaction() | 根据配置数据获取当前状态 |
void commit(TransactionStatus status | void doCommit(DefaultTransactionStatus status) | |||
void rollback(TransactionStatus status) | void doRollback(DefaultTransactionStatus status) |
从某种程度上,transactionManager子类方法跟servlet的init、doGet、destroy作用是一样的。
2018.01.07 补充spring 官方图
实现一个简单的PlatformTransactionManager
PlatformTransactionManager是整个事务抽象策略的顶层接口,它就好像我们的战略蓝图,而战略的具体实施则将由相应的PlatformTransactionManager实现类来执行。
以JDBC数据访问方式的局部事务管理为例,统一中原的过程(1),其基本原理是:保证两个dao的数据访问方法使用的是同一个java.sql.Connection,这样PlatformTransactionManager的commit和rollback就可以转化为conn的对应方法。
采用称为connection-passing的方式,org.springframework.jdbc.datasource.DataSourceTransactionManager
在事务开始时,获取一个conneciton,将其挂到当前线程,随后dao层在获取connection时,先尝试从当前线程获取Connection,(无则新建),然后使用Connection进行数据操作。可以看出,Spring的事务管理与它的数据访问框架是紧密结合的。
事务中的一些概念
对于TransactionDefinition的命名,可以联系spring ioc中的BeanDefinition
- 事务的隔离级别,并发事务的相互影响
- 事务的传播行为,直接说,就是一个标记了@Transactional的方法,调用了另一个标记了@Transactional的方法。后一个方法的失败是否会影响前一个方法。
- 事务的超时时间
- 是否为只读事务
事务实现的其它问题
- 事务数据的持久化。mysql 有redo 和undo log。因此mysql的关闭不影响事务,但业务系统因为更新代码等原因,经常要重新启动,若不能在重启启动时恢复或回滚事务,则会对系统的一致性造成比较大的影响。changmingxie/compensable-transaction 中包含的了数据的存储逻辑。
一个@Transactional 失效问题的排查
有一个看到一个@Transactional 失效的问题,并且奇怪的是本地测试生效,测试环境不生效。我们知道@Transactional 的实现原理是
- 对于@Transactional 注解方法的两次jdbc 操作
- 先尝试从ThreadLocal 中获取数据库连接,若有,则使用该数据库连接。若没有,则向数据库连接池申请。以确保两次jdbc 操作均使用同一个数据库连接。
当时实在是没办法了,便将spring-tx 包的日志放开,想看下 数据库连接的申请和释放过程,以确保两次操作用的是同一个数据库连接。但从日志看,事务管理器生效了,但管控的不是jdbc 操作的数据库连接,为何呢?<tx:annotation-driven />
在项目代码中配置了多个,项目用到了两个不同的db,包含在不同的spring jdbc context文件中,且本地环境和测试环境配置文件中spring jdbc context 引入的顺序不同。实际运行时,第一个配置的<tx:annotation-driven />
生效。
小结
通过对spring事务的分析,可以学到:
- 自定义
<tx:annotation-driven transaction-manager="txManager" />
表现,将其转化特定的数据结构TransactionDefinition及其TransactionAttribute - 通过aspectj或spring aop,通过注解的方式与现有代码协作,囊括前置、后置、切面、异常处理等所有可能