mybatis源码解析(六)-配合spring-tx实现事务的原理

mybatis源码解析(一)-开篇
mybatis源码解析(二)-加载过程
mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程
mybatis源码解析(四)-Mapper方法调用过程
mybatis源码解析(五)-mybatis如何实现的事务控制
mybatis源码解析(六)-配合spring-tx实现事务的原理
mybatis源码解析(七)-当mybatis一级缓存遇上spring

转载请标明出处:
http://blog.csdn.net/bingospunky/article/details/79541494
本文出自马彬彬的博客

spring-tx关键对象

对象包含关系

TransactionAspectSupport.TransactionInfo包含TransactionStatus对象,TransactionStatus对象包含DataSourceTransactionManager.DataSourceTransactionObject(简称Transaction对象),DataSourceTransactionManager.DataSourceTransactionObject包含ConnectionHolder对象,ConnectionHolder包含Connection。

对象创建关系

创建ConnectionHolder需要DataSource,所以DataSourceTransactionManager创建ConnectionHolder。
其他对象基本是在TransactionAspectSupportDataSourceTransactionManager这两个类中。
这些对象基本都是在before advice过程中创建的。

对象存放的位置

TransactionAspectSupport里的ThreadLocal维护TransactionAspectSupport.TransactionInfo。 TransactionSynchronizationManager里的ThreadLocal维和线程相关的TransactionStatus。
其他对象在引用它的对象中。

spring-tx原理

首先spring-tx是基于aop的,既然aop,关键就在advice里。增强后的代码是这个样子的:

Code1
org.springframework.transaction.interceptor.TransactionAspectSupport中

    TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    Object retVal = null;
    try {
        retVal = invocation.proceedWithInvocation();
    } catch (Throwable var15) {
        this.completeTransactionAfterThrowing(txInfo, var15);
        throw var15;
    } finally {
        this.cleanupTransactionInfo(txInfo);
    }
    this.commitTransactionAfterReturning(txInfo);
    return retVal;

Code1第1行,会创建一个新的TransactionStatus,并放在适当的ThreadLocal里,创建TransactionStatus的过程处理的情况比较多,比如现在是否是在Transaction中、新的propagation是怎样的。我们设置的propagation的值也是在这里生效的,这部分代码会根据propagation的不同做一些不同的操作(比如是否新建Connection,是否开启新的Transaction)。
创建TransactionStatus的代码如下:

Code2
org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction

    public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
        Object transaction = this.doGetTransaction();
        boolean debugEnabled = this.logger.isDebugEnabled();
        if (definition == null) {
            definition = new DefaultTransactionDefinition();
        }
        if (this.isExistingTransaction(transaction)) {
            return this.handleExistingTransaction((TransactionDefinition)definition, transaction, debugEnabled);
        } else if (((TransactionDefinition)definition).getTimeout() < -1) {
            throw new InvalidTimeoutException("Invalid transaction timeout", ((TransactionDefinition)definition).getTimeout());
        } else if (((TransactionDefinition)definition).getPropagationBehavior() == 2) {
            throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
        } else if (((TransactionDefinition)definition).getPropagationBehavior() != 0 && ((TransactionDefinition)definition).getPropagationBehavior() != 3 && ((TransactionDefinition)definition).getPropagationBehavior() != 6) {
            if (((TransactionDefinition)definition).getIsolationLevel() != -1 && this.logger.isWarnEnabled()) {
                this.logger.warn("Custom isolation level specified but no actual transaction initiated; isolation level will effectively be ignored: " + definition);
            }
            boolean newSynchronization = this.getTransactionSynchronization() == 0;
            return this.prepareTransactionStatus((TransactionDefinition)definition, (Object)null, true, newSynchronization, debugEnabled, (Object)null);
        } else {
            AbstractPlatformTransactionManager.SuspendedResourcesHolder suspendedResources = this.suspend((Object)null);
            if (debugEnabled) {
                this.logger.debug("Creating new transaction with name [" + ((TransactionDefinition)definition).getName() + "]: " + definition);
            }
            try {
                boolean newSynchronization = this.getTransactionSynchronization() != 2;
                DefaultTransactionStatus status = this.newTransactionStatus((TransactionDefinition)definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                this.doBegin(transaction, (TransactionDefinition)definition);
                this.prepareSynchronization(status, (TransactionDefinition)definition);
                return status;
            } catch (RuntimeException var7) {
                this.resume((Object)null, suspendedResources);
                throw var7;
            } catch (Error var8) {
                this.resume((Object)null, suspendedResources);
                throw var8;
            }
        }
    }

