锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. JAVA
  4. 【MyBatis】MyBatis是怎么在Spring Boot中初始化的?

【MyBatis】MyBatis是怎么在Spring Boot中初始化的?

0
  • JAVA
  • 发布于 2024-08-15
  • 0 次阅读
黄健
黄健

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

目的

  1. 复习 Spring Boot 项目中 IoC 加载的主要流程
  2. 了解如何配置自定义的 AutoConfiguration
  3. (重点) 了解 MyBatis 如何在 Spring Boot 项目中加载配置和初始化

注:本文针对的是 MyBatis 和 Spring Boot 关联内容,没有详细讲解 MyBatis 本身的配置和初始化,这部分会在专题的其他文章中再详述。

先修知识

  1. 需要简单了解 Spring Boot 的 IoC 加载主要流程
  2. 需要了解如何使用 MyBatis
  3. 最好了解单纯的 Java+MyBatis 项目中,MyBatis 如何加载配置和初始化

sample 项目

直接用官网提供的 Quick-Start 的 Sample 项目做本文的样例项目。别管这个项目有多简单,只有真的上手去跑,去看代码,才有感觉。

Quick Start · mybatis/spring-boot-starter Wiki · GitHub

使用下面的 curl 命令拉取 sample 项目后,按照上面官方 Wiki 文档的内容依次将所需文件内容写入项目中

curl -s https://start.spring.io/starter.tgz\
       -d name=mybatis-sample\
       -d artifactId=mybatis-sample\
       -d dependencies=mybatis,h2\
       -d baseDir=mybatis-sample\
       -d type=maven-project\
       | tar -xzvf -

这个项目的基本信息:

jdk17,spring-boot 3.2.4,mybatis-spring-boot-starter 3.0.3,数据库使用内存数据库 h2

mybatis-spring-boot-starter 3.0.3 这个 MAVEN 包中又包含:

  • mybatis
  • mybatis-spring
  • mybatis-spring-boot-autoconfigure
  • spring-boot-starter
  • spring-boot-starter-jdbc

Sample 项目运行结果:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.4)
 
