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动态代理。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容

  • [上一篇]:Mybatis源码解析之配置解析 从菜菜的Mybatis源码解析之Spring获取Mapper过程中知...
    eliter0609阅读 5,316评论 0 1
  • 单独使用mybatis是有很多限制的(比如无法实现跨越多个session的事务),而且很多业务系统本来就是使用sp...
    七寸知架构阅读 3,441评论 0 53
  • # 前言 在java程序员的世界里,最熟悉的开源软件除了 Spring,Tomcat,还有谁呢?当然是 Mybat...
    莫那一鲁道阅读 3,282评论 3 11
  • 南柯锁梦梦思迁,素锦娇衣走海天 何处寄情寻媚影,他乡弃恼觅人怜 相逢纵使遥千尺,阔别仍然待百年 月鉴倾心真是假,悬...
    醉吟诗酒行阅读 126评论 0 0
  • 1、夫妻关系情人化在于放纵时保有些神秘 最近一年前黄磊接受采访的一段话突然火了,获得一众拥趸,很多人非常赞成黄磊的...
    晓妮臆空间阅读 2,143评论 0 2