Mybatis源码01-SpringBoot启动时mybatis加载过程

使用了mybatis这么久还没有具体探究了SpringBoot启动时候对于mybatis是怎么加载的。

1、首先项目构建时我们会引入相关的依赖:

       <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!--MyBatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.9</version>
        </dependency>

mybatis-spring-boot-starter这个包声明了自动构建MyBatis的一些配置,以及创建SqlSessionFactory的类

1 MybatisAutoConfiguration

 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    // 设置数据源
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    // 加载 MyBatis 全局配置文件 例:如果配置了 mybatis.config-location(如 classpath:mybatis-config.xml),则加载该全局配置文件。
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    // 将 application.yml 中的 MyBatis 配置(以 mybatis.configuration.* 开头的属性)应用到 SqlSessionFactory
    applyConfiguration(factory);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
     // 注册 MyBatis 插件 例如分页插件
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    // 多数据库支持
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    // 设置类型别名扫描包
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
        factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }

    // 设置类型别名的父类
    if (this.properties.getTypeAliasesSuperType() != null) {
        factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    
    // 设置类型处理器扫描包
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
        factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    
    // 注册自定义类型处理器
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
        factory.setTypeHandlers(this.typeHandlers);
    }
    // 加载sql mapper.xml文件路径 路径通过 mybatis.mapper-locations 配置。
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    // 动态 SQL 语言驱动
    Set<String> factoryPropertyNames = Stream
        .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
      // Need to mybatis-spring 2.0.2+
      factory.setScriptingLanguageDrivers(this.languageDrivers);
      if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
        defaultLanguageDriver = this.languageDrivers[0].getClass();
      }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
      // Need to mybatis-spring 2.0.2+
      factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }

    // 调用 SqlSessionFactoryBean 的 getObject() 方法,生成最终的 SqlSessionFactory 实例。
    return factory.getObject();
  }

作用

sqlSessionFactory 方法的主要目的是 自动构建并配置 MyBatis 的 SqlSessionFactory 实例,使得开发者无需手动编写大量 XML 或 Java 配置即可集成 MyBatis。该方法通过以下步骤实现:

  1. 依赖注入关键组件 利用 Spring Boot 的自动装配机制,注入必要的依赖(如 `DataSource、MyBatis 配置属性等)。
  2. 应用 MyBatis 全局配置 加载 `mybatis-config.xml(如果存在)或通过属性配置 MyBatis 的行为(如缓存、插件等)。
  3. 配置 Mapper 文件位置 扫描并注册 Mapper 接口或 XML 文件,使其能被 MyBatis 识别。
  4. 整合 Spring 事务管理
    确保 SqlSessionFactory` 与 Spring 的事务管理器兼容。

二、 SqlSessionFactoryBean

factory.getObject() 创建sqlSessionFactory

最终在buildSqlSessionFactory这个方法完成创建。

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      // 使用已经有的Configuration 对象
      targetConfiguration = this.configuration;
      // 合并全局变量(configurationProperties)
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      // 通过 XML 配置文件初始化 Configuration
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      // 创建默认 Configuration 并设置全局变量
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    /**
     * 设置核心扩展组件
     * ObjectFactory:控制对象(如 POJO)的实例化方式。
     * ObjectWrapperFactory:用于包装返回对象(如集合)。
     * VFS:虚拟文件系统实现(如处理 Spring Boot 内嵌 JAR 中的资源)
    */
    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
    
    // 扫描包下的类注册别名
    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);
    }
    // 显式注册类型别名
    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }
    // 注册插件
    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
    // 处理类型处理器
    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);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    // 多数据库支持
    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
    // 解析 XML 配置文件
    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        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();
      }
    }
    // SpringManagedTransactionFactory 确保 MyBatis 事务由 Spring 管理。
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    // 加载和解析 Mapper XML文件
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
    // 构建 SqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

图例解析:

三、 XMLMapperBuilder

XMLMapperBuilder继承于BaseBuilder。他们对于XML文件本身技术上的加载和解析都委托给了XPathParser,最终用的是jdk自带的xml解析器而非第三方比如dom4j,底层使用了xpath方式进行节点解析。new XPathParser(reader, true, props, new XMLMapperEntityResolver())的参数含义分别是Reader,是否进行DTD 校验,属性配置,XML实体节点解析器。
entityResolver比较好理解,跟Spring的XML标签解析器一样,有默认的解析器,也有自定义的比如tx,dubbo等,主要使用了策略模式,在这里mybatis硬编码为了XMLMapperEntityResolver。

XMLMapperEntityResolver的定义如下

public class XMLMapperEntityResolver implements EntityResolver {

  private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
  private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
  private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";

  private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
  private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";

  /*
   * Converts a public DTD into a local one 
   * 将公共的DTD转换为本地模式
   * 
   * @param publicId The public id that is what comes after "PUBLIC"
   * @param systemId The system id that is what comes after the public id.
   * @return The InputSource for the DTD
   * 
   * @throws org.xml.sax.SAXException If anything goes wrong
   */
  @Override
  public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
    try {
      if (systemId != null) {
        String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
        if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
          return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
        } else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
          return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
        }
      }
      return null;
    } catch (Exception e) {
      throw new SAXException(e.toString());
    }
  }

  private InputSource getInputSource(String path, String publicId, String systemId) {
    InputSource source = null;
    if (path != null) {
      try {
        InputStream in = Resources.getResourceAsStream(path);
        source = new InputSource(in);
        source.setPublicId(publicId);
        source.setSystemId(systemId);        
      } catch (IOException e) {
        // ignore, null is ok
      }
    }
    return source;
  }

}

该类的核心作用是通过本地加载 DTD/XSD 验证文件,避免 XML 解析时从网络下载外部资源

继续往下看

  public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(reader));
  }
  private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

主要看 createDocument

  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setValidating(validation);
      //设置由本工厂创建的解析器是否支持XML命名空间 TODO 什么是XML命名空间
      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      //设置是否将CDATA节点转换为Text节点
      factory.setCoalescing(false);
      //设置是否展开实体引用节点,这里应该是sql片段引用的关键
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      //设置解析mybatis xml文档节点的解析器,也就是上面的XMLMapperEntityResolver
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

主要是根据mybatis自身需要创建一个文档解析器,然后调用parse将输入input source解析为DOM XML文档并返回。

得到XPathParser实例之后,就调用另一个使用XPathParser作为配置来源的重载构造函数了,如下:

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

其中调用了父类BaseBuilder的构造器(主要是设置类型别名注册器,以及类型处理器注册器):

XMLConfigBuilder创建完成之后,SqlSessionFactoryBean调用xmlMapperBuilder.parse();创建Configuration。所有,真正Configuration构建逻辑就在xmlMapperBuilder.parse();里面,如下所示:

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

1. 检查资源是否已加载 (`configuration.isResourceLoaded(resource))
  • 作用
    确保同一 Mapper XML 文件不会被重复解析(如多线程环境或错误配置时)。
    实现原理
    configuration对象维护了一个Set loadedResources,存储所有已加载的 XML 文件路径(如com/example/UserMapper.xml)

