在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程。由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因。所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来,独立成文,于是就有了本篇文章。在本篇文章中,我将分析映射文件中出现的一些及节点,比如 <cache>,<cache-ref>,<resultMap>, <select | insert | update | delete> 等。除了分析常规的 XML 解析过程外,我还会向大家介绍 Mapper 接口的绑定过程等。综上所述,本篇文章内容会比较丰富,如果大家对此感兴趣,不妨花点时间读一读,会有新的收获。当然,本篇文章通篇是关于源码分析的,所以阅读本文需要大家对 MyBatis 有一定的了解。如果大家对 MyBatis 还不是很了解,建议阅读一下 MyBatis 的官方文档。
其他的就不多说了,下面开始我们的 MyBatis 源码之旅。
2.映射文件解析过程分析我在前面说过,映射文件的解析过程是 MyBatis 配置文件解析过程的一部分。MyBatis 的配置文件由 XMLConfigBuilder 的 parseConfiguration 进行解析,该方法依次解析了 <properties>、<settings>、<typeAliases> 等节点。至于 <mappers> 节点,parseConfiguration 则是在方法的结尾对其进行了解析。该部分的解析逻辑封装在 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("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }上面的代码比较简单,主要逻辑是遍历 mappers 的子节点,并根据节点属性值判断通过什么方式加载映射文件或映射信息。这里,我把配置在注解中的内容称为映射信息,以 XML 为载体的配置称为映射文件。在 MyBatis 中,共有四种加载映射文件或信息的方式。第一种是从文件系统中加载映射文件;第二种是通过 URL 的方式加载和解析映射文件;第三种是通过 mapper 接口加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中。最后一种是通过包扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息。
以上简单介绍了 MyBatis 加载映射文件或信息的几种方式。需要注意的是,在 MyBatis 中,通过注解配置映射信息的方式是有一定局限性的,这一点 MyBatis 官方文档中描述的比较清楚。这里引用一下:
因为最初设计时,MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且映射语句也是定义在 XML 中的。而到了 MyBatis 3,就有新选择了。MyBatis 3 构建在全面且强大的基于 Java 语言的配置 API 之上。这个配置 API 是基于 XML 的 MyBatis 配置的基础,也是新的基于注解配置的基础。注解提供了一种简单的方式来实现简单映射语句,而不会引入大量的开销。
注意: 不幸的是,Java 注解的的表达力和灵活性十分有限。尽管很多时间都花在调查、设计和试验上,最强大的 MyBatis 映射并不能用注解来构建——并不是在开玩笑,的确是这样。