Spring集成Mybatis源码解析

Spring集成Mybatis

Mybatis和Spring框架的集成,可使用MyBatis-Spring组件,该组件有Mybatis社区开发,可以将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException

MyBatis-Spring 的官网地址 http://mybatis.org/spring/zh/index.html

基本配置使用

MyBatis-Spring中有几个和用户使用有关的核心类,分别是SqlSessionFactoryBean(用它来创建SqlSessionFactory)、MapperFactoryBean(用它来创建Mapper),SqlSessionTemplate(用它来创建Mapper以及执行)。

主要就是配置SqlSessionFactoryBean以及MapperFactoryBean或者SqlSessionTemplate。

具体可参考 官方文档(http://mybatis.org/spring/zh/index.html) ,以及 https://gitee.com/cq-laozhou/mybaits-source-code-analyzer 上的demo代码,不在这儿啰嗦。

Mapper实例的构建过程解析

首先,来看下SqlSessionFactoryBean是如何构建SqlSessionFactory的,我们知道在Spring中FactoryBean是一种特殊的bean,它的作用就是用来创建复杂的bean的,SqlSessionFactoryBean#getObject方法就是用来创建SqlSessionFactory的:

public SqlSessionFactory getObject() throws Exception {
  //如果没有创建过,则使用afterPropertiesSet创建。
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }
	//返回内部属性sqlSessionFactory
  return this.sqlSessionFactory;
}

最终的逻辑在SqlSessionFactoryBean#SqlSessionFactoryBean方法中:

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
	//构建SqlSessionFactory,其实就是构建Configuration对象的过程。
  final Configuration targetConfiguration;
	
  XMLConfigBuilder xmlConfigBuilder = null;
  //可以使用已有的一个Configuration对象。
  if (this.configuration != null) {
    targetConfiguration = this.configuration;
   	...
  } 
  
  //或者配置了mybaits主配置文件地址,则使用XMLConfigBuilder来解析(和原生的代码一样),注意这儿只是new了XMLConfigBuilder,并没有parse。
  else if (this.configLocation != null) {
    xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    targetConfiguration = xmlConfigBuilder.getConfiguration();
  } 
  
  //前两者都没有,则用默认的配置,即new Configuration()。
  else {
    targetConfiguration = new Configuration();
  }

	...

	//如果在SqlSessionFactoryBean中配置别名包路径,则扫描包下的类,并注册满足条件的类的别名。
	//注意,这儿的扫描实现就使用了Spring的扫描方式,即使用Resource体系。
  if (hasLength(this.typeAliasesPackage)) {
    scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
        .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
        .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
  }
	
	//如果在SqlSessionFactoryBean中配置了别名,则直接注册。
  if (!isEmpty(this.typeAliases)) {
    Stream.of(this.typeAliases).forEach(typeAlias -> {
      targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
      LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
    });
  }

	//如果在SqlSessionFactoryBean中配置了插件,则注册插件
  if (!isEmpty(this.plugins)) {
    Stream.of(this.plugins).forEach(plugin -> {
      targetConfiguration.addInterceptor(plugin);
      LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
    });
  }
	
	//如果在SqlSessionFactoryBean中配置了类型处理器包扫描路径,则包下的类,并注册满足条件的类型处理器。
  if (hasLength(this.typeHandlersPackage)) {
    scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
        .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
        .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
  }

	//如果在SqlSessionFactoryBean中配置了类型处理器,则直接注册类型处理器
  if (!isEmpty(this.typeHandlers)) {
    Stream.of(this.typeHandlers).forEach(typeHandler -> {
      targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
      LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
    });
  }
	
	...

	//这儿进行xmlConfigBuilder解析
  if (xmlConfigBuilder != null) {
      xmlConfigBuilder.parse();
   ...
  }

	//注意,前面的Configuration中,就算已经有了解析了环境对象,不会使用。这儿会全新new一个Environment出来,器事务工厂为SpringManagedTransactionFactory。
  targetConfiguration.setEnvironment(new Environment(this.environment,
      this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
      this.dataSource));

	//如果在SqlSessionFactoryBean中配置了mapper的资源路径,则直接使用XMLMapperBuilder来解析Mapper映射文件。
  if (this.mapperLocations != null) {
    ...
      for (Resource mapperLocation : this.mapperLocations) {
        ...
        	//使用XMLMapperBuilder解析mapper.xml文件。
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          xmlMapperBuilder.parse();
        ...
       }
   }
	
	//由SqlSessionFactoryBuilder#build方法,传入Configuration对象,构建一个DefaultSqlSessionFactory出来,这和原生的一样。
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

