MyBatis源码阅读(十二) --- Spring加载MyBatis过程

2024-01-30 09:04:45 浏览数 (2)

欢迎关注MyBatis源码阅读专栏,持续更新中~~

一、概述

通过前面几篇文章的学习,相信小伙伴对Mybatis的认识更加深刻了,对整体的流程应该算是比较清晰了。但是我们在项目中很少单独使用Mybatis,一般都是集成到Spring中,由Spring来帮我们完成以前很多繁琐的步骤,比如管理SqlSessionFactory、创建SqlSession,并且不需要手动调用getMapper方法去获取mapper接口,直接使用autoWired自动注入进来就好了。那么Spring到底是如何整合Mybatis的,我们有必要去了解一下。

二、Spring加载MyBatis过程

首先来回顾一下,没有集成Spring的时候,Mybatis是如何使用的:

代码语言:javascript复制
public static void main(String[] args) {
    //1、读取配置文件
    String resource = "mybatis-config.xml";
    InputStream inputStream;
    SqlSession sqlSession = null;
    try {
        inputStream = Resources.getResourceAsStream(resource);
        //2、初始化mybatis,创建SqlSessionFactory类实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //3、创建Session实例
        sqlSession = sqlSessionFactory.openSession();
        //4、获取Mapper接口
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //5、执行SQL操作
        User user = userMapper.getById(1L);
        System.out.println(user);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //6、关闭sqlSession会话
        if (null != sqlSession) {
            sqlSession.close();
        }
    }
}

大体步骤:

  • 1、加载Mybatis的全局配置文件;
  • 2、初始化mybatis,创建SqlSessionFactory类实例;
  • 3、创建Session会话;
  • 4、获取Mapper接口;
  • 5、执行具体SQL;

其中加载全局配置文件、创建SqlSessionFactory、扫描mapper接口都是比较重要的,所以分析Spring加载MyBatis的过程无非也就是从这几方面入手。

我们来看看Spring要集成Mybatis需要做哪些配置:

代码语言:javascript复制
<!--定义数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://127.0.0.1:3306/user_mybatis"/>
  <property name="username" value="root"/>
  <property name="password" value="root"/>
</bean>
 
<!--定义sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--数据库连接池 -->
    <property name="dataSource" ref="dataSource"/>
    <!--配置mybatis全局配置文件: mybatis-config.xml -->
    <property name="configLocation" value="classpath:/mybatis-config.xml"/>
    <!--扫描entity包,使用别名,多个用;隔开 -->
    <property name="typeAliasesPackage" value="entity"/>
    <!--扫描sql配置文件:mapper需要的xml文件 -->
    <property name="mapperLocations" value="classpath:/mapper/*.xml"/>
</bean>
 
<!-- 配置mapper接口路径,并注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <property name="basePackage" value="com.wsh.mybatis.mybatisdemo.mapper"/>
</bean>

我们看到,有两个主要的配置类:

  • SqlSessionFactoryBean:Spring就是利用它来创建Mybatis的SqlSessionFactory;
  • MapperScannerConfigurer:Spring使用它来扫描我们的mapper接口,并注册到IOC中;

下面分别来看看Spring是如何帮我们自动创建SqlSessionFactory的,SqlSessionFactoryBean类的继承关系图如下:

可以看到SqlSessionFactoryBean类实现了三个接口,一个是InitializingBean,另一个是FactoryBean,还有就是ApplicationListener接口。

  • InitializingBean接口:如果某个bean实现了这个接口,当bean初始化的时候,spring就会调用该接口的实现类的afterPropertiesSet方法,去实现当spring初始化该Bean的时候所需要的逻辑;
  • FactoryBean接口:如果某个bean实现了这个接口,在调用getBean的时候会返回该工厂返回的实例对象,也就是再调一次getObject方法返回工厂的实例;
  • ApplicationListener接口:如果某个bean实现了这个接口,如果注册了该监听的话,那么就可以了监听到Spring的一些事件,然后做相应的处理;

所以我们先来看看SqlSessionFactoryBean类的afterPropertiesSet()做了什么事情?

代码语言:javascript复制
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
 
    /**
     * 实现了InitializingBean接口的Bean,在Spring初始化的时候会回调afterPropertiesSet()方法
     */
    @Override
    public void afterPropertiesSet() throws Exception {
      notNull(dataSource, "Property 'dataSource' is required");
      notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
      state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
                "Property 'configuration' and 'configLocation' can not specified with together");
      
      //具体创建SqlSessionFactory的方法
      this.sqlSessionFactory = buildSqlSessionFactory();
    }
}

从源码中可以看到,通过buildSqlSessionFactory()方法创建了一个SqlSessionFactory,继续跟踪代码:

代码语言:javascript复制
/**
 * 构建一个SqlSessionFactory实例
 *
 *  默认实现使用标准的MyBatis XMLConfigBuilderAPI来构建
 *  基于读取器的SqlSessionFactory实例。
 *  从1.3.0开始,它可以被直接指定为Configuration实例(不需要配置文件)。
 */
 //org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
	//全局配置对象,后续很多解析XML之后的配置都会保存在configuration对象中.
    Configuration configuration;
	
	//XMLConfigBuilder这个类应该比较熟悉,就是Mybatis解析XML配置的核心类
    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
	  //初始化xmlConfigBuilder,通过我们配置的configLocation全局配置的位置
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }
 
	//设置对象工厂
    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }
 
    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }
 
    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }
 
	//处理别名配置
    if (hasLength(this.typeAliasesPackage)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '"   packageToScan   "' for aliases");
        }
      }
    }
 
    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '"   typeAlias   "'");
        }
      }
    }
 
	//处理插件相关
    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '"   plugin   "'");
        }
      }
    }
 
	//处理类型转换器相关逻辑
    if (hasLength(this.typeHandlersPackage)) {
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '"   packageToScan   "' for type handlers");
        }
      }
    }
 
    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type handler: '"   typeHandler   "'");
        }
      }
    }
 
    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }
 
	//处理缓存相关
    if (this.cache != null) {
      configuration.addCache(this.cache);
    }
 
    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
 
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed configuration file: '"   this.configLocation   "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: "   this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
 
    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
 
	//设置Environment环境参数
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
 
	
    if (!isEmpty(this.mapperLocations)) {
	 //mapper接口路径
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
 
        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          //执行XML解析
		  xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '"   mapperLocation   "'", e);
        } finally {
          ErrorContext.instance().reset();
        }
 
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '"   mapperLocation   "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
	
	//最后这里调用的是sqlSessionFactoryBuilder.build构建一个sqlSessionFactory,并返回
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

