Mybatis-Plus源码解析之MybatisPlusAutoConfiguration(二)

group : com.baomidou

version:3.5.2.2-SNAPSHOT

SpringBoot是自动装配。Spring则可以在配置类上@Import(MybatisPlusAutoConfiguration.class)

properties 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

类的头信息

首先我们查看MybatisPlusAutoConfiguration类上的注解。

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisPlusProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {

@Configuration(proxyBeanMethods = false)中的proxyBeanMethods属性的作用是控制是否对配置类中的方法进行代理。

@ConditionalOnClass是Spring中的一个条件注解,用于基于类路径上是否存在某些类的情况来决定是否在加载配置。具体来说,当类路径上存在指定的类时,@ConditionalOnClass所注解的配置类或bean才会被注册到容器中。

@ConditionalOnSingleCandidate 注解会检查容器中是否有且仅有一个符合条件的 bean,如果是,则相应的配置类或 bean 才会被注册到 Spring 容器中。如果存在多个符合条件的 bean 或者一个都没有,那么相应的配置将被忽略。

@EnableConfigurationProperties 是 Spring Boot 中的注解,用于启用对配置属性类的支持。当你在应用中使用 @ConfigurationProperties 注解定义了一个用于绑定配置属性的类时,通过使用 @EnableConfigurationProperties 注解,你可以告诉 Spring Boot 启用这些配置属性类的支持。

@AutoConfigureAfter 是 Spring Boot 中的一个注解,用于指定自动配置类的加载顺序。当一个配置类需要在另一个配置类之后被加载时,可以使用 @AutoConfigureAfter 注解来明确指定顺序。

Bean的加载

SqlSessionTemplate和SqlSessionFactory将会在这个类被加载的时候也加载到容器中。

java 复制代码
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
        return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    // mybatisPlus自动配置类配置sql会话工厂,将sql注入器等组件存入GlobalConfig全局配置钟
    // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
    MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
        factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    //设置了configuration
    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);
    }
    // 这个地方会将xml查询出来,放入到MapperLocations里面,后续循环进行解析,放入configuration
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
        factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    // TODO 修改源码支持定义 TransactionFactory
    this.getBeanThen(TransactionFactory.class, factory::setTransactionFactory);

    // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配)
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (!ObjectUtils.isEmpty(this.languageDrivers)) {
        factory.setScriptingLanguageDrivers(this.languageDrivers);
    }
    Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);

    applySqlSessionFactoryBeanCustomizers(factory);

    // TODO 此处必为非 NULL
    GlobalConfig globalConfig = this.properties.getGlobalConfig();
    // TODO 注入填充器
    this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
    // TODO 注入主键生成器
    this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));
    // TODO 注入sql注入器
    this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
    // TODO 注入ID生成器
    this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
    // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
    factory.setGlobalConfig(globalConfig);
    return factory.getObject();
}

SqlSessionFactory主要的作用就是openSession(),其实就是从连接或者数据源创建一个sqlSession。具体的实现就是MybatisSqlSessionFactoryBean。

SqlSessionTemplate的注入依赖SqlSessionFactory。

java 复制代码
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }

SqlSessionTemplate又实现了org.apache.ibatis.session.SqlSeesion,它是一个接口,有一个默认的实现DefaultSqlSession。既然已经有了一个DefaultSqlSession为什么还要有一个SqlSessionTemplate,而且SqlSessionTemplate中sqlSessionProxy就是使用代理去执行所有的SqlSession的方法。这么做的好处是什么?

SqlSessionFactorySqlSessionTemplate 是与 MyBatis 持久层框架密切相关的两个类,它们在整个数据访问的过程中扮演不同的角色。

  1. SqlSessionFactory:

    • SqlSessionFactory 是 MyBatis 的核心接口之一,负责创建 SqlSession 对象。
    • 通常,SqlSessionFactory 的实现类是 SqlSessionFactoryBean,它会被Spring容器管理。plus的实现类是MybatisSqlSessionFactoryBean
    • SqlSessionFactory 的主要作用是配置并创建 SqlSession 对象,SqlSession 用于执行SQL语句。
  2. SqlSessionTemplate:

    • SqlSessionTemplate 是 MyBatis-Spring 模块提供的一个实现了 SqlSession 接口的类。
    • 它包装了一个由 Spring 管理的 SqlSessionFactory,提供了一些便捷的方法来执行数据库操作。
    • SqlSessionTemplate 作为 Spring 提供的一个 MyBatis 的整合模板,简化了 MyBatis 的使用,无需手动处理 SqlSession 的开启、提交、回滚、关闭等操作。

