Mybatis sql与xml文件读取方法详细分析
xl649138628 人气:0在执行一个自定义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); } } }
加载全部内容