讲解前,我们先思考几个问题:
1、谁来解析?
2、用什么解析?
3、解析成什么?
4、解析结果如何存放?
5、最终用途?
那么,我们顺着上面几个问题来解读源码。
3.1 XMLMapperBuilder对象创建
从第二章
了解,解析前要先创建 XMLMapperBuilder 对象,代码如下:
XMLConfigBuilder.mapperElement方法
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
XMLMapperBuilder对象创建
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {// 进入XPathParser类,可了解到,最终是利用 XPath 来解析xml,XPath是Java自带的一个接口,在javax.xml.xpath包中this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),configuration, resource, sqlFragments);}private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {super(configuration); // 父类构造函数调用,看BaseBuilder类// 创建 MapperBuilderAssistant 构建mapper的辅助类,先不管细节,后面用到时再讲this.builderAssistant = new MapperBuilderAssistant(configuration, resource);// 下面都是赋值this.parser = parser;this.sqlFragments = sqlFragments;this.resource = resource;}public abstract class BaseBuilder {protected final Configuration configuration;protected final TypeAliasRegistry typeAliasRegistry;protected final TypeHandlerRegistry typeHandlerRegistry;public BaseBuilder(Configuration configuration) {this.configuration = configuration; // 赋值// 设置类型别名注册器,先不管细节,后面用到时再讲this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();// 设置类型处理器注册器,先不管细节,后面用到时再讲this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();}
}
以上代码就可以解答问题1和问题2:由XMLMapperBuilder来解析,内部通过XPath来解析xml,XPath怎么解析xml,这里不会细讲,有兴趣的读者可以自行查阅资料学习。
3.2 XML配置文件解析
3.2.1 解析过程
进入 XMLMapperBuilder.parse() 方法
public void parse() {// 判断是不是已经加载过资源,避免重复加载if (!configuration.isResourceLoaded(resource)) {// 从根节点mapper开始解析configurationElement(parser.evalNode("/mapper"));// 解析完了,添加到已加载资源中,里面用的是一个HashSet集合来存放,主要是为了防止重复加载configuration.addLoadedResource(resource);// 给命名空间构建mapper(前提是命名空间必须是可加载的类,这个主要用在注解方式中)bindMapperForNamespace();}// 清空未处理完的result mapparsePendingResultMaps();// 清空未处理完的Cache refparsePendingCacheRefs();// 清空未处理完的statementparsePendingStatements();}
3.2.2 mapper解析细节
入口还是在 XMLMapperBuilder 类中
private void configurationElement(XNode context) {try {// 拿到命名空间String namespace = context.getStringAttribute("namespace");// 命名空间不能为空if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}// 给 MapperBuilderAssistant 辅助对象,设置命名空间,后面会用到builderAssistant.setCurrentNamespace(namespace);// cache-ref节点解析,看章节`3.2.2.1`cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));// 用于引用外部 parameterMap 的属性,目前已被废弃,既然废弃了,就不讲了parameterMapElement(context.evalNodes("/mapper/parameterMap"));// resultMap 节点解析,看章节`3.2.2.3`resultMapElements(context.evalNodes("/mapper/resultMap"));// sql 节点解析,看章节`3.2.2.4`sqlElement(context.evalNodes("/mapper/sql"));// 重点来了,真正的sql语句 节点解析,看章节`3.2.2.5`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);}}
在讲解mapper下各子节点的解析前,先了解下它有哪些子节点,以及各自的用途,SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
cache
– 该命名空间的缓存配置。cache-ref
– 引用其它命名空间的缓存配置。resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。parameterMap
– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。sql
– 可被其它语句引用的可重用语句块。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。
后续将通过抖音视频/直播的形式分享技术,由于前期要做一些准备和规划,预计2024年6月开始,欢迎关注,如有需要或问题咨询,也可直接抖音沟通交流。