Code1第4行就是调用被代理对象的方法。

Code1第6行是处理异常的,如果抛出的异常是@Transaction回滚的,则进行适当的回滚操作(包括真正的回滚或者标记回滚,后面会解释);如果抛出的异常不在回滚范围,那么这里依然会执行commit操作,所以我们一定要注意异常类型,尤其是spring-tx配合别的数据框orm框架使用时(后面也会煮个栗子)。

Code1第9行,清理掉ThreadLocal里的TransactionAspectSupport.TransactionInfo,需要把TransactionAspectSupport.TransactionInfo设置成前一个TransactionAspectSupport.TransactionInfo,这里有一个链式的用法有一点巧妙。事情是这个样子的:调用A.a方法时,设置TransactionAspectSupport.TransactionInfo对象tia在ThreadLocal,在A.a方法中又调用了B.b方法,这个方法也是事务的,这时,需要构造一个TransactionAspectSupport.TransactionInfo对象tib放在ThreadLocal里。且tib有一个属性叫oldTransactionInfo指向了tia。当B.b方法执行完了,需要把tib在ThreadLocal里清除,把tia放进ThreadLocal里,这样通过tib的oldTransactionInfo属性可以找到tia。以此类推,链式形成。

Code1第11行,当业务代码执行完成时,根据情况进行commit或者rollback,这里和Code1第6行的代码是相似的,只是那个是有异常发生时。这里真正处理是commit还是rollback的代码在TransactionManager里的,调用代码如下:

Code3
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning

    if (txInfo != null && txInfo.hasTransaction()) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
    }

propagation

@Transaction里有几个参数可以设置,比较关键的一个就是propagation,就是传播规则。
有下面这几种:

    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

具体含义可以查询相关api,我在这里就叙述一下REQUIREDREQUIRES_NEW是如何生效的。

还是刚才那个例子:
已经调用了A.a方法,A.a是被@Transaction注解的方法,在A.a中调用了B.b方法,B.b也是@Transaction注解的方法,下面分类讨论:

  • 如果B.b的注解为@Transactional(propagation = Propagation.REQUIRES_NEW),则新创建的TransactionStatusnewTransaction属性为true,同时新建一个Connection,begin transaction,在方法执行完成后会commit或者rollback。
  • 如果B.b的注解为@Transactional(propagation = Propagation.REQUIRED),则新创建的TransactionStatusnewTransaction属性为false,不会创建新的Connection,使用之前的事务环境,在方法执行完成后不会进行commit或者rollback,如果发生了异常,给TransactionStatus设置一个状态表示需要回滚,在最外层的方法中会根据这个状态进行回滚。设置这个状态如下:

Code4
org.springframework.jdbc.datasourceDataSourceTransactionManager.doSetRollbackOnly

    protected void doSetRollbackOnly(DefaultTransactionStatus status) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
        txObject.setRollbackOnly();
    }

mybatis配合spring-tx是如何生效的?

万变不离其宗,不论上层添加几个概念,java的单机事务都是以jdbc的api为基础的,关键就是同一个Connection的begin/commit/rollback。

spring-tx的原理上面已经描述过了,这里做一个简化:
在before advice中,先获取TransactionManagerTransactionManager中包含DataSource,再根据当前的事务环境和@Transaction.propagation构造出Connection,放在ThreadLocal里的Map里,Map的key是DataSource。然后执行业务代码。在return advice或exception advice中,先获取之前生成的Connection,然后根据异常情况以及@Transaction.propagation(并不是直接使用这个属性,而是使用它转化后的信息,比如newTransaction的值,这个属性在before advice已经转化到TransactionStatus里了)进行commit或者rollback(或等价操作,设置标记,等外层commit或者rollback)。

spring-tx生效的关键:

1.同一个Connection。

Connection放在ThreadLocal中的Map里,Map的key为DataSource。所以确保同一个Connection,需要在同一个线程中,这个没有问题,需要使用同一个DataSource去Map里取。我们自己去写这几行代码是比较麻烦的,Spring-tx给我们提供了一个工具类来做这个事情,public static Connection org.springframework.jdbc.datasourc.DataSourceUtils.getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException,通过这个方法,只要线程和DataSource正确,就可以获取到正确的Connection。我们使用Connection进行增删改查,且不执行与Transaction相关的方法,advice去完成Transaction。

