mybatis源码-映射文件解析过程

本文github地址: 点击

本文gitee 地址: 点击

1. 前言

本章主要分析一下映射文件的解析过程,映射文件指的是mappper.xml 文件

一般我们会在映射文件中配置SQL语句、字段映射关系等等。映射文件中包含<cache><resultMap><sql> 等等二级节点。

下面开始分析吧!

2. 找准目标(映射文件解析的入口在哪?)

映射文件的解析过程是配置文件的解析过程的一部分,MyBatis会在解析配置文件的过程中对映射文件进行解析,具体解析逻辑在mapperElement方法内。

public class XMLConfigBuilder extends BaseBuilder {
  private void parseConfiguration(XNode root) {
    .....
    // 解析mappers 配置
    mapperElement(root.evalNode("mappers"));  
  }
   /**
   * 解析mapper映射文件
   * @param parent
   * @throws Exception
   */
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 获取 <package> 节点的name属性
        if ("package".equals(child.getName())) {
          /*
          <mappers>
            <package name="xxx.xxx.xxx"/>
          </mapper>
           */
          // 包名配置
          String mapperPackage = child.getStringAttribute("name");
          // 从指定包中查找 mapper接口,并根据mapper接口解析映射配置
          configuration.addMappers(mapperPackage);
        } else {
          // 获取 resource/url/class 等属性

          /*
          <mappers>
            <mapper resource="org/mybatis/mappers/UserMapper.xml"/>
          </mappers>
           */
          // 相对路径
          String resource = child.getStringAttribute("resource");
          /*
          <mappers>
            <mapper url="file:///var/mappers/UserMapper.xml"/>
          </mappers>
           */
          // 绝对路径
          String url = child.getStringAttribute("url");
          /*
          <mappers>
            <mapper class="org.mybatis.mappers.UserMapper"/>
          </mappers>
           */
          //接口信息配置
          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();
          } 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();
          } else if (resource == null && url == null && mapperClass != null) {
            // class配置
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 添加mapper
            configuration.addMapper(mapperInterface);
          } else {
            // 异常
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
}

mapperElement 的主要逻辑就是遍历mappers节点中的mapper节点,根据节点值判断该如何加载映射文件信息。

  • resource 相对路径进行加载
  • url 绝对路径 加载问价
  • mapper接口class 加载
  • package 扫描包下的所有类

接下来我们看一下 映射文件解析入口parse方法

public class XMLMapperBuilder extends BaseBuilder {
  。。。。。
    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. 处理未完成解析的节点
2.1. 解析映射文件

映射文件包含很多种节点,这里就不举例了,上个简单mapper示例:

<mapper namespace="xxxx.xxx">
  <cache/>
  <resultMap id="BaseResultMap" type="xxx.xxx">
   <id column="id" jdbcType="BIGINT" property="id"/>
   <result column="name" jdbcType="VARCHAR" property="name"/>
   <result column="age" jdbcType="INT" property="age"/>
  </resultMap>
  <sql id="table"> user </sql>
  <select id="selectById" resultMap="BaseResultMap">
  	SELECT id, name, age FROM <includ refid="table"/>
    WHERE id = #{id}
  </select>
  
  ......
</mapper>

mapper文件的节点解析逻辑都有封装响应方法,然后由XMLMapperBuilder的configurationElement方法统一调用

public class XMLMapperBuilder extends BaseBuilder {
  /**
   * 统一调用节点解析配置
   */
  private void configurationElement(XNode context) {
    try {
      // 获取mapper namespace 命名空间
      // <mapper namespace="xxx.xxxx.xxxxMapper">
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 设置namespace到builderAssistant 中
      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"));
      // 解析 <select> <insert> <update> <delete>  等节点
      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);
    }
  }
}
2.1.1. 解析 <cache> 节点

Mybatis 提供一二级缓存,一级缓存为SqlSession级别,默认开启,二级缓存配置在映射文件中,必须要显示配置才可以启用。如果没有特殊配置可以直接

<cache/>

比较复杂的:

<cache eviction="FIFO" flushInterval="60000" size="1024" readOnly="true"/>
  • eviction: 缓存策略选择先进先出
  • size:缓存容量为1024个对象引用
  • flushInterval:缓存每隔60秒刷新一次
  • readOnly 写安全,外部修改对象不会影响到缓存内部存储对象

当然还有其他缓存配置了,比如和Ehcache缓存整合,或者自己实现缓存等,这里不细究了。

