Mybatis 解析mapper过程

news2024/11/19 20:44:32

Mapper配置的四种方式

配置方法一共有四种, 分别对应四种解析方式,从解析源码可以看出来

private void mapperElement(XNode parent) throws Exception { //添加接口映射器
    if (parent != null) {
      for (XNode child : parent.getChildren()) { //获取所有的子级元素
        if ("package".equals(child.getName())) { //如果使用包的方式进行配置
          String mapperPackage = child.getStringAttribute("name"); //获取包名
          configuration.addMappers(mapperPackage); //添加Mapper
        } else { //使用mapper 三种定位方式 resource url class
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource); //注入日志信息
            try(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);
            try(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<?> 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.");
          }
        }
      }
    }
  }

所以我们可以这样配置
在这里插入图片描述

我们找一种方式进行解析分析, Mybatis原版本最先是没有接口的,都是xml配置文件, 调用sql语句的时候直接使用namespace加执行的脚本语句id, 如:
在这里插入图片描述
这种api 方法就是原始版本的, 后面普及了面向接口编程,所以才引入了接口调用表达式。 我所以在解析方式中,使用包路径和class类都是按照接口进行解析找到对应的xml的, 另外两种直接获取xml的输入流则是原先版本的,我们先看原先版本。

基础知识点:resource和url如何获取输入流对象,
resource可以通过类加载器进行加载获取资源, 而url 作为http网络协议地址路径, java的解析类URL则可以获取输入流,

 public static InputStream getUrlAsStream(String urlString) throws IOException {
    URL url = new URL(urlString);
    URLConnection conn = url.openConnection();
    return conn.getInputStream();
  }

通过xml文件的输入流进行mapper解析

XMLMapperBuilder 类的parse作为入口:

public void parse() {
    if (!configuration.isResourceLoaded(resource)) { //是否被解析过了
      configurationElement(parser.evalNode("/mapper"));//解析命名空间的xml文件
      configuration.addLoadedResource(resource); //该资源路径已经解析了
      bindMapperForNamespace();/*为命名空间构建mapper接口代理*/
    }
    /*继续完成之前没有完成解析*/
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

所以configurationElement方法就是解析mapper.xml文件的核心方法:

configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace"); //获取命名空间
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace); //设置命名空间
      cacheRefElement(context.evalNode("cache-ref")); //缓存引用, 二级缓存
      cacheElement(context.evalNode("cache"));//构建自己的二级缓存 如果存在将会替换缓存引用
      parameterMapElement(context.evalNodes("/mapper/parameterMap")); //解析参数映射
      resultMapElements(context.evalNodes("/mapper/resultMap")); //解析结果集映射
      sqlElement(context.evalNodes("/mapper/sql")); //添加sqlElement片段
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//sql脚本
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

从图中可见,我们在mapper.xml文件中配置的各种xml元素标签,在这里都要被一一解析成java各种配置对象。

cache-ref 标签元素解析

这个标签元素的作用就当前命名空间的缓存可以引用的别命名空间的缓存。
使用方法:

 <cache-ref namespace="xxx"/>

指定namespace为引用缓存的命名空间

引用关系是存在Configuration配置类的:

/*
   * A map holds cache-ref relationship. The key is the namespace that
   * references a cache bound to another namespace and the value is the
   * namespace which the actual cache is bound to.
   * map持有cache-ref关系。键是引用绑定到另一个名称空间的缓存的名称空间,值是实际缓存绑定到的名称空间。
   */
  protected final Map<String, String> cacheRefMap = new HashMap<>();

解析过程:

首先要知道所有的命名空间的缓存也存在与配置类中

protected final Map<String, Cache> caches = new StrictMap<>("Caches collection"); /*二级缓存 和xml命名空间绑定的缓存*/

既然有了引用缓存的命名空间我们就可以去获取引用缓存,

private void cacheRefElement(XNode context) {
    if (context != null) {
      //缓存映射关系添加  key为当前命名空间, value为被引用缓存的命名空间
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        //从configuration的Cache缓存map中获取缓存, 当前命名空间对象的缓存执行 引用缓存。
        cacheRefResolver.resolveCacheRef();//使用缓存
      } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(cacheRefResolver); //添加到没有完成的缓存解析器
      }
    }
  }

需要思考的是, 如果这个时候引用缓存的命名空间的xml还没有解析的话,那么是不会获取到引用缓存的,所以要标记当前命名空间对象的缓存引用没有解决, 所以会有catch 步骤没有完成缓存引用解析的解析器。

