spring transaction
1.概念
首先认识一下spring事务源码中主要类:
PlatformTransactionManager: spring事务管理顶层接口.其中有获取事务, 提交事务和回滚等抽象操作. 我们使用的主要实现是DataSourceTransactionManager类, 数据源事务管理.需要配置数据源Datasource.我们常用的数据源也有druid,dbcp,c3p0等连接池.
TransactionDefinition: 事务定义类,spring事务的可配置属性:隔离性,传播性,捕获异常类型定义等解析出来后就是一个TransactionDefinition实例.
TransactionStatus: 事务的状态类.
2.事务解析过程
有两种方式实现, 编程式事务以及注解式声明事务(@Transaction或者xml配置). 注解式事务是通过spring aop切面增强的方式实现.下面是一般开启事务的xml配置, tx是自定义标签, 在spring ioc解析配置文件过程中, 碰到tx自定义标签, 会使用TxNamespaceHandler处理器去解析,进入处理器的代码内部, 会看到注册事务Advisor的BeanDefinition过程.
<tx:annotation-driven/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource1"/>
</bean>
org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser#parse方法就是解析tx标签的主要方法.其中定义了三个事务的核心bean.BeanFactoryTransactionAttributeSourceAdvisor及其两个属性transactionAttributeSource(AnnotationTransactionAttributeSource类型)、adviceBeanName(TransactionInterceptor类型)的注入.以及注册了InfrastructureAdvisorAutoProxyCreator类, InfrastructureAdvisorAutoProxyCreator继承了AbstractAutoProxyCreator实现了BeanPostProcessor接口.
看过spring aop创建代理的源码的话应该知道: 在bean实例化后会调用AbstractAutoProxyCreator的postProcessAfterInitialization方法, 该方法会查找到bean适合的Advisor创建代理类.之前提到的BeanFactoryTransactionAttributeSourceAdvisor切面类就会拉出来, 通过其pointCut属性中的匹配规则匹配.由源码可知,调用的是AnnotationTransactionAttributeSource的getTransactionAttribute方法,判断其方法或者类中的事务属性是否存在.
在获取事务属性的时候就解析了事务的属性.至此事务的初始化工作基本上完成.
3.事务触发执行过程
了解了spring aop原理的知道, 事务拦截器是TransactionInterceptor.追踪源码到org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction方法.可以看到事务的最终逻辑:
其中的逻辑更加复杂, 需要判断事务的传播行为, 异常回滚、保存点等一系列逻辑.有兴趣可仔细阅读源码.至此spring事务的整个解析和触发流程结束.顺便提一下编程式事务,通过TransactionTemplate类来操作,transactionTemplate提供了接口直接操作transactionManager.所以需要配置注入.
<bean id="defaultTxTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
TransactionTemplate.execute方法的内部实现就是spring最原始操作事务的逻辑:
编程式事务和注解的区别:就是颗粒度问题, 编程式事务可以作用于代码块, 具有代码侵入问题. 注解和声明式事务最小只能在方法上.不过也可以将事务抽出来作为单独方法做事务注解, 但是只能作用接口,这是spring aop的原理使然.
mybaits-spring
之前阅读过mybatis源码, 所以想去了解mybatis是如何与spring 整合的.并且共享spring事务能力.阅读过mybatis源码的可以知道,每个DAO层接口其实是个代理类,最终调用的sqlSession接口操作. 代理拦截器中做了dao层方法映射mybatis文件中寻找sql、预处理sql、参数和返回值类型解析和拼装等一系列工作.
那mybatis怎么保存自己是处于spring事务的上下文中的呢.我们定义mybatis和spring事务时,一定要统一数据源.否则做不到.
mybatis起源是SqlSessionFactoryBean类.在buildSqlSessionFactory方法中可以看到下面代码,默认将事务代理交给了spring事务管理器.
在SpringManagedTransactionFactory中获取事务追踪源码获取数据库连接时会调用spring的DataSourceUtils工具类获取connection.最后调用TransactionSynchronizationManager获取connectionHolder.对spring事务源码稍微熟悉的人就知道,这一步就跟spring事务获取connection的操作一摸一样了.TransactionSynchronizationManager简单说通过ThreadLocal中通过datasource可以获得当前连接,这样就到了统一连接中.
DataSourceUtils.getConnection方法如下:
总结来说:@Transactional 的由TransactionsManager 通过datasource 获取的 Connection 存储在 ThreadLocal 中,mybatis 的就手动通过spring工具类,然后datasource 获取 Connection.
事务常用概念和注意事项
事务的方法嵌套问题
@Override
@Transactional
public void parent() {
User parent = new User("Parent", "5678", 45);
userMapper.insert(parent);
try {
child();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void child() {
User child = new User("Child", "1234", 25);
userMapper.insert(child);
throw new RuntimeException("child Exception....................");
}
假如parent和child方法都有事务,但是parent中调用了child方法, 那么child方法中的事务传播行为不会生效, 这和代理原理有关, jdk对接口生成代理, 如果是外部类调用代理类肯定会走事务代理, 但是自己掉自己的方法,不会走代理实例. 只有class1中的parent调class2中的child方法, 事务传播行为会生效.具体可以仔细了解代理原理.
事务传播行为
默认是PROPAGATION_REQUIRED.如果事务中没有设置任何属性, TransactionDefinition的默认实现是DefaultTransactionDefinition.其中可以看到传播属性.其它两个常见的是:
PROPAGATION_REQUIRES_NEW : 新建事务,如果当前存在事务,把当前事务挂起。
如果是PROPAGATION_REQUIRES_NEW, 会挂起当前事务, 更新当前线程上下文,保存当前事务的信息.等新事务结束恢复旧事务. 旧事务会作为新事务的属性进行传递.DefaultTransactionStatus中suspendedResources属性就保存这上一个被挂起的事务.
PROPAGATION_NOT_SUPPORTED : 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
如果是PROPAGATION_NOT_SUPPORTED, 则直接从数据源中获取新连接进行数据库操作, 不会更新当前线程上下文中的事务信息.
事务回滚异常
查看事务主流程org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction方法中抛出异常时的处理如下:
追溯源码可知,默认事务回滚的异常是RuntimeException和Error.
如果设置了事务属性.那么源码在org.springframework.transaction.interceptor.RuleBasedTransactionAttribute#rollbackOn方法中, 可以判断当前的异常是否和事务定义的捕获异常是否一致, 如果是则回滚,不是则调用上面默认的异常回滚判断.
所以我们使用spring事务的时候应该注意, 不配置事务异常属性的话, 就必须抛出RuntimeException和Error才会回滚.不能随便自定义Exception.可以去仔细了解一下java异常体系.
Error是指虚拟机运行时异常,比如内存,堆栈溢出等不可恢复性异常.
参考文献: