前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis源码阅读(十二) --- Spring加载MyBatis过程

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

作者头像
终有救赎
发布2024-01-30 09:04:45
1430
发布2024-01-30 09:04:45
举报
文章被收录于专栏:多线程多线程

欢迎关注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类的继承关系图如下:

图片.png
图片.png

可以看到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类的继承关系图:

图片.png
图片.png

重点关注一下上图红框框起来的,这是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容器的过程。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、Spring加载MyBatis过程
  • 三、Mapper接口的获取
  • 四、总结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档