spring管理mybatis事务源码理解随记

2 篇文章 0 订阅
1 篇文章 0 订阅

mysql中session和connection的关系:
mysql中一个session就是一个connection,区别在于,connection是对象池中的一个可复用对象,所以它就是一个物理连接,而session就是connection从对象池中被取出后做的一系列事情,直到connection再次被施加对象池(连接池)中时,发生的所有事情(这里指数据库交互),叫做一个session。

跟踪代码分析:

事务拦截类 org.springframework.transaction.interceptor.TransactionInterceptor extends org.springframework.transaction.interceptor.TransactionAspectSupport implements org.aopalliance.intercept.MethodInterceptor, java.io.Serializable

MethodInterceptor 接口同cglib代理接口,只有一个方法Object invoke(MethodInvocation invocation) throws Throwable; 实现该方法则会在代理类的方法执行时先执行此方法。这就是入口。cglib的原理有兴趣的同学可以自行研究。

TransactionInterceptor 的实现则是

@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
    // Work out the target class: may be {@code null}.
    // The TransactionAttributeSource should be passed the target class
    // as well as the method, which may be from an interface.
    Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

    // Adapt to TransactionAspectSupport's invokeWithinTransaction...
    return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
        @Override
        public Object proceedWithInvocation() throws Throwable {
            return invocation.proceed();
        }
    });
}

其中调用了 invokeWithinTransaction(Method, Class, InvocationCallback) 方法,这个方法是其父类 TransactionAspectSupport 的。
其中的实现比较长,有兴趣的同学可以自行研究。

重点来了,
invokeWithinTransaction(Method, Class, InvocationCallback)
方法调用了
protected TransactionInfo createTransactionIfNecessary(PlatformTransactionManager, TransactionAttribute, String) 方法,其含义是在有必要的情况下创建一个新的事务,那么就是说没必要的时候就不要创建新事务了。
这其中则调用org.springframework.transaction.PlatformTransactionManager.getTransaction(TransactionDefinition)来创建新的事务。

这个接口的实现类就是mybatis事务控制使用的org.springframework.jdbc.datasource.DataSourceTransactionManager extends org.springframework.transaction.support.AbstractPlatformTransactionManager

AbstractPlatformTransactionManager.getTransaction(TransactionDefinition)
首先会调用doGetTransaction()获取一个新的事务对象,然而在这里,这个事务对象可能并不完全是一个新的。因为存在多个事务嵌套的情况存在(propagation使用默认值require),此处会使用数据源从线程中获取(ThreadLocal),以确定是否是嵌套的事务。事务对象中都会有一个属性connectionHolder,每一个事务都会重新生成一个,并不一样,但是只会把第一个保存在线程 ThreadLocal中,因为它们里面包含的Connection是一样的。同时这也是其它位置的代码获取同一个connetion的唯一方式。
如果是嵌套的事务,则不会重新创建新的事务,而是在此基础上处理。handleExistingTransaction(TransactionDefinition, Object, boolean)
处理方式则是根据事务配置中propagation的配置内容分别处理,有兴趣的同学可以自行研究。这里只讨论TransactionDefinition.PROPAGATION_REQUIRED。因此嵌套的事务只是创建了一个新的事务状态信息TransactionInfo,并没有再次创建数据库事务,此TransactionInfo包含它的父级,也就是上一个事务信息用于回溯(此时回到invokeWithinTransaction(Method, Class, InvocationCallback)方法)。

注意DataSourceTransactionManager.doBegin(Object transaction, TransactionDefinition definition)方法,第一个事务过来时,走的是这里,但是只走一次,后面的都不会再执行这里的代码,因为事务只需要开启一次,如果事务隔离级别需要修改,则是在这个方法中调用org.springframework.jdbc.datasource.DataSourceUtils.prepareConnectionForTransaction(con, definition)处理的,如果不需要修改,则需要等到第一次需要执行sql时,由sqlSessionFactory.openSession(ExecutorType execType)开启事务。

然后就会通过InvocationCallback.proceedWithInvocation()回调到最开始的入口方法处,进行业务处理。

接着走过层层代理之后,来到 org.apache.ibatis.binding.MapperProxy.invoke(Object proxy, Method method, Object[] args) 方法中。它会回调真正的执行方法中。

其中的
sqlSession.insert(command.getName(), param)
会进入到
org.mybatis.spring.SqlSessionTemplate
的下面方法

@Override
public int insert(String statement, Object parameter) {
  return this.sqlSessionProxy.insert(statement, parameter);
}

这里需要注意,这已经从mybatis的源码跳转到mybatis-spring源码了,原因是,在配置mybatis的mapper和java接口时都是使用的mybatis-spring的配置,所以sqlSession也在内部被替换成spring的了。

而这里的sqlSessionProxy是在SqlSessionTemplate构造函数中生成的代理类

this.sqlSessionProxy = (SqlSession) newProxyInstance(
    SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class },
    new SqlSessionInterceptor());

下面是这个内部代理类的信息:
位置:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
源码:

  /**
 * Proxy needed to route MyBatis method calls to the proper SqlSession got
 * from Spring's Transaction Manager
 * It also unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to
 * pass a {@code PersistenceException} to the {@code PersistenceExceptionTranslator}.
 */
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
    try {
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
    } catch (Throwable t) {
      Throwable unwrapped = unwrapThrowable(t);
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

从注释可以看出,这就是为了把mybatis的方法调用转到spring方法调用,从而可以替换掉sqlSession,而达到管理事务的目的。(是在MapperFactoryBean中替换的,如果使用自动扫描,则是在ClassPathMapperScanner类中会有相关类创建MapperFactoryBean,从而在MapperFactoryBean的父类SqlSessionDaoSupport中。)
通过org.mybatis.spring.SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) 这个方法,回调后会到mybatis中进行sql执行,而此时有sqlSession也有dataSource,但是却没有connetion(因为前两者本地创建,而后者需要连接),这时mybatis的SimpleExecutor.prepareStatement() 会调用 getConnection()获取连接,而这个连接是之前用SqlSessionUtils.getSqlSession()创建的,里面包含有sqlSessionFactory,也就是会有 SpringManagedTransaction 进行事务连接创建。创建连接就在 SpringManagedTransaction.getConnection(),它是通过 DataSourceUtils.getConnection(this.dataSource) 调用 doGetConnection(DataSource dataSource) 来获取连接的,所以通过和之前事务管理中一样的dataSource,就能获取到spring事务管理中的connectionHoler,再获取其connection即可。然,如果spring事务和mybatis数据源配置的不是同一个(即配置出错),则无法获取到spring事务中的connectionHolder,这时,doGetConnection(DataSource dataSource) 会调用传来的dataSource创建一份新的connection,也就是不受事务控制的一个connection,返回给myabatis使用,所以一定需要保证spring和mybatis使用的是同一个数据源(spring中的是真实生效的,mybatis中的只是一个引用,作为key用来获取spring中相应的数据。)。

再转回到一开始定义的ConnectionHolder中,从ThreadLocal中获取出相应的connection,然后直接method.invoke运行程序中的sql。结束。

为什么放在threadLocal中?
因为一个任务是一个线程执行,所以不管到了哪一行代码,都能获取到。而datasource和sqlSessionFactory都是spring管理,所以可以直接作为key来使用。

到此为止:
今日分析的目标已经达到,这其中的代码,思想都是非常值得参考,学习的。学到自己的思想里,才能运用自如。

2016-09-16 06:29:16
一夜,又过去了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值