文档章节

Mybatis源码分析之Mapper文件解析

Lucare
 Lucare
发布于 2018/07/23 20:23
字数 999
阅读 34
收藏 0

xxMapper.xml的解析主要由XMLMapperBuilder类完成,parse方法来完成解析:

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
}

configurationElement(parser.evalNode("/mapper"));

上面的这行代码是提取<html><code><mapper></mapper></code></html>部分来解析:

private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      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"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
    }
}

将各个元素细分,逐一解析:

  • parameterMapElement方法处理parameterMap节点部分
  • resultMapElements方法处理resultMap节点部分
  • sqlElement处理sql节点部分
  • buildStatementFromContext方法处理select|insert|update|delete部分

重点看看buildStatementFromContext:

private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
}

List<XNode>类型的list包含了单个mapper.xml文件的所有sql动作部分:

<select></select>
<insert></insert>
<update></update>
<delete></delete>

单一节点使用XMLStatementBuilder的parseStatementNode来解析,取其中重要的三行代码:

List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  • List<SqlNode> contents = parseDynamicTags(context);
private List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      String nodeName = child.getNode().getNodeName();
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE
          || child.getNode().getNodeType() == Node.TEXT_NODE) {
         String data = child.getStringBody("");
         contents.add(new TextSqlNode(data));
      } else {
         NodeHandler handler = nodeHandlers.get(nodeName);
         if (handler == null) {
            throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
         }
         handler.handleNode(child, contents);

      }
    }
    return contents;
}

if块是处理text部分,else块处理其他内嵌node部分:

<if></if>
<choose></choose>
....

最终的结果都会添加到List<SqlNode>类型的contexts中。XNode形如父子关系,类似链表存储。

例如:

<select id="getCurrSpaceNums" resultType="com.fcs.model.CarParkingSpaceNum">
	select pp.permit_cards maxLeng,pp.permit_cards currLeng 
	from TB_UHOME_PARKING_PLACE pp
	where pp.COMMUNITY_ID=#{orgId} and pp.STATUS='1'
	and pp.PLACE_CODE = #{parkingCode}
	<if test="parkingArea != null and parkingArea !=''">
		and pp.PLACE_AREA= #{parkingArea}
	</if>
</select>

body部分:

select pp.permit_cards maxLeng,pp.permit_cards currLeng 
from TB_UHOME_PARKING_PLACE pp
where pp.COMMUNITY_ID=#{orgId} and pp.STATUS='1'
and pp.PLACE_CODE = #{parkingCode}

取上面的text构成TextSqlNode

第二个childNode是if标签包裹部分,取出来的body为:

and pp.PLACE_AREA= #{parkingArea}

NodeHandler handler = nodeHandlers.get(nodeName);

上面的代码获取IfHandler(对应的还有ChooseHandler,ForEachHandler等)。

handler.handleNode(child, contents);

看看内部类IfHandler会如何处理:

private class IfHandler implements NodeHandler {
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> contents = parseDynamicTags(nodeToHandle);
      MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
}

继续调用parseDynamicTags,然后构造IfSqlNode,添加到总的contents中。

这时候name为“select”的XNode下解析出的contents包含了三个SqlNode:

image

  • MixedSqlNode rootSqlNode = new MixedSqlNode(contents);

利用contents构造MixedSqlNode类型的rootSqlNode。

  • SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode);

利用rootSqlNode构造DynamicSqlSource。DynamicSqlSource的getBoundSql方法是非常重要的,用来获取BoundSql。此时仅仅是构造sqlSource放到MappedStatement中,以sqlSource形式入,以BoundSql形式出。

  • builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, keyGenerator, keyProperty, keyColumn, databaseId);

解析完一个statement节点,就会将其包装成MappedStatement,基本上就是你在Mapper.xml文件中写的每个sql语句对应一个MappedStatement。最终都添加到Configuration的MappedStatement集合中。

补充

在DynamicSqlSource的getBoundSql方法中有下面一行代码:

rootSqlNode.apply(context);

我们之前存的rootSqlNode是一个MixedSqlNode,代表混合型SqlNode,看其apply方法:

public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
}

