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

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  小结

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

posted @ 2024-02-26 08:34  酷酷-  阅读(29)  评论(0编辑  收藏  举报