解析Mybatis的Mapper配置,初始化SQL环境的过程
Mybatis主配置文件中定义的mappers元素,可以指定Mybatis加载Mapper对象的行为和方式。
mappers元素的DTD定义如下:
<!ELEMENT mappers (mapper*,package*)>
mappers元素中允许定义零个或多个mapper和package子元素来进行Mapper对象的查找配置。
其中package子元素有一个必填的name属性,他指向一个基础包名,该包内所有符合条件的Mapper接口定义都会被注册到mybatis中。
<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
mapper子元素有三个具有互斥关系的属性定义,这三个属性必须有一个有值且不能同时存在。
mapper子元素的DTD定义如下:
<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>
他们的含义分别是:
resource表示使用相对类路径的资源(XML资源)引用url表示使用完全限定资源定位符的资源(XML资源)引用class表示使用映射器接口实现类的完全限定类名
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
解析mappers元素的入口在XmlConfigBuilder#parseConfiguration(XNode root)方法中:
// !!注册解析Dao对应的MapperXml文件
mapperElement(root.evalNode("mappers"));
mapperElement方法定义如下:
/**
* 解析配置文件中的mappers节点
*
* @param parent mappers节点内容
*/
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 解析package元素,将包内的映射器接口实现全部注册为映射器
// 比如:<package name="org.mybatis.builder"/>
// 判断 configuration>mappers>package 属性
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 根据包名加载所有的DAO操作类并注册
configuration.addMappers(mapperPackage);
} else {
// 解析mapper元素
// 判断 configuration>mappers>mapper 属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// 解析使用相对类路径的资源引用
// 比如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
// 只有resource
ErrorContext.instance().resource(resource);
// 读取指定的XML资源文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 继续解析Mapper Xml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(
inputStream, /*XML文件输入流*/
configuration, /*用户全局配置*/
resource, /*资源配置文件的地址*/
configuration.getSqlFragments() /*已有的XML代码块*/
);
/*解析该XML文件*/
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// 解析使用完全限定资源定位符的资源引用
// 比如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
// 只有url
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 解析使用映射器接口实现类的完全限定类名
// 比如:<mapper class="org.mybatis.builder.AuthorMapper"/>
// 只有mapperClass 直接添加对象
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元素的所有子节点之后,Mybatis会根据mappers子元素的类型和具体的属性配置来执行不同的逻辑。
其中package元素会交给Configuration#addMappers(String)来完成映射器的批量注册。
// 解析package元素,将包内的映射器接口实现全部注册为映射器
// 比如:<package name="org.mybatis.builder"/>
// 判断 configuration>mappers>package 属性
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 根据包名加载所有的DAO操作类并注册
configuration.addMappers(mapperPackage);
}
mapper元素则会根据具体配置的属性来执行不同的注册方式,配置class属性的mapper元素,会调用Configuration#addMapper(Class<T> type)方法来完成映射器的注册操作。
if (resource == null && url == null && mapperClass != null) {
// 解析使用映射器接口实现类的完全限定类名
// 比如:<mapper class="org.mybatis.builder.AuthorMapper"/>
// 只有mapperClass 直接添加对象
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
}
这种注册操作和package类似,都是通过解析Mapper接口作为依据来完成映射器的注册。
resource和url属性的处理略有不同,他们的实现是基于Mapper接口对应的XML文件来完成的。
在具体的解析处理操作上,resource和url的实现也比较类似,区别只在于加载XML文件的寻址方式不同而已。
resouce:
if (resource != null && url == null && mapperClass == null) {
// 解析使用相对类路径的资源引用
// 比如:<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
// 只有resource
ErrorContext.instance().resource(resource);
// 读取指定的XML资源文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 继续解析Mapper Xml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(
inputStream, /*XML文件输入流*/
configuration, /*用户全局配置*/
resource, /*资源配置文件的地址*/
configuration.getSqlFragments() /*已有的XML代码块*/
);
/*解析该XML文件*/
mapperParser.parse();
}
url
if (resource == null && url != null && mapperClass == null) {
// 解析使用完全限定资源定位符的资源引用
// 比如:<mapper url="file:///var/mappers/AuthorMapper.xml"/>
// 只有url
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(
inputStream/*XML文件输入流*/
, configuration/*用户全局配置*/
, url/*资源配置文件的地址*/
, configuration.getSqlFragments()/*已有的XML代码块*/
);
/*解析该XML文件*/
mapperParser.parse();
}
仔细看上述resource和url的处理代码,两者几乎完全一致,唯一的区别只在于加载文件的方式:
resource加载XML文件使用的是基于类加载路径的:InputStream inputStream = Resources.getResourceAsStream(resource);
而url加载XML文件使用的是基于URL的:InputStream inputStream = Resources.getUrlAsStream(url);.
两者对于
XML资源文件的加载都是委托给了Resources来实现,Resources是Mybatis封装的一个简化资源访问的工具类.
大致归类,我们可以将上诉四种解析方式分为两类: 一类是以Mapper接口作为依据,一类是以Mapper接口对应的XML文件作为依据。
package元素和mapper元素的class属性以Mapper接口作为依据。
他们的解析处理工作在Configuration中被委托给了名为MapperRegistry的对象来完成。
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
mapper元素的resource和url属性以Mapper接口对应的XML文件作为依据。
他们的解析处理工作被委托给了名为XMLMapperBuilder的对象来完成:
// 继续解析Mapper Xml文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(
inputStream, /*XML文件输入流*/
configuration, /*用户全局配置*/
resource, /*资源配置文件的地址*/
configuration.getSqlFragments() /*已有的XML代码块*/
);
/*解析该XML文件*/
mapperParser.parse();
MapperRegistry是Mybatis中的映射器注册表,肩负着注册映射器的光荣任务。
XmlMapperBuilder是BaseBuilder的一个实现,他的作用主要是用来解析Mybatis的mapper文件。
MapperRegistry和XmlMapperBuilder的具体实现,我们后面会慢慢探索学习。