当前位置: 首页 > ds >正文

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.* 开头的属性)应用到 SqlSessionFactoryapplyConfiguration(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();}

作用

**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">sqlSessionFactory</font>** 方法的主要目的是 自动构建并配置 MyBatis 的**** **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">SqlSessionFactory</font>** ****实例,使得开发者无需手动编写大量 XML 或 Java 配置即可集成 MyBatis。该方法通过以下步骤实现:

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

二、 SqlSessionFactoryBean

factory.getObject() 创建**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">sqlSessionFactory</font>**

最终在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 配置文件初始化 ConfigurationxmlConfigBuilder = 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 xmlstry {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.");}// 构建 SqlSessionFactoryreturn 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*/@Overridepublic 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 constructortry {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文档节点的解析器,也就是上面的XMLMapperEntityResolverbuilder.setEntityResolver(entityResolver);builder.setErrorHandler(new ErrorHandler() {@Overridepublic void error(SAXParseException exception) throws SAXException {throw exception;}@Overridepublic void fatalError(SAXParseException exception) throws SAXException {throw exception;}@Overridepublic 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. 检查资源是否已加载 (**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">configuration.isResourceLoaded(resource)</font>**)
  • 作用
    确保同一 Mapper XML 文件不会被重复解析(如多线程环境或错误配置时)。
  • 实现原理
    **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">configuration</font>** 对象维护了一个 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">Set<String> loadedResources</font>**,存储所有已加载的 XML 文件路径(如 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">com/example/UserMapper.xml</font>**)。

2. 解析**** **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);"><mapper></font>** ****根标签 (**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">configurationElement(...)</font>**)
  • 作用
    解析 XML 文件中 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);"><mapper></font>** 标签下的所有配置元素,包括:
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);"><cache></font>**:二级缓存配置
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);"><resultMap></font>**:结果集映射
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);"><sql></font>**:可重用的 SQL 片段
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);"><select|insert|update|delete></font>**: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. 标记资源为已加载 (**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">addLoadedResource</font>**)
  • 目的
    将当前 XML 文件路径(如 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">com/example/UserMapper.xml</font>**)添加到 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">loadedResources</font>** 集合,后续再次解析同一文件时会直接跳过。

4. 绑定 Mapper 接口 (**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">bindMapperForNamespace</font>**)
  • 作用
    将 XML 中 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">namespace</font>** 属性指定的接口(如 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">com.example.UserMapper</font>**)注册到 MyBatis,使得接口方法与 XML 中的 SQL 语句关联。

实现逻辑

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

5. 处理延迟解析的依赖项
  • 背景
    某些配置项(如 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);"><resultMap></font>**)可能依赖其他尚未解析的资源(如其他 XML 文件中的定义),需要延迟解析。
  • 具体方法
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">parsePendingResultMaps()</font>**
      解析之前未能完全初始化的 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">ResultMap</font>**(例如,引用了其他 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">ResultMap</font>** 的情况)。
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">parsePendingCacheRefs()</font>**
      处理 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);"><cache-ref></font>** 标签引用的其他命名空间的缓存配置。
    • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">parsePendingStatements()</font>**
      完成所有 SQL 语句的最终解析(如确认引用的 **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">resultMap</font>****<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">parameterType</font>** 已存在)。
http://www.xdnf.cn/news/513.html

相关文章:

  • U-Boot 启动过程详解
  • 杂记-2025年4月19日
  • Linux压缩与解压命令完全指南:tar.gz、zip等格式详解
  • JAVA 继承
  • 【EDA软件】【设计约束和分析操作方法】
  • 【AI提示词】经济学家
  • 使用Ingress发布应用程序
  • MySQL——事务
  • 【java实现+4种变体完整例子】排序算法中【快速排序】的详细解析,包含基础实现、常见变体的完整代码示例,以及各变体的对比表格
  • Day4-存储技术概述
  • csdn教程
  • 统信UOS1060中恢复默认出厂设置
  • 使用 YOLOv8 模型对外接摄像头(设备索引为 1)实时分析
  • 端口镜像,
  • Java InvalidClassException 深度解析
  • Linux网络编程——I/O多路转接(2)之 poll、epoll
  • Mesh模型孔洞修补算法总汇
  • 【大疆dji】什么是ESDK?
  • 腾讯云对象存储m3u8文件使用腾讯播放器播放
  • 【HDFS入门】HDFS性能调优实战:小文件问题优化方案
  • 基于Springboot+Mysql的的小区物业管理系统(含LW+PPT+源码+系统演示视频+安装说明)
  • 【web服务_负载均衡Nginx】三、Nginx 实践应用与高级配置技巧
  • Vue+Notification 自定义消息通知组件 支持数据分页 实时更新
  • 【大疆dji】边缘计算模块在大疆机场中的位置
  • 双指针算法(部分例题解析)
  • STM 单片机主要系列及特点
  • 【Python办公】图片批量裁剪工具(GUI打包版)
  • 6.8 Python定时任务实战:APScheduler+Cron实现每日/每周自动化调度
  • 服务器简介(含硬件外观接口介绍)
  • 【C++】新手入门指南(上)