就是将之前的SqlNode集合contents遍历处理。这个contents包含两种类型的SqlNode:TextSqlNode和IfSqlNode。

TextSqlNode的apply方法:

public boolean apply(DynamicContext context) {
    GenericTokenParser parser = new GenericTokenParser("${", "}", new BindingTokenParser(context));
    context.appendSql(parser.parse(text));
    return true;
}

这里就涉及到参数绑定了,将${param}替换为实际参数值。

IfSqlNode的apply方法:

public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
}

通常IfSqlNode也是包含一个TextSqlNode,表达式满足要求就继续调用TextSqlNode的apply方法,append满足条件的sql语句。

这样一个动态sql就构造出来了个大概。后面还有进一步的处理:

SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType);

主要是针对#{param}部分的处理,后面在”参数绑定“分析时会详细解读。

© 著作权归作者所有

Lucare

Lucare

粉丝 6
博文 11
码字总数 11404
作品 0
深圳
程序员
私信 提问
源码分析Mybatis MappedStatement的创建流程

上文源码分析Mybatis MapperProxy创建流程重点阐述MapperProxy的创建流程,但并没有介绍.Mapper.java(UserMapper.java)是如何与Mapper.xml文件中的SQL语句是如何建立关联的。本文将重点接开这...

丁威
09/17
0
0
通过源码分析MyBatis的缓存

前方高能! 本文内容有点多,通过实际测试例子+源码分析的方式解剖MyBatis缓存的概念,对这方面有兴趣的小伙伴请继续看下去~ MyBatis缓存介绍 首先看一段wiki上关于MyBatis缓存的介绍: MyBa...

whthomas
2014/12/11
20
0
MyBatis 源码分析 - 映射文件解析过程

1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程。由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因。所以我将映射文件解析过程的分析内容从上一篇文章中...

田小波⊰
2018/07/30
0
0
深入浅出MyBatis_Index

深入浅出MyBatis系列 【深入浅出MyBatis系列一】MyBatis入门 【深入浅出MyBatis系列二】配置简介(MyBatis源码篇) 【深入浅出MyBatis系列三】Mapper映射文件配置 【深入浅出MyBatis系列四】...

陶邦仁
2015/12/22
1K
0
Mybatis3.3.x技术内幕(九):Mybatis初始化流程(中)

Mybatis初始化流程,其实就是组装重量级All-In-One对象Configuration的过程,主要分为系统环境参数初始化和Mapper映射初始化。 上一节中,粗略讲述了Mybatis初始化的基本步骤,本节,将详细分...

祖大俊
2016/05/02
1K
1

没有更多内容

加载失败,请刷新页面

加载更多

采坑指南——k8s域名解析coredns问题排查过程

正文 前几天,在ucloud上搭建的k8s集群(搭建教程后续会发出)。今天发现域名解析不了。 组件版本:k8s 1.15.0,coredns:1.3.1 过程是这样的: 首先用以下yaml文件创建了一个nginx服务 apiV...

码农实战
18分钟前
2
0
【2019年8月版本】OCP 071认证考试最新版本的考试原题-第6题

choose three Which three statements are true about indexes and their administration in an Orade database? A) An INVISIBLE index is not maintained when Data Manipulation Language......

oschina_5359
20分钟前
2
0
阿里巴巴开源 Dragonwell JDK 最新版本 8.1.1-GA 发布

导读:新版本主要有三大变化:同步了 OpenJDK 上游社区 jdk8u222-ga 的最新更新;带来了正式的 feature:G1ElasticHeap;发布了用户期待的 Windows 实验版本 Experimental Windows version。...

阿里巴巴云原生
25分钟前
1
0
教你玩转Linux—磁盘管理

Linux磁盘管理好坏直接关系到整个系统的性能问题,Linux磁盘管理常用三个命令为df、du和fdisk。 df df命令参数功能:检查文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少...

xiangyunyan
28分钟前
3
0
js 让textarea的高度自适应父元素的高度

textarea按照普通元素设置height是没有作用的,可以这么来设置, 下面给上一段项目代码 JS代码: $.fn.extend({ txtaAutoHeight: function () { return this.each(function () {...

文文1
29分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部