Mybatis sql与xml文件读取方法详细分析
作者:xl649138628
在执行一个自定义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文件读取内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!