MyBatis的Mapper与Class映射源码阅读

MyBatis的配置读取的脑图

点击下载思维导图

MyBatis的版本

                    
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
xml

MyBatis的Mapper配置

                    
<mappers>
<package name="com.xie.mapper"/>
</mappers>
xml

开始源码调试

1、读取MyBatis配置
                    
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
java
2、创建SqlSessionFactory

进行单步调试可以看到:

                    
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
java

这个时候创建了XMLConfigBuilder,执行parser.parse() 创建MyBatis的Configuration对象。

3、创建MyBatis的Configuration对象
                    
public Configuration parse() {
// 判断是否已经解析过
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 读取MyBatis的配置文件,<configuration> MyBatis配置文件的跟标签
// 解析配置文件中的配置,包括数据源信息、事务、Mapper等
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
java

代码总是那么清晰明了

4、解析MapperClass
                    
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
// ...
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
java

方法mapperElement用来处理mappers标签下的字标签mapper/package以及属性(resource,class,url),其中可以分为两类,一类是通过Class去匹配对应的XML,一类是通过指定XML读取namespace属性找到对应的Class。

通过Class去寻找对应的XML,需要XML和Class在同一资源目录下。

且看mapperElement方法:

                    
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历 mappers 下的字标签
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(xxxx);
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
// 同 resource
} else if (resource == null && url == null && mapperClass != null) {
// 同 package,后续会说明的
} else {
throw new BuilderException("xxxx");
}
}
}
}
}
java

这里的package处理和resource殊途同归

5、configuration的addMappers方法
                    
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 遍历包下的 Class 找到符合要求的MapperClass
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 这里的 addMapper 和 resource 的 class 调用的同一个方法
addMapper(mapperClass);
}
}
java
6、addMapper方法
                    
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// 重复的 Class
throw new BindingException("");
}
boolean loadCompleted = false;
try {
// 标记已经解析过的 Class
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
java
7、MapperAnnotationBuilder的parse方法
                    
public void parse() {
String resource = type.toString();
// 防止重复加载 XML
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
// ...
}
}
java

然后重点看一下loadXmlResource方法。

                    
private void loadXmlResource() {
// 一系列判断
// 获取 inputStream
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(xxx);
xmlParser.parse();
}
}
java

这段代码就是最开始处理mappers的字标签mapperresource属性的代码

剩下的就是通过XMLMapperBuilder解析Mapper中的SQL、各种Handle等等

总结

MyBatis使用package配置标签,会先获取到包路径下符合要求的Class,准确的说应该是接口、Mapper接口,通过接口的全路径去寻找MapperClass与之对应的MapperXML,所以在使用Mybatis的时候,有些同学会有MapperClass找不到对应的MapperXML的经历。

当使用<mapper resource="com/BaseMapper.xml" />去配置Mapper时,就不需要关注MapperClass和MapperXML是否在同一个文件下。MyBatis会读取Mapper XML的namespace属性去找到对应的MapperClass。

这里的同一文件夹下,指的是打包之后的Class文件与MapperXml文件在同一文件夹下。所有在开发的时候Class与Mapper是分开的