Mybatis源码分析第三章-映射⽂件解析过程

本文详细解析MyBatis映射文件的解析过程,包括从文件系统、URL、mapper接口和包扫描四种加载方式。重点分析XML映射文件的解析,包括mapper节点、命名空间绑定、未完成节点的处理,以及<select|insert|update|delete>等二级节点的解析逻辑。内容涵盖映射文件配置示例和解析方法的调用流程。
摘要由CSDN通过智能技术生成

本章,我们来分析一下映射文件解析的过程。与配置文件不同,映射文件用于配置 SQL
语句,字段映射关系等。映射文件中包含、、、、
<select|insert|update|delete>等二级节点,这些节点将在接下来内容中进行分析。本章除了分析常规的 XML 解析过程外,还会向大家介绍 Mapper 接口的绑定过程,以及其他一些知识。内容较多,需要有一定的耐心阅读。

3、映射⽂件解析

映射文件的解析过程是配置文件解析过程的一部分,MyBatis 会在解析配置文件的过程中对映射文件进行解析。解析逻辑封装在mapperElement 方法中,下面来看一下。

// -☆- XMLConfigBuilder
private void mapperElement(XNode parent) throws Exception { 
if (parent != null) {
  for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) {
	// 获取 <package> 节点中的 name 属性
	String mapperPackage = child.getStringAttribute("name");
	// 从指定包中查找 mapper 接口,并根据 mapper 接口解析映射配置
	configuration.addMappers(mapperPackage);
} 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);
	InputStream inputStream = Resources.getResourceAsStream(resource);
	XMLMapperBuilder mapperParser = new XMLMapperBuilder( inputStream, configuration, resource, configuration.getSqlFragments());
	// 解析映射文件
	mapperParser.parse();

// url 不为空,且其他两者为空,则通过 url 加载配置
} else if (resource == null &&
	url != null && mapperClass == null) { ErrorContext.instance().resource(url);
	InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser =         new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
	// 解析映射文件
	mapperParser.parse();
	// mapperClass 不为空,且其他两者为空,
	// 则通过mapperClass 解析映射配置
} else if (resource == null &&
	url == null && mapperClass != null) { Class<?> mapperInterface =
	Resources.classForName(mapperClass); configuration.addMapper(mapperInterface);
// 以上条件不满足,则抛出异常
} else {
throw new BuilderException("……");
}
}
}
}
}

上面代码的主要逻辑是遍历mappers 的子节点,并根据节点属性值判断通过何种方式加载映射文件或映射信息。这里把配置在注解中的内容称为映射信息,以 XML 为载体的配置称为映射文件。在MyBatis 中,共有四种加载映射文件或映射信息的方式。

第一种是从文件系统中加载映射文件;

第二种是通过 URL 的方式加载映射文件;第三种是通过 mapper 接口加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中。最后一种是通过包扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息。

以上简单介绍了 MyBatis 加载映射文件或信息的几种方式。需要注意的是,在 MyBatis 中,通过注解配置映射信息的方式是有一定局限性的,这一点 MyBatis 官方文档中描述的比较清楚。这里引用一下:

因为最初设计时,MyBatis 是一个XML 驱动的框架。配置信息是基于XML 的,而且映射语句也是定义在XML 中的。而到了MyBatis3,就有新选择了。MyBatis3 构建在全面且强大的基于 Java 语言的配置 API 之上。这个配置 API 是基于 XML 的MyBatis 配置的基础, 也是新的基于注解配置的基础。注解提供了一种简单的方式来实现简单映射语句,而不会引入大量的开销。
注意:不幸的是,Java 注解的表达力和灵活性十分有限。尽管很多时间都花在调查、设计和试验上,最强大的MyBatis 映射并不能用注解来构建——并不是在开玩笑,的确是这样。

如上,请注意用黑体标注了内容。限于 Java 注解的表达力和灵活性,通过注解的方式并不能完全发挥 MyBatis 的能力。因此,对于一些较为复杂的配置信息,我们还是应该通过XML 的方式进行配置。在接下的章节中,我会重点分析以 XML 为载体的映射文件的解析过程。如果能弄懂此种配置方式的解析过程,那么基于注解的解析过程也不在话下。
下面开始分析映射文件的解析过程,在分析之前,先来看一下映射文件解析入口。

// -☆- XMLMapperBuilder public void parse() {
// 检测映射文件是否已经被解析过
if (!configuration.isResourceLoaded(resource)) {
// 解析 mapper 节点
configurationElement(parser.evalNode("/mapper"));
// 添加资源路径到“已解析资源集合”中
configuration.addLoadedResource(resource);
// 通过命名空间绑定 Mapper 接口
bindMapperForNamespace();
}

// 处理未完成解析的节点parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements();

映射文件解析入口逻辑包含三个核心操作,如下:
1.解析mapper 节点
2.通过命名空间绑定Mapper 接口
3.处理未完成解析的节点
这三个操作对应的逻辑将会在随后的章节中依次进行分析,下面先来分析第一个操作对应的逻辑。

3.2解析映射⽂件

映 射 文 件 包 含 多 种 二 级 节 点 , 比 如 , , 以 及
<select|insert|update|delete> 等。除此之外,还包含了一些三级节点,比如 ,,
等。这些节点的解析过程将会在接下来的内容中陆续进行分析。在分析之前,我们先来看一个映射文件配置示例。

<mapper namespace="xyz.coolblog.dao.AuthorDao">

<cache/>

<resultMap id="authorResult" type="Author">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- ... -->
</resultMap>

<sql id="table"> author
</sql>

<select id="findOne" resultMap="authorResult"> SELECT
id, name, age, sex, email FROM
<include refid="table"/> WHERE
id = #{id}
</select>

<!-- <insert|update|delete/> -->
</mapper>

上面是一个比较简单的映射文件,还有一些的节点未出现在上面。以上配置中每种节点的 解 析 逻 辑 都封 装 在 了相 应 的 方法 中 , 这些方 法 由 XMLMapperBuilder 类 的
configurationElement 方法统一调用。该方法的逻辑如下:

private void configurationElement(XNode context) { try {
// 获取 mapper 命名空间
String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) {
throw new BuilderException("……");
}

// 设置命名空间到 builderAssistant 中
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>、...、<delete> 等节点
buildStatementFromContext( context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("……");
}
}

上面代码的执行流程清晰明了。在阅读源码时,我们按部就班的分析每个方法调用即可。不过本章在叙述的过程中会对分析顺序进行一些调整,本章将会先分析节点的解析过程,然后再分析节点,之后会按照顺序分析其他节点的解析过程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值