为什么需要 SqlSessionTemplate

  1. 事务管理: SqlSessionTemplate 简化了事务管理。它会自动参与到 Spring 管理的事务中,无需手动调用 commitrollback 方法。

  2. 线程安全: SqlSession 不是线程安全的,而 SqlSessionTemplate 是线程安全的。这使得你可以在 Spring 容器中注入 SqlSessionTemplate,而不必担心线程安全性问题。

  3. 便捷性: SqlSessionTemplate 提供了一系列便捷的方法,例如 selectOneselectListupdateinsertdelete 等,减少了手动编写繁琐的 MyBatis 代码。

MybatisSqlSessionFactoryBean

SqlSessionFactory注入的最后一步就是执行getObject方法。

java 复制代码
@Override
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            afterPropertiesSet();
        }

        return this.sqlSessionFactory;
    }

项目启动的时候,当前sqlSessionFactory一定是为空,所以会进入到afterPropertiesSet方法。

java 复制代码
@Override
public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");
    //TODO 清理掉资源  建议不要保留这个玩意了
    SqlRunner.DEFAULT.close();
    // 创建sql会话工厂的过程就伴随着配置的解析,也会解析XML文件和Mapper的类
    // 简单的说就是这里创建了sqlSessionfactory实例
    this.sqlSessionFactory = buildSqlSessionFactory();
}

所以核心就是buildSqlSessionFactory。

java 复制代码
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;

    // TODO 使用 MybatisXmlConfigBuilder 而不是 XMLConfigBuilder
    MybatisXMLConfigBuilder xmlConfigBuilder = null;
    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) {
        // TODO 使用 MybatisXMLConfigBuilder
        xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
        targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
        LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
        // TODO 使用 MybatisConfiguration
        targetConfiguration = new MybatisConfiguration();
        Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    // TODO 无配置启动所必须的
    this.globalConfig = Optional.ofNullable(this.globalConfig).orElseGet(GlobalConfigUtils::defaults);
    this.globalConfig.setDbConfig(Optional.ofNullable(this.globalConfig.getDbConfig()).orElseGet(GlobalConfig.DbConfig::new));

    // TODO 初始化 id-work 以及 打印骚东西
    GlobalConfigUtils.setGlobalConfig(targetConfiguration, this.globalConfig);

    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 + "'");
        });
    }

    targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

    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);

    if (xmlConfigBuilder != null) {
        try {
            // 猜测这里就是xml钟sql的解析,其实不是
            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();
        }
    }

    targetConfiguration.setEnvironment(new Environment(MybatisSqlSessionFactoryBean.class.getSimpleName(),
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));

    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());
                    // 真正的解析在这里,mapperxml文件
                    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.");
    }

    final SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration);

    // TODO SqlRunner
    SqlHelper.FACTORY = sqlSessionFactory;

    // TODO 打印 Banner
    if (globalConfig.isBanner()) {
        System.out.println(" _ _   |_  _ _|_. ___ _ |    _ ");
        System.out.println("| | |\\/|_)(_| | |_\\  |_)||_|_\\ ");
        System.out.println("     /               |         ");
        System.out.println("                        " + MybatisPlusVersion.getVersion() + " ");
    }

    return sqlSessionFactory;
}

需要注意返回的SqlSessionFactory返回的是DefaultSqlSessionFactory。这个对我们后面很重要,因为后面使用的DefaultSqlSession就是他里面创建的。

并且这个方法内部xmlMapperBuilder.parse()对我们的xml进行解析,存放在sqlSessionFactory中的configuration属性中。但是如果我们的mapper interface没有xml,那他是怎么知道执行什么sql?

相关推荐
程序员鱼皮1 小时前
我代表编程导航,向大家道歉!
前端·后端·程序员
间彧1 小时前
Spring Boot项目中如何实现Redis分布式锁
java
zjjuejin1 小时前
Maven 生命周期与插件机制
后端·maven
掘金安东尼1 小时前
AI 应用落地谈起 ,免费试用 Amazon Bedrock 的最佳时机
java·架构
阿杆2 小时前
为什么我建议你把自建 Redis 迁移到云上进行托管
redis·后端
杨杨杨大侠2 小时前
案例03-附件E-部署运维
java·docker·github
Java水解2 小时前
go语言教程(全网最全,持续更新补全)
后端·go
杨杨杨大侠2 小时前
案例03-附件B-映射器实现
java·开源·github
杨杨杨大侠2 小时前
案例03-附件A-订单实体设计
java·开源·github
杨杨杨大侠2 小时前
案例03-附件C-性能优化
java·开源·github