我们使用Mybatis-spring这个框架集成一下mybatis和spring,mybatis在@Transaction注解标记的方法内是正常的支持事务的。那么mybatis是如何获取到正确的Connection的呢?

在我的mybatis源码解析(五)-mybatis如何实现的事务控制这篇文章中可以获取如下的信息:用户操作org.apache.ibatis.session.SqlSession这个类,org.apache.ibatis.session.SqlSession操作org.apache.ibatis.executor.Executor去执行sql,org.apache.ibatis.executor.Executor只是使用Connection,但是不去维护Connection,它维护了org.apache.ibatis.transaction.Transaction,由org.apache.ibatis.transaction.Transaction去创建、维护、回收Connection。
在mybatis-spring初始化的过程会执行如下的代码:

Code5
org.mybatis.spring.SqlSessionFactoryBean

    if (this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
    }
    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);

Code5给Configuration设置了SpringManagedTransactionFactorySpringManagedTransactionFactory就是用来生成Transaction的,它生成的类为org.mybatis.spring.transaction.SpringManagedTransactionorg.mybatis.spring.transaction.SpringManagedTransaction生成Connection的代码如下:

Code6
org.mybatis.spring.transaction.SpringManagedTransaction

    private void openConnection() throws SQLException {
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
        if (logger.isDebugEnabled()) {
            logger.debug("JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
        }
    }

Code6第2行,就是使用的spring的方法去获取到对应的Connection了。这就对上了。

2.异常类型匹配,默认是RunTimeException。

@Transaction注解默认回滚的是RunTimeException,为了更方便地配合@Transaction注解,Mybatis默认抛出的是RunTimeException,这点是比较符合的。关于异常会在后面section描述。

jdbc proxy or cglib

spring-tx是基于spring aop的,aop对于proxy模式,支持jdbc proxy和cglib。

proxy-target-class=”false”(jdbc proxy)
  • 1.注解只放在interface的方法上,jdbc代理,事务生效。
  • 2.注解只放在实现类的方法上,jdbc代理,事务生效。
proxy-target-class=”true”(cglib)
  • 1.注解只放在interface的方法上,cglib代理,事务不生效。
  • 2.注解只放在实现类的方法上,cglib代理,事务生效。cglib代理,事务生效。

使用时,默认的proxy-target-class=”false”就能满足我们的需求;如果需要使用proxy-target-class=”true”,需要把@Transaction注解加在实现类的方法上,因为注解是不支持继承的。

注意:

    1. 如果一个Service类没有接口,又使用事务,需要生成代理,默认使用jdbc proxy。由于这个类没有interface,就会抛出异常。

关于异常

@Transaction默认的回滚是RunTimeException,使用JdbcTemplate或者Mybatis产生的都是RunTimeException。如果是下面这种代码:

Code7

    @Transactional
    public void transactionInJdbcApi2() throws Exception{
        Connection connection = DataSourceUtils.getConnection(dataSource);

        PreparedStatement preparedStatement = connection.prepareStatement("INSERT  INTO foo(id, name) values(1, 'aaa')");
        preparedStatement.executeUpdate();

        PreparedStatement preparedStatement2 = connection.prepareStatement("INSERT  INTO foo(id, name) values(2, 'xxx')");
        preparedStatement2.executeUpdate();
    }

Code7代码是不能支持事务的,如果在第二个插入语句出现主键重复异常时,抛出的是check exception,不能回滚。
为了解决上面的问题,我们让配置的回滚规则和出现的异常对应起来就行了。比如,1:配置@Transaction针对所有异常进行回滚。2.对Exception进行处理,使其抛出RunTimeException。

Using the PlatformTransactionManager

You can also use the org.springframework.transaction.PlatformTransactionManager directly to manage your transaction. Simply pass the implementation of the PlatformTransactionManager you are using to your bean through a bean reference. Then, using the TransactionDefinition and TransactionStatus objects you can initiate transactions, roll back, and commit.

    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    // explicitly setting the transaction name is something that can only be done programmatically
    def.setName("SomeTxName");
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus status = txManager.getTransaction(def);
    try {
        // execute your business logic here
    }
    catch (MyException ex) {
        txManager.rollback(status);
        throw ex;
    }
    txManager.commit(status);
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值