Mybatis源码解析之SqlSession来自何方

[上一篇]:Mybatis源码解析之MapperProxy

上一篇我们知道了MyBatis通过JDK动态代理让我们只用写接口不用写实现,但是还是有一些细节需要我们去研究下。

问题

我们从MapperFactoryBean源码中看到调用getObject再调用getSqlSession().getMapper 这个getSqlSession()中的sqlSession是什么类型的,又是什么时候赋值的,是Spring注入还是实例化赋值

 @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

//SqlSessionDaoSupport中的方法
public SqlSession getSqlSession() {
    return this.sqlSession;
  }

揭秘源码

在《1》Mybatis源码解析之Spring获取Mapper过程中看到启动的时候调用processBeanDefinitions,而这方法里最后会通过definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
设置beanDefinition中按类型注入,看过《1》就知道这里的beanDefinition是MapperFactoryBean,主要是为了按类型自动注入SqlSessionFactory或者SqlSessionTemplate(也就是实例化MapperFactoryBean时会自动调用它的set方法)。我们接下来看下MapperFactoryBean有哪些set方法,通过源码看到MapperFactoryBean继承了SqlSessionDaoSupport,贴出SqlSessionDaoSupport源码

/**
 * Convenient super class for MyBatis SqlSession data access objects.
 * It gives you access to the template which can then be used to execute SQL methods.
 * <p>
 * This class needs a SqlSessionTemplate or a SqlSessionFactory.
 * If both are set the SqlSessionFactory will be ignored.
 * <p>
 * {code Autowired} was removed from setSqlSessionTemplate and setSqlSessionFactory
 * in version 1.2.0.
 * 
 * @author Putthibong Boonbong
 *
 * @see #setSqlSessionFactory
 * @see #setSqlSessionTemplate
 * @see SqlSessionTemplate
 * @version $Id$
 */
public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

  /**
   * Users should use this method to get a SqlSession to call its statement methods
   * This is SqlSession is managed by spring. Users should not commit/rollback/close it
   * because it will be automatically done.
   *
   * @return Spring managed thread safe SqlSession
   */
  public SqlSession getSqlSession() {
    return this.sqlSession;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

可以看到有我们关心的setSqlSessionFactory与setSqlSessionTemplate,这个类上的注释告诉我们需要一个SqlSessionTemplate或者一个SqlSessionFactory,在本菜的MyBatisConfig配置中只配置了SqlSessionFactoryBean如下所示,
这样就会将配置的SqlSessionFactory注入到SqlSessionDaoSupport中

@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@PropertySource({"classpath:jdbc.properties"})
@MapperScan(basePackages = "me.ele.eliter.**.mapper")
public class MyBatisConfig {
  private static final Log LOG = LogFactory.getLog(MyBatisConfig.class);

  @Bean(destroyMethod = "close")
  public DataSource dataSource(
      @Value("${db.driver}") String driverClass,
      @Value("${db.url}") String jdbcUrl,
      @Value("${db.username}") String userName,
      @Value("${db.password}") String passWord) throws Exception {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setDriverClass(driverClass);
    dataSource.setJdbcUrl(jdbcUrl);
    dataSource.setUser(userName);
    dataSource.setPassword(passWord);
    return dataSource;
  }

  @Bean
  public PlatformTransactionManager transactionManager(DataSource dataSource) {
    DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
    dataSourceTransactionManager.afterPropertiesSet();
    return dataSourceTransactionManager;
  }

  @Bean
  public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource, ApplicationContext applicationContext) throws Exception {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    sessionFactory.setConfigLocation(applicationContext.getResource("classpath:mybatis-config.xml"));
    sessionFactory.afterPropertiesSet();
    sessionFactory.setPlugins(new Interceptor[] { new PagerInterceptor()});
    return sessionFactory;
  }

  @Bean
  public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) throws Exception {
    TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
    transactionTemplate.afterPropertiesSet();
    return transactionTemplate;
  }
}

有人会问,你明明配置的是SqlSessionFactoryBean,为啥说会将这个以sqlSessionFactory注入到SqlSessionDaoSupport中,这是因为SqlSessionFactoryBean是个factoryBean,在获取它的实例时Spring会调用它的getObject方法,我们看下面它的getObject方法就知道返回的是SqlSessionFactory

 @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

将配置的SqlSessionFactory注入到SqlSessionDaoSupport中也就是调用 setSqlSessionFactory(SqlSessionFactory sqlSessionFactory)这个方法,单独拎出这个方法

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

我们看到直接创建了一个SqlSessionTemplate对象。接下下我们看下
new SqlSessionTemplate(sqlSessionFactory)干了些什么事,通过跟源码找到了最终调用的构造方法。

 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new 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);
        }
      }
    }
  }

SqlSessionTemplate构造方法的最后一步创建了jdk的动态代理sqlSessionProxy,将代理对象作为sqlSession。查看SqlSessionInterceptor的源码看到第一行就是我们关心的getSqlSession,得到sqlSession后执行被代理的方法。我们继续跟踪getSqlSession

/**
   * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed.
   * Tries to get a SqlSession out of current transaction. If there is not any, it creates a new one.
   * Then, it synchronizes the SqlSession with the transaction if Spring TX is active and
   * <code>SpringManagedTransactionFactory</code> is configured as a transaction manager.
   *
   * @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions
   * @param executorType The executor type of the SqlSession to create
   * @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions.
   * @throws TransientDataAccessResourceException if a transaction is active and the
   *             {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory}
   * @see SpringManagedTransactionFactory
   */
  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

getSqlSession方法首先从Spring当前事务中获取SqlSession,如果不存在就通过SqlSessionFactory创建一个新的SqlSession,再将sqlSession放入SqlSessionHolder里供当前事务下下一次使用。我们知道SqlSessionFactory是创建SqlSession的工厂,到这里就清楚了MyBatis是如何走到SqlSessionFactory创建SqlSession的

总结

通过本文知道了MapperFactoryBean(实际是SqlSessionDaoSupport)里的SqlSession是如何来的,主要是由于Mybatis设置了definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
通过类型注入,以及使用了Jdk动态代理。

如有错误,欢迎各位大佬斧正!

推荐阅读更多精彩内容