不管是有无主配置文件,均会生成Configuration对象来保存Mybatis的MappedStatement信息。笔者此处分析无主文件的情况,则会调用Configuration的无参数构造函数
public Configuration() {
//预存常用的别名
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
Spring加载mapper配置文件入口
前提是sqlSessionFactoryBean配置了mapperLocations属性。类似
<property name="mapperLocations" value="classpath:com/du/wxServer/mapper/*.xml" />
SqlSessionFactoryBean读取mapper配置文件
代码片段如下
if (!isEmpty(this.mapperLocations)) {
//具体的如何从string转为Resource[],暂且不知何处加载获得,有兴趣的读者可补充
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//对扫描包及其子包下的每个mapper配置文件进行解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
XMLMapperBuilder-Mapper配置文件解析类
解析对应的XML配置文件,代码如下
public void parse() {
//对每个xml资源只加载一次
if (!configuration.isResourceLoaded(resource)) {
//解析xml配置,其中配置的根节点必须为mapper
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//绑定mapper的工作区间
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}
紧接着观察XMLMapperBuilder#configurationElement()方法
private void configurationElement(XNode context) {
try {
//表明mapper根节点的namespace属性是必须的,且不为空
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//设置工作区间
builderAssistant.setCurrentNamespace(namespace);
//解析相应的属性
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//解析<parameterMap>节点集合
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析<resultMap>节点集合
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析<sql>节点集合
sqlElement(context.evalNodes("/mapper/sql"));
//创建MappedStatement,这里与注解方式的加载方式还是类似的
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
笔者对重要的节点解析作下分析,详见如下
resultMap节点解析
解析mapper节点下的resultMap节点属性如下
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
//读取id属性,最好配置以免不必要的错误
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
//优先级为type>ofType>resultType>javaType
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
//是否开启自动映射,默认值为unset
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
//<discriminator><case /><case/></discriminator>根据结果值进行结果类型的映射,类似java的switch-case语法
Discriminator discriminator = null;
//ResultMap节点信息转化为ResultMapping集合
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
//<resultMap>节点下<constructor>节点处理
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
//<resultMap>节点下<discriminator>节点处理
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
//<id>/<result>/<collection>/<association>节点的解析
ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
//组装成ResultMap对象保存到Configuration对象的私有集合变量resultMaps
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
sql节点解析
解析mapper节点下的sql节点属性如下。主要作用是将每个sql节点对象都保存到Configuration对象中的sqlFragmentsHashMap属性中。
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
//sql节点的databaseId属性
String databaseId = context.getStringAttribute("databaseId");
//sql节点的id属性,id=${namespace}+"."+id
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
//true的前提是主配置文件指定了databaseId属性或者主配置和sql节点的databaseId属性均不存在,但sql节点的id属性存在
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);
}
}
CRUD节点解析
即解析select/update/delete/insert节点对应的信息,笔者此处关注XMLStatementBuilder#parseStatementNode()方法的片段代码
//节点上支持的常见属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
...
String resultSetType = context.getStringAttribute("resultSetType");
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
...
// Include Fragments before parsing 导入<include>标签内容,其内部可以含有<if>/<where>/<set>等标签
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.导入<selectKey>标签内容
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 如何解析sql语句?放置下一章节讲解
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
...
//创建MappedStatement对象,保存在Configuration的mappedStatement集合属性中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
总结
不管是通过注解模式还是配置文件模式,都会生成MappedStatement对象保存到Configuration对象中
注解模式可以很好的达到sql语句与类直接绑定;
但本文的mapper配置文件并无法绑定对应namespace指向的class对象,其一般搭配MapperScannerConfigurer使用
每个select|update|insert|delete标签均会被解析为单个MappedStatement对象,其中的id以${namespace}_id作为唯一标志
网友评论