继续找出抛出异常的代码:

public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      Cache cache = configuration.getCache(namespace); //根据命名空间获取缓存集合
      if (cache == null) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
      }
      currentCache = cache;
      unresolvedCacheRef = false;/*缓存引用标记解决*/
      return cache;
    } catch (IllegalArgumentException e) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
    }
  }

嗯嗯, 整体流程 缓存引用标签的解析原理还是比较简单的。

cache 标签 构建自己的缓存

如果对Mybatis的缓存接口不熟悉, 请移步到这个篇博客Mybatis缓存接口, 整个缓存使用的装饰器设计模式

xml标签配置内容:

<cache type="" blocking="" eviction="" flushInterval="" readOnly="" size="">
    <property name="" value=""/>
    <property name="" value=""/>
  </cache>

如果不记得了直接看代码怎么解析的。。。。。。。。。。。

private void cacheElement(XNode context) {
    if (context != null) {
      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);//使用新的缓存
    }
  }

从上面代码也可以看出,如果我们只配置一个标签, 没有各种熟悉配置, 也能开启二级缓存, 代码中默认给了一个PERPETUAL别名的缓存类, 所以在之前一定会有改别名注入到别名工厂中。

typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);

PerpetualCache 就是一个基本的缓存类并没有多少额外功能, 本质就是一个HashMap

别的属性就是装饰的配置

eviction 指定缓存置换算法的装饰器, 默认是使用LRU

typeAliasRegistry.registerAlias("LRU", LruCache.class);

另外的几个属性:flushInterval、size、readOnly、blocking 都有对应的装饰器, 在Mybatis缓存接口博客都有涉及。

接着进行构建缓存:

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.addCache(cache); //二级缓存添加到configuration
    currentCache = cache; //当前命名空间缓存进行指向创建的缓存
    return cache;
  }