2024-04-01T16:47:21.323+08:00  INFO 66794 --- [mybatis-sample] [           main] c.e.m.MybatisSampleApplication           : Starting MybatisSampleApplication using Java 17 with PID 66794 (/Users/xxx/Documents/study/mybatis_study/mybatis-sample/target/classes started by zhuangsuyu in /Users/xxx/Documents/study/mybatis_study/mybatis-sample)
2024-04-01T16:47:21.326+08:00  INFO 66794 --- [mybatis-sample] [           main] c.e.m.MybatisSampleApplication           : No active profile set, falling back to 1 default profile: "default"
2024-04-01T16:47:22.158+08:00  INFO 66794 --- [mybatis-sample] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-04-01T16:47:22.314+08:00  INFO 66794 --- [mybatis-sample] [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:a1672050-cc5a-4f4f-b0c9-b28b3b635f20 user=SA
2024-04-01T16:47:22.315+08:00  INFO 66794 --- [mybatis-sample] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-04-01T16:47:22.585+08:00  INFO 66794 --- [mybatis-sample] [           main] c.e.m.MybatisSampleApplication           : Started MybatisSampleApplication in 1.594 seconds (process running for 1.997)
1,San Francisco,CA,US
2024-04-01T16:47:22.648+08:00  INFO 66794 --- [mybatis-sample] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-04-01T16:47:22.651+08:00  INFO 66794 --- [mybatis-sample] [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

AutoConfiguration 的配置

auto-configuration 官方文档

我们知道 Spring Boot 的设计核心原则之一是简化配置(约定优于配置):让一些常用的配置直接默认,如果是特殊需要定制的配置,用户可以用自己的 Bean 实例去替换。

而实现 “约定优于配置” 这一原则的核心功能之一就是 AutoConfiguration

在不同的 Spring Boot 版本中,AutoConfiguration 的写法略有不同,下面展示的是不同版本中,Mybatis 官方提供的 MybatisAutoConfiguration 源码配置方式,稍作了解即可。

在 spring-boot.2.7.x 之前版本里,

在目录 META-INF/下新建文件 spring.factories

文件中内容填写:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

在 spring-boot.2.7.x 之后版本里,

在目录 META-INF/spring/下新建文件 org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件中内容填写:

org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

简述 SpringIoC 的加载过程

在 Spring Boot 启动过程中,我们需要知道 AbstractApplicationContext 类中有个非常关键的**refresh()**方法。

**refresh()**方法中又有两个重要的方法:

  1. **invokeBeanFactoryPostProcessors(beanFactory);**调用已经注册过了的 Bean 工厂后置处理器,用来解析和加载各种配置类,注册到 beanDefinitionMap
    • 先分别 getBean 之前的 Bean 工厂后置处理器(如 internalConfigurationAnnotationProcessor)
    • 然后利用 ConfigurationClassPostProcessor 这个 Bean 工厂后置处理器,解析各种加了 @Configuration的配置类, @ComponentScan包, @Import 注解等
  2. **finishBeanFactoryInitialization(beanFactory);**实例化所有剩余的单例 bean
    1. 利用反射的方式实例化
    2. 根据 DI,填充属性
    3. 初始化

以下是一个常见的**refresh()**方法,粘贴在这里帮助大家对我们要讲的两个重点方法在哪个位置有个大致印象。

@Override
public void refresh() throws BeansException, IllegalStateException {
    this.startupShutdownLock.lock();
    try {
        // 一些准备工作...
        try {
            postProcessBeanFactory(beanFactory);
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
       
 
            // 【重点】调用ConfigurationClassPostProcessor解析配置类,会注册生成beanDefinitionMap
            invokeBeanFactoryPostProcessors(beanFactory);
          
 
            registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();
            initMessageSource();
            initApplicationEventMulticaster();
            onRefresh();
            registerListeners();
 
 
            // 【重点】 Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);
 
 
            finishRefresh();
        }
 
        catch (RuntimeException | Error ex ) {
           // 一些catch工作...
        } finally {
            contextRefresh.end();
        }
    } finally {
        this.startupShutdownThread = null;
        this.startupShutdownLock.unlock();
    }
}

源码总体流程图

从 refresh() 方法中的两个重点方法出发,一步步会通过默认配置、用户自定义配置将 MyBatis 需要的信息加载到框架中。

这里我先将我整理的源码总体流程图贴在这里,后面的内容会详细、局部地去说明各个环节的执行流程。

图中左上方灰色方块底的模块是发生在 Spring Boot 包中的,绿色方块底的模块发生在 mybatis-spring 包中,图正中偏右下的红色方块底是 mybatis-spring-boot-autoconfigure 提供的能力,蓝紫色方块底是最单纯的 mybatis 包本身了。

mybatis-spring 包在各个包中起到了连接作用。

xml 配置文件的解析流程

先说 xml 配置文件。xml 配置文件一般长下面这样,文件本身放在项目的 resources 目录下。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property />
                    <property />
                    <property />
                    <property />
                </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="aaaa/bbbb.xml"/>
    </mappers>
</configuration>

如果配置了 mybatis-config.xml 配置文件,该文件的解析和配置是在本文所述的 refresh() 第二个核心方法(即实例化各个 bean 的阶段)发生的,如下图所示。

在调用**SqlSessionFactoryBean的factory.getObject()方法时,会在buildSqlSessionFactory()**方法中进行 parse,使用 mybatis 包中的 XMLConfigBuilder 解析 xml 配置文件。

当然,项目也可能自己注入了一个自定义的 SqlSessionFactory,仍然也是会调用到**factory.getObject()**方法的,如下所示:

package com.example.mybatissample.config;
 
 
@Configuration
@MapperScan("com.example.mybatissample")
public class MybatisConfig {
 
    @Autowired
    DataSource dataSource;
 
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactoryBean(
            @Value("classpath:/mybatis/mybatis-config.xml") Resource configLocation
    ) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setConfigLocation(configLocation);
        return sqlSessionFactoryBean.getObject();
    }
}

当然,在实际项目中,我们也可能完全不写 xml 配置文件。这样便于管理配置信息,避免配置分散在不同文件里。

Mapper 接口和 xml 映射文件的扫描加载

mapper 接口

我们知道有以下两种配置 mapper 接口类的方法,这些配置方式到底是怎么影响扫描的?

  • @MapperScan,配合 @Repository(后者可以不配置,不写最多可能会导致有的版本的 idea 在注入的地方有红色波浪线)
  • @Mapper,如果不使用 @MapperScan 统一扫描包,那么每个 mapper 接口上都一定要注解 @Mapper。注意,在使用了 @MapperScan 的情况下,自定义的包外面单独的 @Mapper 就没用了。

所以在大型项目中,一般都会采用 @MapperScan 的方式。

在没有 @MapperScan 的情况下,AutoConfiguredMapperScannerRegistrar 类中的 registerBeanDefinitions() 方法会在 load 阶段调用,用来生成默认的 MapperScannerConfigurer 类的 beanDefinition,该默认配置中的配置的 basePackage 是整个项目的 Application 类所在的包。

mapper 接口的扫描也发生在 SpringIoC 的生成 beanDefinition 阶段,通过 MapperScannerConfigurer

在有 @MapperScan 的情况下

MybatisAutoConfiguration 类下的 MapperScannerRegistrarNotFoundConfiguration 类的 ConditionalOnMissingBean 条件不满足,所以不会注册默认的 MapperScannerConfigurer 类的 beanDefinition,而是使用用户自定义的方式。

@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
 
    @Override
    public void afterPropertiesSet() {
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
    }
 
  }

