Mapper文件解析源码导读

mapper文件解析入口

mybatis配置文件解析核心方法最后一项

private void parseConfiguration(XNode root) {
    try {
    
      ...
      //解析mapper文件
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

查看mapperElement(XNode parent)源码

 private void mapperElement(XNode parent) throws Exception {
   if (parent != null) {
     for (XNode child : parent.getChildren()) {
       //节点是package
       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) {
           //从resource加载
           ErrorContext.instance().resource(resource);
           InputStream inputStream = Resources.getResourceAsStream(resource);
           XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
           mapperParser.parse();
         } else if (resource == null && url != null && mapperClass == null) {
           //从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) {
           //从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.");
         }
       }
     }
   }
 }

mapper节点分两种方式解析(mapper、package)

   <!--mapper分三种情况:-->
   <mapper class="com.mybatis.builder.UserMapper"/> 
   <mapper resource="mapper/UserMapper.xml"/>
   <mapper url="file:///var/mappers/UserMapper.xml"/> 
   <package name="com.mybatis.source.mapper" />

以上不同分支逻辑最后都会进入MapperRegistry的addMapper(...)方法

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

最初设计时,Mybatis是一个XML驱动的框架。配置信息基于xml,映射语句也定义在xml。Mybaits3开发了基于注解的配置方式,注解方式提供一种简单方式来实现简单映射语句,不引入大量开销,但最强大的mybatis不能用注解来构建

重点分析xml解析方式mapperParser.parse()

 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. 处理未完成解析的节点

在mapper映射文件中可以配置多种节点。eg:,,以及<select |insert|update|delete>等。配置示例

<mapper namespace="cn.yto.mat.interfacex.dao.IDictDao">

    <cache/>

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="cn.yto.mat.interfacex.entity.DictEntity">
        <id column="ID" property="id" />
        <result column="PID" property="pid" />
        <result column="TYPE" property="type" />
        <result column="CODE" property="code" />
        <result column="NAME" property="name" />
        <result column="VALUE" property="value" />
        <result column="SORTNO" property="sortno" />
        <result column="STATUS" property="status" />
        <result column="IS_FIXED" property="isFixed" />
        <result column="DATA_DESC" property="dataDesc" />
        <result column="UPDATE_TIME" property="updateTime" />
        <result column="UPDATE_USER" property="updateUser" />
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        ID, PID, TYPE, CODE, NAME, VALUE, SORTNO, STATUS, IS_FIXED, DATA_DESC, UPDATE_TIME, UPDATE_USER
    </sql>


    <select id="getDict" resultType="cn.yto.mat.interfacex.entity.DictEntity">
        select d.type, d.code, d.name, d.value
          from t_mat_dict d
         where d.type = #{type}
           and d.code = #{code}
    </select>

  </mapper>

解析mapper节点核心代码


private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      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"));
      //SQL语句解析
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}

ResultMap的设计思想是,简单的语句不需要明确的结果映射,而复杂一点的语句只需要描述他们的关系就行了。简述解析过程

  1. 获取节点的各种属性
  2. 遍历的子节点,并根据子节点名称执行相应的解析逻辑
  3. 构建resultMap对象
  4. 若构建的过程中发生异常,则将resultMapResolver添加到incompleteResultMaps集合中
posted @ 2021-01-13 14:59  刘66  阅读(43)  评论(0编辑  收藏  举报