@

在深入理解 mybatis 原理过程中, 我不单单是想理解整个 mybatis 是怎么运行的, 我还想从这个过程中提取出一些对自己有益的编程方法, 编程思想, 注释, 以及一些实用工具类。

1. 简介

1.1 mybatis-config.xml 中使用

mybatis-config.xml 文件中, 我们常常看到类似的配置

<properties>
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="aaabbb" />
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<!--填写你的数据库用户名-->
<property name="username" value="${username}"/>
<!--填写你的数据库密码-->
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>

将一些属性放置在 properties标签下的子标签中, 后续在配置文件中就可以使用 ${key} 的形式将 value 取出。

也可以将属性配置在外部文件中,将外部文件的相对路径告知解析器即可:

<properties resource="jdbc.properties"></properties>

1.2 xxxMapper.xml 中使用

当然, 在 xxxMapper.xml 中, 我们写 SQL 语句时也会用到, 如

    select
<include refid="Base_Column_List" />
from student
where student_id=#{student_id, jdbcType=INTEGER}

#{student_id, jdbcType=INTEGER} 替换为传入的参数。

2. 原理

在 mybatis 中, 处理这个过程的就是 GenericTokenParser 类。

2.1 GenericTokenParser 成员变量

GenericTokenParser 类有三个成员变量

// 开始标记
private final String openToken;
// 结束标记
private final String closeToken;
// 表处理器
private final TokenHandler handler;

举个例子

解析以上配置中的 ${driver}, 那么这几个成员变量

openToken="${";
closeToken="}";

handler则是一个 TokenHandler 接口

public interface TokenHandler {
String handleToken(String content);
}

在实际的过程中, 我们需要自己定义的处理器, 该处理器实现 TokenHandler 即可, 后面的例子中会有示例。

2.2 GenericTokenParser 构造函数

构造函数很简单, 就是给几个成员变量赋值即可。

public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}

2.3 解析过程

2.3.1 整体流程

大的流程如下:

整体上来讲, 就是找到这个需要处理的表达式, 将表达式的内容替换为处理器处理后的内容, 最后返回最终的字符串。

2.3.2 流程详解

先看代码以及我给的注释

public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// 从第0位开始, 查找开始标记的下标
int start = text.indexOf(openToken, 0);
if (start == -1) { // 找不到则返回原参数
return text;
}
char[] src = text.toCharArray();
// offset用来记录builder变量读取到了哪
int offset = 0;
// builder 是最终返回的字符串
final StringBuilder builder = new StringBuilder();
// expression 是每一次找到的表达式, 要传入处理器中进行处理
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// 开始标记是转义的, 则去除转义字符'\'
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// 此分支是找到了结束标记, 要找到结束标记
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
// 将开始标记前的字符串都添加到 builder 中
builder.append(src, offset, start - offset);
// 计算新的 offset
offset = start + openToken.length(); // 从此处开始查找结束的标记
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// 此结束标记是转义的
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// 找不到结束标记了
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 找到了结束的标记, 则放入处理器进行处理
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
// 因为字符串中可能有很多表达式需要解析, 因此开始下一个表达式的查找
start = text.indexOf(openToken, offset);
}
// 最后一次未找到开始标记, 则将 offset 后的字符串添加到 builder 中
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}

如果你看代码看明白了, 就不用看下面的详细过程了

第一步:就是参数的非空处理

参数为空或 "" , 则返回 ""。

if (text == null || text.isEmpty()) {
return "";
}

第二步:查找开始标记,声明变量

    // 从第0位开始, 查找开始标记的下标
int start = text.indexOf(openToken, 0);
if (start == -1) { // 找不到则返回原参数
return text;
}
char[] src = text.toCharArray();
// offset用来记录 builder 变量读取到的位置
int offset = 0;
// builder 是最终返回的字符串
final StringBuilder builder = new StringBuilder();
// expression 是每一次找到的表达式, 要传入处理器中进行处理
StringBuilder expression = null;

如果传入的字符串中没有要处理的开始标记, 那么直接就返回了, 不需要进行一堆变量的声明。

如果找到了, 则进行变量的声明。

第三步: 循环查找标记并进行处理

在本例子中, 假设开始标记为 ${, 结束标记为 }

首先, 先查找开始标记符

只有找到了开始标记符才需要去找对应的结束标记符, 不然单独找到结束标记符没有意义。

我们找到了 ${, 有两种情况:

  1. ${ 是开始标记符
  2. ${ 就是我们本身想要的字符

如何区分这两种情况呢, 正常的情况得到的是情况1, 如果想得到情况2, 在该解析器中, 是需要加入转义符号\\

即我们在最终的字符中想要得到 ${字符, 则应该这样写 \\${

对于情况2, 该解析器中是这样子处理的

 builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();

把转移符去掉, 将字符串添加到 builder 中。 记录解析到的位置 offset。 继续查找下一个开始标记。

对于情况1,

接着, 查找结束标记符

我们找到了 }, 有两种情况:

  1. } 是开始标记符
  2. } 就是我们本身想要的字符

那么, 如同前面, 情况2也需要转移标记才能区分。情况2处理

 // 此结束标记是转义的
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);

把转移符去掉, 将字符串添加到 builder 中。 记录解析到的位置 offset。 继续查找下一个结束标记,直到找到的是情况1, 则跳出循环。

对于情况1, 得到${}之间的表字符串作为 expression, 继续往下

处理器处理

 // 找到了结束的标记, 则放入处理器进行处理
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();

最后, 只要还没找到最后, 则继续查找下一个开始的标记

start = text.indexOf(openToken, offset);

3. 测试

  @Test
public void simpleTest() {
GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<String, String>() {
{
put("driver", "com.mysql.jdbc.Driver");
put("url", "jdbc:mysql://localhost:3306/mybatis");
put("username", "root");
put("password", "aaabbb"); }
})); // 测试单个解析
assertEquals("com.mysql.jdbc.Driver", parser.parse("${driver}"));
// 多个一起测试
assertEquals("驱动=com.mysql.jdbc.Driver,地址=jdbc:mysql://localhost:3306/mybatis,用户名=root",
parser.parse("驱动=${driver},地址=${url},用户名=${username}"));
}

