感觉CSDN对markdown的支持不够友好,总是伴随各种问题,很恼火!

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

上面的这行代码是提取部分来解析:

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类型的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 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类型的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:

  • 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}部分的处理,后面在”参数绑定“分析时会详细解读。

Mybatis源码分析之Mapper文件解析的更多相关文章

  1. mybatis源码分析之02配置文件解析

    该篇正式开始学习mybatis的源码,本篇主要学习mybatis是如何加载配置文件mybatis-config.xml的, 先从测试代码入手. public class V1Test { public ...

  2. Mybatis源码分析之Mapper的创建和获取

    Mybatis我们一般都是和Spring一起使用的,它们是怎么融合到一起的,又各自发挥了什么作用? 就拿这个Mapper来说,我们定义了一个接口,声明了一个方法,然后对应的xml写了这个sql语句, ...

  3. Mybatis源码分析之Mapper执行SQL过程(三)

    上两篇已经讲解了SqlSessionFactory的创建和SqlSession创建过程.今天我们来分析myabtis的sql是如何一步一步走到Excutor. 还是之前的demo    public  ...

  4. mybatis源码分析之06二级缓存

    上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的. 通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置 &l ...

  5. MyBatis 源码分析 - 映射文件解析过程

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

  6. 精尽MyBatis源码分析 - MyBatis初始化(二)之加载Mapper接口与XML映射文件

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  7. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  8. (一) Mybatis源码分析-解析器模块

    Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...

  9. MyBatis源码分析-MyBatis初始化流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

随机推荐

  1. JS-this的用法

    o.onclick=function(){alert(this)}//这个this是指o ------ var arr=[1,2,3,4,5]; arr.a=12; arr.show=function ...

  2. ACM-ICPC2018 沈阳赛区网络预赛-E-The cake is a lie

    You promised your girlfriend a rounded cake with at least SS strawberries. But something goes wrong, ...

  3. git使用(1)----推送代码到远程

    git使用(1) 首先要明白git上有三个区域 1.工作区 2.暂存区 3.历史记录区 步骤: 1.git  init 2.配置环境(如果配置一次了以后就不用再继续配置) git  config  - ...

  4. (64位)本体学习程序(ontoEnrich)系统配置说明文档

    1系统环境 64位 Ubuntu 2 第三方依赖库配置 boost_1_44_0 #解压boost_1_44_0.tar.gz 到 /usr/local.如果出现权限问题,请用sudo执行该命令 ta ...

  5. ④ 设计模式的艺术-10.装饰(Decorator)模式

    职责 装饰模式是在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象. 装饰模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对 ...

  6. Intellij IDEA 快捷键整理(转)

    Ctrl+Shift + Enter,语句完成 “!”,否定完成,输入表达式时按 “!”键 Ctrl+E,最近的文件 Ctrl+Shift+E,最近更改的文件 Shift+Click,可以关闭文件 C ...

  7. 【计蒜客】是男人就过 8 题--Pony.AI 题 A. A String Game 后缀自动机+SG函数

    [题目]A. A String Game [题意]给定目标串S和n个子串Ti,Alice和Bob轮流选择一个子串操作,必须且只能在子串末尾添加一个字符使得新串也是S的子串,不能操作即输,求胜利者.|S ...

  8. Java生成验证码简记

    验证码定义 验证码(captcha):是一种区分用户是计算机还是人的公共全自动程序. 作用:可以防止恶意破解密码.刷票.灌水,有效防止对某一个特定注册用户用特定程序进行暴力破解的登录尝试. 验证码交互 ...

  9. NYOJ 1063 生活的烦恼 (二叉树)

    题目链接 描述 生活的暑假刚集训开始,他要决心学好字典树,二叉树,线段树和各种树,但生活在OJ上刷题的时候就遇到了一个特别烦恼的问题.那当然就是他最喜欢的二二叉树咯!题目是这样的:给你一颗非空的二叉树 ...

  10. struts入门

    1.概念