mapper 接口的映射文件(xml)

配置 mapper 接口的映射文件

  • setMapperLocations
  • 在 xml 配置文件中 下的 < mapper resource=""> 标签 < mapper resdource="/mybatis/mapper/StudentMapperDAO.xml"/>
  • 接口文件与映射文件在同一路径下,接口名与映射文件名相同,并且映射文件命名为接口类名时, 在 xml 配置文件中 下的 < mapper > 标签 <mapper/>
  • 接口文件与映射文件在同一路径下,接口名与映射文件名相同,并且映射文件命名为接口类名时, 在 xml 配置文件中 下的 < package > 标签<mapper/>

映射文件的解析是在的 refresh() 第二个核心方法(即实例化各个 bean 的阶段)发生的解析的。

使用 mybatis 包中的 XMLConfigBuilder 解析 xml 配置文件的最后,会交由 XMLMapperBuilder 类负责映射文件的解析,生成 mappedStatements 存放在 configuration 对象中。这部分就是纯 mybatis 源码里的内容了,不在这里赘述。

private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfsImpl(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginsElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlersElement(root.evalNode("typeHandlers"));
            this.mappersElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

总结

 在这篇文章里,我们先梳理了 Spring Boot IoC 的基本流程,确定了 refresh() 中两个最重点的方法。通过将这两个重点方法与 MyBatis 配置加载和初始化相结合的方式,明确了 xml 配置文件、Mapper 接口以及映射文件的加载、解析分别发生在这两个方法的什么环节。

换句话说,本质上我们是先在脑海中通过 Spring Boot IoC 的基本流程绘制了一张简要地图,然后在这张地图中圈出了哪些地点是 MyBatis 加载、解析、初始化的重要地方。

核心知识点简要总结,Spring Boot IoC 的基本流程里 refresh() 中有两个重点方法,分别是为了形成 beanDefinitionMap 的**invokeBeanFactoryPostProcessors(beanFactory);和为了实例化各种剩余bean的finishBeanFactoryInitialization(beanFactory)**;mapper 接口的扫描发生在第 1 个重点方法;xml 配置文件的解析和 xml 映射文件的解析,都发生在第 2 个重点方法中。

标签: #软件开发 1171 #JAVA 991
相关文章

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

SpringBoot整合异步任务执行 2024-10-08 11:24

同步任务: 同步任务是在单线程中按顺序执行,每次只有一个任务在执行,不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务,无法同时处理多个任务,响应慢,影响用 户体验 异步任务: 异步任务是在多线程中同时执行,多个任务可以并发执行,同时处理多个请求,响应快,资源

springboot kafka多数据源,通过配置动态加载发送者和消费者 2024-10-08 11:24

前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。 准备test kafka本身非常容易上手,如果我们需要单元测试,引入ja

SpringBoot 集成 Redis 2024-10-08 11:24

一:SpringBoot 集成 Redis ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list ③通过Redis客户端可以使用多种语

SpringBoot整合QQ邮箱 2024-10-08 11:24

SpringBoot可以通过导入依赖的方式集成多种技术,这当然少不了我们常用的邮箱,现在本章演示SpringBoot整合QQ邮箱发送邮件…. 下面按步骤进行: 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域,如果当前账号未开启的话自己手动开启。

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.