一、我们点进代码片段1中SqlSessionFactoryBuilder().build()里面,经过重重重载方法的调用,最终到了代码片段2这个方法。
这里可以看到做了两件事,
1、创建了一个XMLConfigBuilder来对配置文件进行解析。
2、根据解析返回的全局配置对象Configuration创建DefaultSqlSessionFactory。
//代码片段1
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//代码片段2
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 执行 XML 解析
// 创建 DefaultSqlSessionFactory 对象
return build(parser.parse());
}
二、点进去parser.parse()方法,里面调用parseConfiguration(XNode)方法对配置文件中configuration节点进行解析。
代码片段3
public Configuration parse() {
// 若已解析,抛出 BuilderException 异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标记已解析
parsed = true;
///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
// 解析 XML configuration 节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
三、点进去parseConfiguration(XNode)方法,这里可以看到对诸多子标签进行了解析。
// 代码片段4
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析 <properties /> 标签
propertiesElement(root.evalNode("properties"));
// 解析 <settings /> 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载自定义的 VFS 实现类
loadCustomVfs(settings);
// 解析 <typeAliases /> 标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 <plugins /> 标签
pluginElement(root.evalNode("plugins"));
// 解析 <objectFactory /> 标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析 <objectWrapperFactory /> 标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 <reflectorFactory /> 标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 赋值 <settings /> 到 Configuration 属性
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析 <environments /> 标签
environmentsElement(root.evalNode("environments"));
// 解析 <databaseIdProvider /> 标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 <typeHandlers /> 标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 <mappers /> 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
四、解析到mapperElement(XNode)之前,就算把mybatis的全局配置文件解析完成,并且保存在Configuration对象中。
最后mapperElement(XNode),解析mapper标签,并对引入的XXMapper.xml资源文件进行解析。
在对每个XXMapper.xml解析时,都会创建一个XMLMapperBuilder对象来专门对XXMapper.xml解析。
点进去,如下
代码片段5
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历子节点
for (XNode child : parent.getChildren()) {
// 如果是 package 标签,则扫描该包
if ("package".equals(child.getName())) {
// 获取 <package> 节点中的 name 属性
String mapperPackage = child.getStringAttribute("name");
// 从指定包中查找 mapper 接口,并根据 mapper 接口解析映射配置
configuration.addMappers(mapperPackage);
// 如果是 mapper 标签,
} else {
// 获得 resource、url、class 属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// resource 不为空,且其他两者为空,则从指定路径中加载配置
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 获得 resource 的 InputStream 对象
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 执行解析
mapperParser.parse();
// url 不为空,且其他两者为空,则通过 url 加载配置
} else if (resource == null && url != null && mapperClass == null) {
// mapperClass 不为空,且其他两者为空,则通过 mapperClass 解析映射配置
} else if (resource == null && url == null && mapperClass != null) {
//省略
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
五、XMLMapperBuilder. Parse(),其中调用的configurationElement(parser.evalNode("/mapper"));就是对XXMapper.xml中如cache,resultMap,select各个标签进行解析。
代码片段6
private void configurationElement(XNode context) {
try {
// 获得 namespace 属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置 namespace 属性
builderAssistant.setCurrentNamespace(namespace);
// 解析 <cache-ref /> 节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析 <cache /> 节点
cacheElement(context.evalNode("cache"));
// 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 <resultMap /> 节点们
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析 <sql /> 节点们
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 <select /> <insert /> <update /> <delete /> 节点们
// 这里会将生成的Cache包装到对应的MappedStatement
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);
}
}
六、buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 这里对编写的sql进行了解析。
以下是buildStatementFromContext(XNode parent)解析sql的调用过程
--1、buildStatementFromContext(XNode parent)
---- 2、buildStatementFromContext(List<XNode> list, String requiredDatabaseId) 调用重载方法
------3、创建XMLStatementBuilder,并调用XMLStatementBuilder.parseStatementNode() 对每个sql增删查标签创建专门的解析对象来解析
-------- 4、builderAssistant.addMappedStatement() 根据解析的出的值,创建MappedStatement并将MappedStatement添加到configuration中
可按照以上调用过程,翻看代码,直到看到第4步中方法的最后两行代码,你会恍然大悟,原来sql都平衡车解析成MappedStatement放到Configuration中了
代码片段7
// 创建 MappedStatement 对象
MappedStatement statement = statementBuilder.build();
// 添加到 configuration 中
configuration.addMappedStatement(statement);
至此解析流程走完。