缓存解析

public class XMLMapperBuilder extends BaseBuilder {
  /**
   * 缓存解析逻辑
   * @param context
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      // 获取<cache>节点的各种属性
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 子节点配置
      Properties props = context.getChildrenAsProperties();
      // 构建缓存对象
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
}

大致逻辑就是解析属性,然后构建缓存对象,构建方法为 MapperBuilderAssistant.userNewCache

我们来看看 MapperBuilderAssistant.userNewCache 方法吧

public class MapperBuilderAssistant extends BaseBuilder {
  /**
   * 构造缓存对象
   */
	public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    // 添加缓存到 Configuration 对象中
    configuration.addCache(cache);
    // 设置 currentCache 遍历,即当前使用的缓存
    currentCache = cache;
    return cache;
  }
}

CacheBuilder.build 负责构建缓存

public class CacheBuilder {
  public Cache build() {
    // 设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
    setDefaultImplementations();
    // 通过反射创建缓存
    Cache cache = newBaseCacheInstance(implementation, id);
    // 仅对内置缓存 PerpetualCache 应用装饰器
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      // 遍历装饰器集合,应用装饰器
      for (Class<? extends Cache> decorator : decorators) {
        // 通过反射创建装饰器实例
        cache = newCacheDecoratorInstance(decorator, cache);
        // 设置属性值到缓存实例中
        setCacheProperties(cache);
      }
      // 应用标准的装饰器,比如 LoggingCache、SynchronizedCache
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      // 应用具有日志功能的缓存装饰器
      cache = new LoggingCache(cache);
    }
    return cache;
  }

  private void setDefaultImplementations() {
    if (implementation == null) {
      // 设置默认的缓存实现类
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        // 添加LrcCache装饰器
        decorators.add(LruCache.class);
      }
    }
  }
  private void setCacheProperties(Cache cache) {
    if (properties != null) {
      // 为缓存实例生成一个“元信息”实例,forObject 方法调用层次比较深,
      // 但最终调用了 MetaClass 的 forClass 方法。
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      for (Map.Entry<Object, Object> entry : properties.entrySet()) {
        String name = (String) entry.getKey();
        String value = (String) entry.getValue();
        if (metaCache.hasSetter(name)) {
          // 获取 setter 方法的参数类型
          Class<?> type = metaCache.getSetterType(name);

          // 根据参数类型对属性值进行转换,并将转换后的值
          // 通过 setter 方法设置到 Cache 实例
          if (String.class == type) {
            metaCache.setValue(name, value);
          } else if (int.class == type
              || Integer.class == type) {
            metaCache.setValue(name, Integer.valueOf(value));
          } else if (long.class == type
              || Long.class == type) {
            metaCache.setValue(name, Long.valueOf(value));
          } else if (short.class == type
              || Short.class == type) {
            metaCache.setValue(name, Short.valueOf(value));
          } else if (byte.class == type
              || Byte.class == type) {
            metaCache.setValue(name, Byte.valueOf(value));
          } else if (float.class == type
              || Float.class == type) {
            metaCache.setValue(name, Float.valueOf(value));
          } else if (boolean.class == type
              || Boolean.class == type) {
            metaCache.setValue(name, Boolean.valueOf(value));
          } else if (double.class == type
              || Double.class == type) {
            metaCache.setValue(name, Double.valueOf(value));
          } else {
            throw new CacheException("Unsupported property type for cache: '" + name + "' of type " + type);
          }
        }
      }
    }
    // 如果缓存类实现了 InitializingObject 接口,
    // 则调用 initialize 方法执行初始化逻辑
    if (InitializingObject.class.isAssignableFrom(cache.getClass())) {
      try {
        ((InitializingObject) cache).initialize();
      } catch (Exception e) {
        throw new CacheException("Failed cache initialization for '"
          + cache.getId() + "' on '" + cache.getClass().getName() + "'", e);
      }
    }
  }
    private Cache setStandardDecorators(Cache cache) {
    try {
      // 创建“元信息”对象
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        // 设置 size 属性,
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        // clearInterval 不为空,应用 ScheduledCache 装饰器
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        // readWrite 为 true,应用 SerializedCache 装饰器
        cache = new SerializedCache(cache);
      }
      /*
       * 应用 LoggingCache,SynchronizedCache 装饰器, * 使原缓存具备打印日志和线程同步的能力
       */
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {

        // blocking 为 true,应用 BlockingCache 装饰器
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }
}
  1. 设置默认的缓存类型及装饰器
  2. 应用装饰器到 PerpetualCache 对象上
  3. 应用标准装饰器
  4. 对非 LoggingCache 类型的缓存应用 LoggingCache 装饰器
