spring transaction & mybatis-spring

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是指虚拟机运行时异常,比如内存,堆栈溢出等不可恢复性异常.

 

参考文献: 

关于Spring+Mybatis事务管理中数据源的思考

mybatis-spring官方文档

spring源码解析之事务篇

深入理解java异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值