可以看出,使用SqlSessionFactoryBean来构建SqlSessionFactory,和原生的方式区别不大,支持Mybatis的主配置文件方式,也可将很多配置直接放到SqlSessionFactoryBean中来,从而省略mybatis的主配置文件。这里面最大的不同点就是环境中,事务工厂的实现类为SpringManagedTransactionFactory。

接下来,无论是通过MapperFactoryBean,还是直接通过SqlSessionTemplate来获取Mapper的实例,这两种方式最终通过SqlSessionTemplate#getMapper方法来获取的:

public <T> T getMapper(Class<T> type) {
	//委托给Configuration#getMapper方法,将SqlSessionTemplate自身的实例作为SqlSession参数传入。
  return getConfiguration().getMapper(type, this);
}

可以看到,获取Mapper实例的最终还是由Configuration对象来创建,又回到了原生的Mapper实例创建的过程,具体可参考 Mybatis执行流程源码解析 ( https://blog.csdn.net/gruelxsp/article/details/103813154 ) 。需要特别注意的是,传入的Sqlsession现在是 SqlSessionTemplate 了。

Mapper方法执行流程解析

用户调用Mapper的查询方法(比如selectById),参考 Mybatis执行流程源码解析 ( https://blog.csdn.net/gruelxsp/article/details/103813154 ) 的 ”通过Mapper接口查询“ 这一节,我们知道,调用Mapper接口是调用的生成的动态代理对象,会被MapperProxy#invoke方法拦截,但最终会调用到SqlSession上,在前面分析中我们知道,此时的SqlSession是SqlSessionTemplate,因此会调用到SqlSessionTemplate#selectList方法上:

public <E> List<E> selectList(String statement, Object parameter) {
	//所有的SqlSession调用,都委托给了sqlSessionProxy
  return this.sqlSessionProxy.selectList(statement, parameter);
}

所有的SqlSession调用,都委托给了sqlSessionProxy,而sqlSessionProxy是在SqlSessionTemplate初始化时创建出来的:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {
	...
  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  //使用JDK动态代理,创建一个SqlSession接口的代理对象,使用SqlSessionInterceptor进行拦截。
  this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}

这个SqlSessionInterceptor就是SqlSessionTemplate的核心所在了,为什么SqlSessionTemplate是线程安全的,就是它的功劳:

//SqlSessionInterceptor实现了InvocationHandler接口
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //通过SqlSessionUtils来获取一个SqlSession,这个SqlSession才是真正干活的
    SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
    try {
    	//反射调用刚获取的SqlSession的方法。
      Object result = method.invoke(sqlSession, args);
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        //没有在事务中执行的话,直接提交。
        sqlSession.commit(true);
      }
      return result;
    	...
    } finally {
    	//调用完了之后,关闭SqlSession
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

跟到SqlSessionUtils#getSqlSession方法中:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {
	//从事务同步管理器中获取SqlSessionHolder。(事务同步管理器包装了一堆的ThreadLocal,用来保存当前线程中的事务相关信息)
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

	//如果获取到了SqlSessionHolder,则从中获取SqlSession返回
  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    return session;
  }

	//否则调用sessionFactory#openSession方法,开启一个新的sqlSession,这就回到了mybaits的代码。
  LOGGER.debug(() -> "Creating a new SqlSession");
  session = sessionFactory.openSession(executorType);

	//将新创建的sqlsession注册绑定到事务同步管理器中,以便于同一个事务中,可以复用sqlsession。
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

现在就可以解释为什么SqlSessionTemplate是线程安全的,那是因为如果不是同一线程,那么每次getSqlSession都会获取一个新的SqlSession用于真正的执行,因此完全不会有任何线程问题。

其实就算同一个线程,也只有开启了事务,在事务中执行,才会复用sqlSession,否则每执行一次mapper的方法,都会新创建sqlSession来执行,这样才能享受到mybatis一级缓存的好处。

最后来来个示意图帮助理解:
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值