首頁 > 軟體

Mybatis sql與xml檔案讀取方法詳細分析

2023-01-28 18:03:09

在執行一個自定義sql語句時,dao對應的代理物件時如何找到sql,也就是dao的代理物件和sql之間的關聯關係是如何建立的。

        在mybatis中的MybatisPlusAutoConfiguration類被@Configuration註解,在該類中通過被@Bean註解的sqlSessionFactory方法向spring上下文注入bean並生成SqlSessionFactory型別的bean範例。關注該方法的最後一行程式碼。

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // 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()));
        }
        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);
        }
        Resource[] mapperLocations = this.properties.resolveMapperLocations();
        if (!ObjectUtils.isEmpty(mapperLocations)) {
            factory.setMapperLocations(mapperLocations);
        }
        // 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);
        // TODO 自定義列舉包
        if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
            factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
        }
        // TODO 此處必為非 NULL
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        // TODO 注入填充器
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        // TODO 注入主鍵生成器
        this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(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();
    }

        進入最後一行程式碼找到MybatisSqlSessionFactoryBean類裡的getObject方法,然後進入到該類的afterPropertiesSet方法,找到了buildSqlSessionFactory方法。

public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            //關注該行方法
            afterPropertiesSet();
        }
        return this.sqlSessionFactory;
}
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");
        //關注該行方法
        this.sqlSessionFactory = buildSqlSessionFactory();
 }

 在buildSqlSessionFactory中有兩處程式碼比較關鍵,第一處如下圖hasLength(this.typeAliasesPackage)判斷了在yml中設定的的mybatis-plus.typeAliasesPackage,並通過buildSqlSessionFactory方法裡的scanClasses方法將讀取到的類路徑全部小寫後存放到TypeAliasRegistry類的typeAliases屬性的hashMap快取中。

mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  typeAliasesPackage: >
    com.changshin.entity.po
  global-config:
    id-type: 0  # 0:資料庫ID自增   1:使用者輸入id  2:全域性唯一id(IdWorker)  3:全域性唯一ID(uuid)
    db-column-underline: false
    refresh-mapper: true
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true #設定的快取的全域性開關
    lazyLoadingEnabled: true #延時載入的開關
    multipleResultSetsEnabled: true #開啟的話,延時載入一個屬性時會載入該物件全部屬性,否則按需載入屬性
 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);
        }
        //省略。。。。。。。
 
        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 (this.mapperLocations != null) {
            if (this.mapperLocations.length == 0) {
                LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
            } else {
                // 關注for迴圈結構體裡的程式碼
                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.");
        }
        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;
    }

第二處xmlMapperBuilder.parse()方法解析了xml檔案,mapperLocations屬性是一個陣列型別的屬性,陣列裡儲存了xml檔案的全路徑。通

過for迴圈對每一個xml進行解析。進入parse方法。在進入parse方法前初始化了XMLMapperBuilder並將其configuration屬性設定為MybatisSqlSessionFactoryBean的configuration屬性屬性。

XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());

對parse方法進行解析 

public void parse() {
    //判斷是否載入過組態檔
    if (!configuration.isResourceLoaded(resource)) {
      //解析mapper標籤
      configurationElement(parser.evalNode("/mapper"));
      //標註該設定已經被載入過了
      configuration.addLoadedResource(resource);
      //將dao對應的類與xml mapper標籤的namespaces屬性做繫結
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

先分析 parse方法裡的configurationElement方法。該方法逐步解析mapper標籤下的子標籤,具體解析過程比較複雜在此就不分析了。

 private void configurationElement(XNode context) {
    try {
      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"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

bindMapperForNamespace方法將namespace將生成的的類型別通過

configuration.addMapper(boundType)方法放到Configuration類的MapperRegistry型別屬性mapperRegistry的knownMappers屬性的快取中(該屬性是一個以類型別key,MapperProxyFactory為value的HashMap)。

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }

到此這篇關於Mybatis sql與xml檔案讀取方法詳細分析的文章就介紹到這了,更多相關Mybatis sql與xml檔案讀取內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com