2.1.2. 解析<cache-ref> 节点

Mybatis 二级缓存可以共用,通过 <cache-ref> 来配置参照缓存

大致逻辑已备注在代码中了 不做详细介绍了

public class XMLMapperBuilder extends BaseBuilder {
  private void cacheRefElement(XNode context) {
    if (context != null) {
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      // cacheRefResolver示例
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        // 解析参照缓存
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        // 捕捉IncompleteElementException 异常,并将 cacheRefResolver // 存入到 Configuration 的 incompleteCacheRefs 集合中
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }
}
public class CacheRefResolver {
  private final MapperBuilderAssistant assistant;
  private final String cacheRefNamespace;

  public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
    this.assistant = assistant;
    this.cacheRefNamespace = cacheRefNamespace;
  }

  public Cache resolveCacheRef() {
    // 调用 builderAssistant 的 useNewCache(namespace) 方法
    return assistant.useCacheRef(cacheRefNamespace);
  }
}

public class MapperBuilderAssistant extends BaseBuilder {
    public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      // 根据命名空间从全局配置对象(Configuration)中查找相应的缓存实例
      Cache cache = configuration.getCache(namespace);
      /*
        若未查找到缓存实例,此处抛出异常。这里存在两种情况导致未查找到 cache实例,分别如下:
        1.使用者在 <cache-ref> 中配置了一个不存在的命名空间, 导致无法找到 cache 实例
        2.使用者所引用的缓存实例还未创建
       */
      if (cache == null) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      }
      // 设置 cache 为当前使用缓存
      currentCache = cache;
      unresolvedCacheRef = false;
      return cache;
    } catch (IllegalArgumentException e) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
  }
}
2.1.3. <resultMap>节点
 public class XMLMapperBuilder extends BaseBuilder {
   private void resultMapElements(List<XNode> list) {
    // 遍历 <resultMap>节点列表
    for (XNode resultMapNode : list) {
      try {
        // 解析resultMap节点
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }
  private ResultMap resultMapElement(XNode resultMapNode) {
    return resultMapElement(resultMapNode, Collections.emptyList(), null);
  }

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());

    // 获取type属性
    String type = resultMapNode.getStringAttribute("type",
      resultMapNode.getStringAttribute("ofType",
        resultMapNode.getStringAttribute("resultType",
          resultMapNode.getStringAttribute("javaType"))));
    // 解析type属性对应的class类型
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    // 获取并遍历 <resultMap> 的子节点列表
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        // 解析 constructor 节点,并生成相应的 ResultMapping
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        // 解析 discriminator 节点
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          // 添加 ID 到 flags 集合中
          flags.add(ResultFlag.ID);
        }
        // 解析 id 和 property 节点,并生成相应的 ResultMapping
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 获取 id 属性
    String id = resultMapNode.getStringAttribute("id",
      resultMapNode.getValueBasedIdentifier());
    // 获取extends属性
    String extend = resultMapNode.getStringAttribute("extends");
    // 获取autoMapping属性
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 根据前面获取到的信息构建 ResultMap 对象
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      /*
       * 如果发生 IncompleteElementException 异常,
       * 这里将 resultMapResolver 添加到 incompleteResultMaps 集合中 
       * */
      
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
 }
  1. 获取<resultMap>节点的各种属性
  2. 遍历<resultMap>的子节点,并根据子节点名称执行相应的解析逻辑
  3. 构建 ResultMap 对象
  4. 若构建过程中发生异常,则将 resultMapResolver 添加到incompleteResultMaps 集合中
2.1.4. 解析<sql> 节点

入口:

public class XMLMapperBuilder extends BaseBuilder {

    /**
   * 统一调用节点解析配置
   */
  private void configurationElement(XNode context) {
    try {
      // 解析 <sql> 节点
      sqlElement(context.evalNodes("/mapper/sql"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

有兴趣的可以自己跟踪源码看一下

2.1.5. 解析SQL语句节点

<select> <insert> <update> <delete> 节点的解析

具体跟踪源码吧 内容较多以后可能会完善

2.2. Mapper接口绑定
  1. 获取命名空间 根据命名空间解析mapper类型
  2. 将type和mapperProxyFactory实例存入konwnMappers中
  3. 解析注解中得信息
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值