4 代码

如需要代码, 请访问我的Github

如有问题, 请跟我交流。

mybatis抽取出的工具-(一)通用标记解析器(即拿即用)的更多相关文章

  1. 浩哥解析MyBatis源码(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...

  2. MyBatis源码解析(十一)——Parsing解析模块之通用标记解析器(GenericTokenParser)与标记处理器(TokenHandler)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6724223.html 1.回顾 上面的几篇解析了类型模块,在MyBatis中类型模块包含的 ...

  3. Mybatis源码学习之parsing包(解析器)(二)

    简述 大家都知道mybatis中,无论是配置文件mybatis-config.xml,还是SQL语句,都是写在XML文件中的,那么mybatis是如何解析这些XML文件呢?这就是本文将要学习的就是,m ...

  4. [开源]开放域实体抽取泛用工具 NetCore2.1

    开放域实体抽取泛用工具 https://github.com/magicdict/FDDC 更新时间 2018年7月16日 By 带着兔子去旅行 开发这个工具的起源是天池大数据竞赛,FDDC2018金 ...

  5. 【POI xls Java map】使用POI处理xls 抽取出异常信息 --java1.8Group by ---map迭代 -- 设置单元格高度

    代码处理逻辑: 代码流程: 1.首先需要创建一个实体 用来存储 相关信息 package com.sxd.test.unusualName; public class NameEntity { pri ...

  6. Android弹出Toast工具类总结

    Android弹出Toast工具类总结,包括系统自带的,也包括自定义的. public class ToastUtil { public ToastUtil() { } public static T ...

  7. hive表增量抽取到mysql(关系数据库)的通用程序(三)

    hive表增量抽取到oracle数据库的通用程序(一) hive表增量抽取到oracle数据库的通用程序(二) 这几天又用到了该功能了,所以又改进了一版,增加了全量抽取和批量抽取两个参数.并且可以设置 ...

  8. hive表增量抽取到oracle数据库的通用程序(二)

    hive表增量抽取到oracle数据库的通用程序(一) 前一篇介绍了java程序的如何编写.使用以及引用到的依赖包.这篇接着上一篇来介绍如何在oozie中使用该java程序. 在我的业务中,分为两段: ...

  9. hive表增量抽取到oracle数据库的通用程序(一)

    hive表增量抽取到oracle数据库的通用程序(二) sqoop在export的时候 只能通过--export-dir参数来指定hdfs的路径.而目前的需求是需要将hive中某个表中的多个分区记录一 ...

随机推荐

  1. session,cookie,sessionStorage,localStorage的区别

    浏览器的缓存机制提供了可以将用户数据存储在客户端上的方式,可以利用cookie,session等跟服务端进行数据交互. 一.cookie和session cookie和session都是用来跟踪浏览器 ...

  2. go 开发中需要注意的与python的不同点

    从python转golang开发已经3个月了,因为写过c++,所以对golang接受的还算快,这段经历也不是很痛苦.伯乐在线上看了一些大神关于python转golang过程中的不适应和吐槽,决定写下篇 ...

  3. Django JsonResponse与HttpResponse重要区别

    JsonResponse是HttpResponse的一个子类,是Django提供的用于创建JSON编码类型响应的快捷类.它的默认Content-Type头部设置为application/json,它的 ...

  4. 使用Visual Studio Team Services持续集成(一)——构建ASP.NET Core

    使用Visual Studio Team Services持续集成(一)--构建ASP.NET Core 概述 持续集成(CI)是将代码尽可能频繁地集成到共享仓库中的过程.在代码集成期间,构建中断或测 ...

  5. Linux日志出现大量"kernel: NET: Registered protocol family 36"

    一台Linux服务器的系统错误日志出现大量的" kernel: NET: Registered protocol family 36"错误信息,如下所示: Jul  2 05:27 ...

  6. 翻译:MySQL "Got an Error Reading Communication Packet" Errors

    前言: 本文是对Muhammad Irfan的这篇博客MySQL "Got an Error Reading Communication Packet" Errors的翻译,如有翻 ...

  7. Process 0:0:0 (0x1ffc) Worker 0x00000001E580A1A0 appears to be non-yielding on Scheduler 3. Thread creation time: 13153975602106.

    现场报错如下: Process 0:0:0 (0x1ffc) Worker 0x00000001E580A1A0 appears to be non-yielding on Scheduler 3. ...

  8. 设置SSH免密码登录

    1.cd .ssh 2.执行下面的命令,三次回车. ssh-keygen -t rsa cat id_rsa.pub >> authorized_keys 3.发送公钥 scp .ssh/ ...

  9. Unity 琐碎(4) 可视化辅助调试Giamos

    Gizmos 类 可以在代码中绘制一些输出结果或者中间输出内容,比如计算后的包围盒等等 类变量 方法 作用 color 控制输出颜色 matrix Set the gizmo matrix used ...

  10. ArcMap2Sld:一个将MXD中图层配图样式转换为OGC的SLD文件的开源工具

    在一个项目中,用户采用GeoServer做为GIS服务器(原因嘛当然是免费能省钱,经过验证可能还会在性能和稳定性等表现力也有优越性),但是手上收集的数据都是ESRI格式的,这倒不打紧,因为GeoSer ...