2. 解析根标签 (configurationElement(…))
  • 作用
  • 解析 XML 文件中标签下的所有配置元素,
    包括:
    • 二级缓存配置
    • 结果集映射
    • 可重用的 SQL 片段
    • :SQL 语句定义

实现逻辑

  • java复制下载
private void configurationElement(XNode context) {
    // 获取 namespace 属性(必须对应 Mapper 接口的全限定名)
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);

    // 解析缓存配置
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));

    // 解析所有 <resultMap>
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    
    // 解析所有 <sql> 片段
    sqlElement(context.evalNodes("/mapper/sql"));
    
    // 解析所有 SQL 语句(select|insert|update|delete)
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}

3. 标记资源为已加载 ( addLoadedResource )
  • 目的
    将当前 XML 文件路径(如 com/example/UserMapper.xml)添加到 `loadedResources 集合,后续再次解析同一文件时会直接跳过。

4. 绑定 Mapper 接口 (bindMapperForNamespace)
  • 作用
    将 XML 中 namespace 属性指定的接口(如 com.example.UserMapper`)注册到 MyBatis,使得接口方法与 XML 中的 SQL 语句关联。

实现逻辑

  • java复制下载
private void bindMapperForNamespace() {
    // 获取 namespace 对应的接口类
    Class<?> boundType = Resources.classForName(namespace);
    // 将接口添加到 MapperRegistry
    if (boundType != null && !configuration.hasMapper(boundType)) {
        configuration.addMapper(boundType);
    }
}
  • 关键点
    MyBatis 通过动态代理为接口生成实现类,将方法调用映射到 XML 中的 SQL 语句。

5. 处理延迟解析的依赖项
  • 背景
    某些配置项(如 `)可能依赖其他尚未解析的资源(如其他 XML 文件中的定义),需要延迟解析。
  • 具体方法
    • parsePendingResultMaps()
      解析之前未能完全初始化的 ResultMap(例如,引用了其他 ResultMap` 的情况)。
    • parsePendingCacheRefs()
      处理 标签引用的其他命名空间的缓存配置。
    • parsePendingStatements()
      完成所有 SQL 语句的最终解析(如确认引用的 resultMapparameterType` 已存在)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值