LOADING

加载过慢请开启缓存 浏览器默认开启

MyBatis的Mapper与Class映射源码阅读

2022/11/4 xieajiu MyBatis

MyBatis的配置读取的脑图

点击下载思维导图

MyBatis的版本

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

MyBatis的Mapper配置

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

开始源码调试

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

进行单步调试可以看到:

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

这个时候创建了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;
}

代码总是那么清晰明了

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);
  }
}

方法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");
        }
      }
    }
  }
}

这里的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);
  }
}
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);
      }
    }
  }
}
7、MapperAnnotationBuilder的parse方法
public void parse() {
  String resource = type.toString();
  // 防止重复加载 XML
  if (!configuration.isResourceLoaded(resource)) {
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // ... 
  }
}

然后重点看一下loadXmlResource方法。

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

这段代码就是最开始处理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是分开的