首页 > 其他分享 >【Mybatis】Mybatis 是如何整合到 Spring 中的呢?

【Mybatis】Mybatis 是如何整合到 Spring 中的呢?

时间:2024-02-26 09:04:54浏览次数:23  
标签:configuration return Spring factory targetConfiguration 整合 Mybatis null properti

1  前言

当你把 Spring、SpringBoot、Mybatis 或者 Mybatis-Plus 的源码都看过后,那有没有想过比如 Mybatis 如何整合到 Spring 或者 SpringBoot 的呢?就是思考框架跟框架之间的融合,那么这节我们就来看看单纯的 Mybatis 是如何融合到 SpringBoot 的。融合 Spring 的就不看了,毕竟大家现在用的少,比较老的了,就不写了哈,主要看融合 SpringBoot 的。

SpringBoot 的一大特点就是自动装配,就是 Starter。那么 Mybatis 的融合也是基于此。

2  环境准备

首先我们新建个工程,然后引入 Mybatis,这里我直接截图,就不罗嗦了哈:

SpringBoot :2.3.7.RELEASE

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.7</version>
</dependency>

配置信息:

# 服务端口
server.port = 9900
# 驱动
spring.datasource.driver-class-name = org.postgresql.Driver
spring.datasource.hikari.connection-test-query=SELECT 1
# 数据库信息
spring.datasource.url = jdbc:postgresql://localhost:5432/test
spring.datasource.username = postgres
spring.datasource.password = sql626..
# 扫描 Mapper xml
mybatis.mapper-locations=classpath*:mapper/*.xml
# SQL日志
mybatis.configuration.log-impl = org.apache.ibatis.logging.stdout.StdOutImpl

启动类以及文件结构:

效果:

3  源码分析

好了,环境准备好了,我们就看看 Mybatis 是如何结合的,从哪里看起呢?我们是不是引入了一个 Starter,自动装配就从 Starter 看起:

3.1  MybatisAutoConfiguration 配置

可以看到 META-INF 下的 spring.factories:

Mybatis 最重要的几个类 Configuration、SqlSessionFactory就都在这里边了:

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
    private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
    private final MybatisProperties properties;
    private final Interceptor[] interceptors;
    private final TypeHandler[] typeHandlers;
    private final LanguageDriver[] languageDrivers;
    private final ResourceLoader resourceLoader;
    private final DatabaseIdProvider databaseIdProvider;
    private final List<ConfigurationCustomizer> configurationCustomizers;
    ...
}

3.2  MybatisProperties 属性

mybatis 前缀的属性都会封装进该对象:

@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {
    public static final String MYBATIS_PREFIX = "mybatis";
    ...  
    @NestedConfigurationProperty
    private Configuration configuration;
}

那么该对象是什么时候生成的呢?谁引进的呢?细心的会发现刚才在第一步里 MybatisAutoConfiguration 的构造函数里,依靠 Spring 的依赖注入(构造器注入)来创建的。

小细节:@NestedConfigurationProperty 属性嵌套,构造 Configuration 

3.3  SqlSessionFactoryBean

我们再看一个重要的 SqlSessionFactoryBean,看后缀就能知道它是 FactoryBean 形式创建的:

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    // 数据源
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    // configuration 配置对象(上一步的 MybatisProperties 中有的话就用它的,没有的话就创新一个新的)
    this.applyConfiguration(factory);
    if (this.properties.getConfigurationProperties() != null) {
        factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    // 插件集合
    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);
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
        factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    Set<String> factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Co
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
        factory.setScriptingLanguageDrivers(this.languageDrivers);
        if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
            defaultLanguageDriver = this.languageDrivers[0].getClass();
        }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
        factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }
    // getObject
    return factory.getObject();
}
private void applyConfiguration(SqlSessionFactoryBean factory) {
    // 先从 MybatisProperties 中拿
    org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
   // 发现没有或者也没配置的话 就创建一个新的
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
        configuration = new org.apache.ibatis.session.Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
        Iterator var3 = this.configurationCustomizers.iterator();
        while(var3.hasNext()) {
            ConfigurationCustomizer customizer = (ConfigurationCustomizer)var3.next();
            customizer.customize(configuration);
        }
    }
    factory.setConfiguration(configuration);
}

然后继续看 getObject() 方法:

public SqlSessionFactory getObject() throws Exception {
    // afterPropertiesSet 进行创建
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }
    return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
    // 必须校验
    Assert.notNull(this.dataSource, "Property 'dataSource' is required");
    Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
    // 创建
    this.sqlSessionFactory = this.buildSqlSessionFactory();
} 

继续进入 buildSqlSessionFactory 开始创建:

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    XMLConfigBuilder xmlConfigBuilder = null;
    Configuration targetConfiguration;
    Optional var10000;
    if (this.configuration != null) {
        targetConfiguration = this.configuration;
        if (targetConfiguration.getVariables() == null) {
            targetConfiguration.setVariables(this.configurationProperties);
        } else if (this.configurationProperties != null) {
            targetConfiguration.getVariables().putAll(this.configurationProperties);
        }
    } else if (this.configLocation != null) {
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
        targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
        LOGGER.debug(() -> {
            return "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration";
        });
        targetConfiguration = new Configuration();
        var10000 = Optional.ofNullable(this.configurationProperties);
        Objects.requireNonNull(targetConfiguration);
        var10000.ifPresent(targetConfiguration::setVariables);
    }
    var10000 = Optional.ofNullable(this.objectFactory);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::setObjectFactory);
    var10000 = Optional.ofNullable(this.objectWrapperFactory);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::setObjectWrapperFactory);
    var10000 = Optional.ofNullable(this.vfs);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::setVfsImpl);
    Stream var24;
    if (StringUtils.hasLength(this.typeAliasesPackage)) {
        var24 = this.scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream().filter((clazz) -> {
            return !clazz.isAnonymousClass();
        }).filter((clazz) -> {
            return !clazz.isInterface();
        }).filter((clazz) -> {
            return !clazz.isMemberClass();
        });
        TypeAliasRegistry var10001 = targetConfiguration.getTypeAliasRegistry();
        Objects.requireNonNull(var10001);
        var24.forEach(var10001::registerAlias);
    }
    if (!ObjectUtils.isEmpty(this.typeAliases)) {
        Stream.of(this.typeAliases).forEach((typeAlias) -> {
            targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
            LOGGER.debug(() -> {
                return "Registered type alias: '" + typeAlias + "'";
            });
        });
    }
    if (!ObjectUtils.isEmpty(this.plugins)) {
        Stream.of(this.plugins).forEach((plugin) -> {
            targetConfiguration.addInterceptor(plugin);
            LOGGER.debug(() -> {
                return "Registered plugin: '" + plugin + "'";
            });
        });
    }
    if (StringUtils.hasLength(this.typeHandlersPackage)) {
        var24 = this.scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter((clazz) -> {
            return !clazz.isAnonymousClass();
        }).filter((clazz) -> {
            return !clazz.isInterface();
        }).filter((clazz) -> {
            return !Modifier.isAbstract(clazz.getModifiers());
        });
        TypeHandlerRegistry var25 = targetConfiguration.getTypeHandlerRegistry();
        Objects.requireNonNull(var25);
        var24.forEach(var25::register);
    }
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
        Stream.of(this.typeHandlers).forEach((typeHandler) -> {
            targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
            LOGGER.debug(() -> {
                return "Registered type handler: '" + typeHandler + "'";
            });
        });
    }
    targetConfiguration.setDefaultEnumTypeHandler(this.defaultEnumTypeHandler);
    if (!ObjectUtils.isEmpty(this.scriptingLanguageDrivers)) {
        Stream.of(this.scriptingLanguageDrivers).forEach((languageDriver) -> {
            targetConfiguration.getLanguageRegistry().register(languageDriver);
            LOGGER.debug(() -> {
                return "Registered scripting language driver: '" + languageDriver + "'";
            });
        });
    }
    var10000 = Optional.ofNullable(this.defaultScriptingLanguageDriver);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::setDefaultScriptingLanguage);
    if (this.databaseIdProvider != null) {
        try {
            targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException var23) {
            throw new NestedIOException("Failed getting a databaseId", var23);
        }
    }
    var10000 = Optional.ofNullable(this.cache);
    Objects.requireNonNull(targetConfiguration);
    var10000.ifPresent(targetConfiguration::addCache);
    if (xmlConfigBuilder != null) {
        try {
            xmlConfigBuilder.parse();
            LOGGER.debug(() -> {
                return "Parsed configuration file: '" + this.configLocation + "'";
            });
        } catch (Exception var21) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var21);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    // 这里对以后理解 Mybatis 和 Spring 的事务两者结合帮助很大
    targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
    // 扫描我们的 Mapper.xml
    if (this.mapperLocations != null) {
        if (this.mapperLocations.length == 0) {
            LOGGER.warn(() -> {
                return "Property 'mapperLocations' was specified but matching resources are not found.";
            });
        } else {
            Resource[] var3 = this.mapperLocations;
            int var4 = var3.length;
            for(int var5 = 0; var5 < var4; ++var5) {
                Resource mapperLocation = var3[var5];
                if (mapperLocation != null) {
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    } catch (Exception var19) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
                    } finally {
                        ErrorContext.instance().reset();
                    }
                    LOGGER.debug(() -> {
                        return "Parsed mapper file: '" + mapperLocation + "'";
                    });
                }
            }
        }
    } else {
        LOGGER.debug(() -> {
            return "Property 'mapperLocations' was not specified.";
        });
    }
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

两个重要的:

(1)Environment 里的 TransactionFactory (SpringManagedTransactionFactory 对 Mybatis 和 Spring 事务的结合衔接)

(2)扫描 Mapper.xml 进行解析注入(这个就不细看了哈,之前看过了 xml 的解析,可以看我之前的哈)

3.4  AutoConfiguredMapperScannerRegistrar 

看到这里,我们还差什么?是不是就差 Mapper 接口没看到了?

可以看到我的启动类上 @MapperScan 用于扫描我们的 Mapper 接口,但是当我把这个去掉,程序还是能正常运行,这是为什么呢?这就是 MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar 发挥作用了:

并且它跟 @MapperScan 不冲突,当存在 @MapperScan的时候,那就交给 MapperScannerRegistrar 来处理了,没有的话,就交给 MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar 来处理,他会通过 @Mapper 标记来进行处理哈。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
    String[] value() default {};
}

具体如何创建代理(JDK 的代理噢),就不看了哈(之前看过了哈)可以参考这篇【Mapper 接口都是怎么注入到 Spring容器中的?https://www.cnblogs.com/kukuxjx/p/17505609.html】。

4  小结

好啦,看到这里就差不多了哈,画个图来简单小结下:

标签:configuration,return,Spring,factory,targetConfiguration,整合,Mybatis,null,properti
From: https://www.cnblogs.com/kukuxjx/p/18033472

相关文章

  • 使用SpringSecurity相关说明
    原理探析思路实现密码加密存储......
  • spring6入门开发案例步骤
    引入依赖创建类和方法编写配置文件4.测试Spring是如何创建对象的呢?原理是什么?//dom4j解析beans.xml文件,从中获取class属性值,类的全类名//通过反射机制调用无参数构造方法创建对象Classclazz=Class.forName("com.atguigu.spring6.bean.HelloWorld");//Objectobj......
  • mybatis
    MyBatis概述MyBatis是一个实现了数据持久化的开源框架,简单理解就是对JDBC进行封装MyBatis优点与JDBC相比,减少了50%以上的代码量。MyBatis是最简单的持久化框架,小巧并且简单易学。MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻......
  • SpringBoot结合Liquibase实现数据库变更管理
    https://juejin.cn/post/7171232605478584328 https://juejin.cn/post/7170857098538909732  前言研发过程中经常涉及到数据库变更,对表结构的修复及对数据的修改,为了保证各环境都能正确的进行变更,我们可能需要维护一个数据库升级文档来保存这些记录,有需要升级的环境按......
  • 从零开始学Spring Boot系列-Hello World
    欢迎来到从零开始学SpringBoot的旅程!在这个系列的第二篇文章中,我们将从一个非常基础但重要的示例开始:创建一个简单的SpringBoot应用程序,并输出“HelloWorld”。1.环境准备首先,确保你的开发环境已经安装了以下工具:JavaDevelopmentKit(JDK):SpringBoot需要Java来运行,所......
  • 一秒出图?SDXL-Turbo实时AI绘画整合包下载
     SDXLTurbo是一种快速生成的AI构图模型,它基于一种称为对抗性扩散蒸馏的新训练方法,该方法允许在1到4个步骤中以高图像质量对大规模基础图像扩散模型进行采样,并将其与对抗性损失相结合,以确保即使在一个或两个采样步骤的低阶模式下也能获得高图像保真度简单说,就是快速成图的同时......
  • Linux离线部署SpringBoot前后端分离项目
    本文介绍了在内网下的纯离线环境中部署SpringBoot前后端分离项目,由于是个前端仔,并未接触过linux,在经历诸多错误和踩坑之后,终于部署成功(大哭),在此记录一下。工具选择选择合适的工具进行远程连接,如Xshell、Xftp、putty、Terminus等Xshell:连接远程服务器的命令终端Xftp:连接远......
  • Spring启动流程XML版(源码)
    1.XML方式配置bean启动Spring的核心类: ClassPathXmlApplicationContext-> AbstractXmlApplicationContext 类结构如下: 核心方法: refresh()方法, 刷新容器, 包含12个核心子方法, 如下: 方法1:prepareRefresh();作用:容器刷新前的准备工作方法2: ConfigurableL......
  • JWT(Token令牌)整合 SpringBoot
     前言:JWT(JSONWebToken)可以被称为令牌(token)。JWT是一种在网络应用中广泛使用的令牌格式,用于在用户和服务器之间传递安全可靠的信息。JWT通常包含了用户的身份信息和一些其他的元数据,被用作身份验证和授权。因此,人们经常将JWT简称为令牌(token)。 代码整合:1.导......
  • Spring bean life cycle
      一、概要org.springframework.beans.factory.BeanFactoryBeanfactoryimplementationsshouldsupportthestandardbeanlifecycleinterfacesasfaraspossible.Thefullsetofinitializationmethodsandtheirstandardorderis:1.BeanNameAware'ssetBea......