从源码中我们可以看到buildSqlSessionFactory()方法主要做了几件事情:

  • 1、组装全局配置对象Configuration,将很多解析XML之后的配置都会保存在configuration对象中;
  • 2、使用这些配置通过SqlSessionFactoryBuilder创建SqlSessionFactory;
  • 3、最后调用了sqlSessionFactoryBuilder.build();

下面我们看一下sqlSessionFactoryBuilder.build()方法,发现此方法就是Mybatis创建sqlSessionFactory的方法,在前面的文章中有详细介绍过,这里就不过多赘述了。

代码语言:javascript复制
//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

到这里,SqlSessionFactory已经创建完成了,即SqlSessionFactoryBean的初始化完成。

下面我们看一下如何获取SqlSessionFactoryBean实例,前面说过SqlSessionFactoryBean实现了FactoryBean接口,所以当我们通过getBean获取它的实例的时候实际是调用它的getObject方法,获取到的是sqlSessionFactory。

代码语言:javascript复制
/**
 * 返回IOC容器的Bean
 */
@Override
public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }
   //返回创建好的sqlSessionFactory
  //sqlSessionFactory就是在afterPropertiesSet()方法执行完后赋值的
  return this.sqlSessionFactory;
}
 
/**
 * 返回IOC容器Bean的类型
 */
@Override
public Class<? extends SqlSessionFactory> getObjectType() {
  return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}
 
/**
 * 是否单例
 */
@Override
public boolean isSingleton() {
  return true;
}
三、Mapper接口的获取

下面就需要看下Spring是如何加载我们的Mapper接口了,Spring里面主要通过MapperScannerConfigurer类来扫描我们的mapper接口,并将它们注册到IOC容器中。

  • MapperScannerConfigurer:Spring使用它来扫描我们的mapper接口,并注册到IOC中;

首先看一下MapperScannerConfigurer类的继承关系图:

重点关注一下上图红框框起来的,这是Spring扫描mapper接口的核心类:

可以看到MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,重写了postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法,postProcessBeanDefinitionRegistry方法允许我们通过编码的方式,改变、新增类的定义信息。 我们来看一下具体的源码:

代码语言:javascript复制
//org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
    processPropertyPlaceHolders();
  }
  //创建一个mapper扫描器
  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  //设置一些属性,如SqlSessionFactory、BeanNameGenerator等
  scanner.setAddToConfig(this.addToConfig);
  scanner.setAnnotationClass(this.annotationClass);
  scanner.setMarkerInterface(this.markerInterface);
  scanner.setSqlSessionFactory(this.sqlSessionFactory);
  scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  scanner.setResourceLoader(this.applicationContext);
  scanner.setBeanNameGenerator(this.nameGenerator);
  scanner.registerFilters();
  //扫描具体的mapper接口,并加入到IOC容器中
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

MapperScannerConfigurer已经将Mapper扫描,并加入到IOC容器中了,那我们是如何从Spring的IOC中获取mapper接口的,这时候另外一个类:MapperFactoryBean类就发挥作用了。

代码语言:javascript复制
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
 
  private Class<T> mapperInterface;
 
  private boolean addToConfig = true;
 
  public MapperFactoryBean() {
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
 
  /**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
 
  /**
   * {@inheritDoc}
   */
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
 
  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isSingleton() {
    return true;
  }
 
 
}

我们看到,MapperFactoryBean实现了FactoryBean接口,那么在调用getBean方法获取MapperFactoryBean实例的时候,实际上调用的就是getObject方法。

代码语言:javascript复制
public T getObject() throws Exception {
  //具体获取流程,其实就是Mybatis自己去做了
  return getSqlSession().getMapper(this.mapperInterface);
}

如上可以看到,Spring内部封装了获取mapper接口的操作,所以我们不需要去手动管理SqlSession,Spring帮我们自动管理并获取mapper接口。

四、总结

本文主要总结了Spring加载MyBatis的过程,有几个关键的类:

  • SqlSessionFactoryBean:实现了InitializingBean和FactoryBean接口,主要完成的工作就是构建SqlSessionFactory对象,重点关注afterPropertiesSet()方法和getObject()方法;
  • MapperScannerConfigurer:实现了BeanDefinitionRegistryPostProcessor接口,主要完成的工作就是扫描我们配置的mapper接口,并注册到IOC中。重点关注postProcessBeanDefinitionRegistry方法;
  • MapperFactoryBean:实现了FactoryBean接口,重点关注getObject()方法,主要完成mapper接口的获取;

Spring加载MyBatis这个过程,其实就是把MyBatis的Mapper接口转换成Bean,注入到Spring容器的过程。

鉴于笔者水平有限,如果文章有什么错误或者需要补充的,希望小伙伴们指出来,希望这篇文章对大家有帮助。

0 人点赞