public Cache build() {
    setDefaultImplementations(); //设置默认的
    Cache cache = newBaseCacheInstance(implementation, id); //创建基本的存储缓存
    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);
      }
      cache = setStandardDecorators(cache);/*设置标准的装饰器*/
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }
private void setDefaultImplementations() {
    //没有指定缓存type属性就使用默认的
    if (implementation == null) {
      implementation = PerpetualCache.class;
      //添加默认的置换算法缓存装饰器
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }

从上面代码可以看出, 如果type属性 是指定得是我们自己的缓存类, 那么那些基本的属性配置是不会生效的, 应为代码判断了cache 如果默认缓存才会去调用setStandardDecorators方法,这个方法就是对那些别的属性进行解析装饰缓存的代码:

private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {/*设置缓存大小*/
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        cache = new ScheduledCache(cache); /*定时清理缓存*/
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) { /*读写*/
        cache = new SerializedCache(cache);
      }
      cache = new LoggingCache(cache); /*设置日志缓存*/
      cache = new SynchronizedCache(cache); /*同步缓存*/
      if (blocking) {
        cache = new BlockingCache(cache); /*设置阻塞缓存*/
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

等cache创建完成之后 当前命名空间的缓存引用会指向当前构建的缓存, 所以cache-ref标签的功能将被覆盖。

parameterMap 标签

用过Mybatis的都会知道参数映射, 这个标签就是指定需要怎么进行参数映射

我们配置之后的Mybatis解析配置代码如下:

private void parameterMapElement(List<XNode> list) {
    for (XNode parameterMapNode : list) { /*遍历多个参数解析*/
      String id = parameterMapNode.getStringAttribute("id");/*获取唯一id*/
      String type = parameterMapNode.getStringAttribute("type");/*获取类名称*/
      Class<?> parameterClass = resolveClass(type);/*别名解析*/
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");/*获取所有的属性配置*/
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");/*属性*/
        String javaType = parameterNode.getStringAttribute("javaType");/*java类型*/
        String jdbcType = parameterNode.getStringAttribute("jdbcType");/*jdbc类型*/
        String resultMap = parameterNode.getStringAttribute("resultMap");/*结果映射map*/
        String mode = parameterNode.getStringAttribute("mode"); /*存储过程相关的*/
        String typeHandler = parameterNode.getStringAttribute("typeHandler");/*类型处理器*/
        Integer numericScale = parameterNode.getIntAttribute("numericScale");/*啥东西*/
        ParameterMode modeEnum = resolveParameterMode(mode);//存储过程的输入输出
        Class<?> javaTypeClass = resolveClass(javaType); //获取java类型
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); //获取Jdbc类型
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); //获取类型处理器
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }

整体来说代码都很简单,就是把id与parameterMapping的映射关系存入configuration中。

  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");

resultMap 标签

字如其名, 作为结果集映射的配置标签,我们需要指定一个反参实体类对象如果接收结果集进行赋值。

resultMap 标签里面可以配置的子标签一共有:id、association、discriminator、result、collection、constructor 几种。具体怎么使用请移步到Mybatis官网, 有很好的中文翻译版本。

sql标签

sql片段

select|insert|update|delete 脚本驱动

脚本标签

里面要进行脚本解析, sql片段替换、以及selectKey 解析 sql移除等等。 具体代码慢慢研究把,很复杂。。。。,构建出MappedStatement。

总结

xml 配置的代码, 总得需要代码去解析。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124728.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4.7W防削顶单声道D类音频功率放大器HT6872介绍

HT6872简介 HT6872是一款低EMI&#xff0c;防削顶失真&#xff0c;单声道免滤波D类音频功率放大器。在6.5V电源&#xff0c;10%THDN&#xff0c;4Ω负载条件下&#xff0c;输出4.71W功率&#xff0c;在各类音频终端应用中维持高效率并提供AB类放大器的性能。 HT6872的最大特点是…

C++:设计一个保留字(或关键字)的统计程序,从源数据文件(C或C++语言程序)中,读取字符或字符串,与保留字文件中的保留字进行匹配比较,并统计计数。

2.1题目&#xff1a; 设计一个保留字&#xff08;或关键字&#xff09;的统计程序 l建立保留字文件&#xff1b; l从源数据文件&#xff08;C或C语言程序&#xff09;中&#xff0c;读取字符或字符串&#xff0c;与保留字文件中的保留字进行匹配比较&#xff0c;并统计计数。…

python数据分析(1)numpy基础

iamseancheney/python_for_data_analysis_2nd_chinese_version: 《利用Python进行数据分析第2版》 (github.com) NumPy的ndarray&#xff1a;一种多维数组对象 1.性质 NumPy最重要的一个特点就是其N维数组对象&#xff08;即ndarray&#xff09;&#xff0c;该对象是一个快速…

73.qt quick-通用可拖拽表盘示例

截图如下所示: 效果如下所示: 源码已上传至专栏群(第一章底部获取)中,感兴趣的自行下载 demo使用介绍 控件自定义属性已经封装出来了,如下图所示: main.qml如下所示: import QtQuick 2.14 import QtQuick.Window 2.14 import QtQuick.Extras 1.4 import QtQuick.Layouts 1.1…

微信小程序-会议OA项目03

目录 1.Flex布局简介 1.1 什么是flex布局 1.2 flex属性 2.轮播图--组件的使用 3.会议OA项目-首页 1.Flex布局简介 布局的传统解决方案&#xff0c;基于盒状模型&#xff0c;依赖 display属性 position属性 float属性 1.1 什么是flex布局 1) Flex是Flexible Box的缩写&…

攻防世界-fileclude

题目 访问题目场景 阅读php代码 <?php include("flag.php"); highlight_file(__FILE__); if(isset($_GET["file1"]) && isset($_GET["file2"])) {$file1 $_GET["file1"];$file2 $_GET["file2"];if(!empty($f…

法国半导体制造企业RIBER部署MBE技术以支持量子计算

图片来源&#xff1a;网络 法国半导体制造企业RIBER在2022年进一步提升了其在量子处理器耗材市场的发展水平。 早在2021年6月&#xff0c;RIBER已开始部署系统&#xff0c;它在法国图卢兹与 法国国家科学研究中心系统分析与架构实验室&#xff08;LAAS-CNRS&#xff09;创办联合…

Google Guice 1:如何实现依赖注入?

1. 待完善的邮箱程序 1.1 手动注入依赖 前一篇博文《谈谈自己对依赖注入的理解》&#xff0c;笔者只是基于依赖注入的思想&#xff0c;为EmailClient预留了依赖注入的入口 到目前为止&#xff0c;我们只是让dependent class预留了依赖注入的入口&#xff0c;要想实现依赖的自动…

TOPLAS‘07: Effective Field-Sensitive Pointer Analysis for C 字段敏感C程序指针分析

文章目录1. 集合约束式的指针分析1.1 基本介绍1.2 求解约束1.2.1 图传播1.2.2 迭代顺序1.2.3 节点替换 (Variable Subsititution)1.2.4 传递化简 (Transitive Reduction)1.2.5 集合的表示1.2.6 差分传播1.2.7 相同解的集合2. 扩展约束模型2.1 简介2.2 处理函数指针2.3 处理字段…

NNOM第一个模型实例

目录 一、keras开发环境搭建 二、安装visual studio 2019 1. 下载安装 2. 配置使用MSVC编译器 三、编译第一个NNOM的demo 1. 下载源码 2. 安装依赖库 3. 编译auto_test 四、移植 1. 新建新的VS项目 2. 拷贝相关源码 3. 配置工程 4. 编译并运行 一、keras开发环境搭…

Java并发——线程池

线程池 一、线程池的作用 线程的创建和销毁需要占用CPU资源&#xff0c;若频繁的进行创建和销毁会产生很大的开销&#xff0c;影响性能和系统稳定性。 线程池的优点&#xff1a; 线程池可以保存创建好的线程随用随取&#xff0c;降低资源消耗&#xff08;重复利用线程池中的…

一文读懂堡垒机对企业信息安全起到的重要作用

堡垒机的发展历程大致可分为以下三个阶段&#xff1a;      第一代堡垒机&#xff1a;堡垒机最初的理念起源于跳板机&#xff0c;但跳板机无法实现对运维人员操作行为进行控制和审计&#xff0c;一旦出现违规操作导致操作事故&#xff0c;很难快速定位原因和责任人。    …

Java分析-对象头

前言 HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding) HotSpot虚拟机的对象头(Object Header)包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁…

移动是否是商业bi的前景?

一 前言 五年前就有人预言商业智能BI移动化会成为必然趋势&#xff0c;如今5年时间已过&#xff0c;移动BI的普及程度并不如预期。原因主要是数据分析的交互性一直是很高的要求&#xff0c;手机屏幕的限制一直抑制了商业智能BI在移动端得到更好发挥的障碍。 比如数据看板或可…

[笔记] - springboot-jpa 使用sqlite 踩坑

前言&#xff08;可略过&#xff09; 最近准备写一些小项目来验证一下脑袋中的项目 因为是“小项目”&#xff0c;所以对于数据持久化的实现&#xff0c;就不想用mysql等很重的db了 而且不用考虑安全性&#xff0c;故首先想到的就是使用 sqlite 本地数据库即可 然后因为是使用…

Android进阶 之 SPI机制及实现原理

什么是SPI SPI &#xff0c;全称为 Service Provider Interface&#xff0c;是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件&#xff0c;自动加载文件里所定义的类。是Java提供的一套用来被第三方实现或者扩展的API&#xff0c;它可以用来启用…

BCryptPasswordEncoder加密与MD5加密的区别

MD5 加密说明 MD5&#xff08;Message Digest Algorithm 5&#xff09;中文名为消息摘要算法第五版&#xff0c;是计算机安全领域广泛使用的一种散列函数&#xff0c;用以提供消息的完整性保护。 MD5作为一种常用的摘要算法&#xff08;或指纹算法&#xff09;&#xff0c;其…

一文教会你如何利用领英多账号高效开发客户资源

作为全球最大的职业社交平台&#xff0c;领英&#xff08;linkedin&#xff09;的属性关键词包括“商业、互动和机会”。这些属性覆盖了领英全球超过6亿的用户&#xff0c;这决定了领英是一个拥有无限商业交易机会的社交平台。因此&#xff0c;越来越多的国内外企业不断在领英上…

如何在右键菜单添加将文档“转换为PDF”选项

本文介绍一种方法&#xff0c;可以实现右键快速将docx、doc、txt、ppt等文档转换为PDF文档 文章目录1. Acrobat DC 软件安装2.添加右键“转PDF”功能选项3.功能效果1. Acrobat DC 软件安装 下载链接&#xff1a; 1.百度网盘:链接 提取码: vumk 2.阿里云盘&#xff1a;链接…

虹科分享|关于SANS报告的顶级勒索软件洞察

近年来&#xff0c;勒索软件攻击经历了大流行加速的演变&#xff0c;而防御系统则难以跟上。勒索软件的第一阶段已经让位于新的、不同的、更好的和更坏的东西。为了帮助理解这一演变&#xff0c;Morphisec赞助了一份来自SANS的报告&#xff0c;探索勒索软件防御的现状。它研究了…