网易首页 > 网易号 > 正文 申请入驻

java培训Mybatis动态Sql处理解析

0
分享至

以下文章来源于架构师必备

动态Sql介绍

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

Mybatis动态解析里面有2个核心的类SqlNode、SqlSource、ExpressionEvaluator。Mybatis动态Sql使用分为2个部分:动态Sql解析、动态Sql拼接执行。

封装SqlNode

SqlNode是在解析Xml文件的时候对动态Sql进行解析,【关注尚硅谷,轻松学IT】并存在MappedStatement的sqlSource属性中。对于嵌套动态Sql,mybatis用递归调用来进行解析。这块东西个人觉得还是比较绕,所以这块博主准备事例、源码、执行结果一起讲解。

Sql脚本分类

在Mybatis中Sql脚本分为2种类型:静态Sql和动态Sql。下面我们通过具体的源码来看下2者区分。

静态Sql和动态Sql

静态Sql说白了就没有太任何判断了解的Sql脚本。

// Select 是查询的一些属性

//这条查询语句select * from user where id > #{user.id}就是Mybatis中的静态Sql//静态Sql就是不太任何条件的Sql语句select * from user where id > #{ user.id}//这里有if判断条件,Mybatis把带有判断条件的Sql叫动态Sql。//动态Sql除了if之外还有foreach、where、trim等。具体自己去mybatis官网看下AND name = #{ user.name}

SqlNode类结果体系

看mybatis代码很多时候可以看到这种结构。每个SqlNode负责自己那块功能。职责单一。SqlNode的核心方法apply就是通过ExpressionEvaluator来解析OGNL表达式数据的。接下来我们看看Mybatis是如何递归解析动态sql脚本的。

// 解析Sql脚本节点

public SqlSource parseScriptNode() {

//解析静态和动态脚本,并存在MixedSqlNode里面

//这行代码很关键,后面我们会去分析parseDynamicTags这里就是一层一层递归调用该方法把Sql脚本生成MixedSqlNode对象。

MixedSqlNode rootSqlNode = parseDynamicTags(context);

SqlSource sqlSource = null;

//是否为动态Sql

if (isDynamic) {

//动态Sql则生成DynamicSqlSource

sqlSource = new DynamicSqlSource(configuration, rootSqlNode);

} else {

//否则为静态SqlSource

sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);

return sqlSource;

// An highlighted block

protected MixedSqlNode parseDynamicTags(XNode node) {

//创建个SqlNode,这个列表存了当前Sql脚本节点下的所有的SqlNode信息

List

contents = new ArrayList

NodeList children = node.getNode().getChildNodes();

for (int i = 0; i < children.getLength(); i++) {

XNode child = node.newXNode(children.item(i));

//判断子元素或属性中的文本内容 || 子元素文档中的 CDATA 部(不会由解析器解析的文本)

if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {

String data = child.getStringBody("");

//解析data

TextSqlNode textSqlNode = new TextSqlNode(data);

//判断当前的Sql脚本是否为动态脚本

if (textSqlNode.isDynamic()) {

contents.add(textSqlNode);

isDynamic = true;

} else {

contents.add(new StaticTextSqlNode(data));

//如果子元素为代表元素,则需要解析子元素

} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628

//获取元素的名字

String nodeName = child.getNode().getNodeName();

//根据元素名获取到元素节点的处理器,Mybatis提供了8中元素处理器,ChooseHandler、IfHandler、OtherwiseHandler

//TrimHandler、BindHandler、WhereHandler、SetHandler、ForEachHandler。博主会给大家分析下IfHandler

NodeHandler handler = nodeHandlerMap.get(nodeName);

if (handler == null) {

throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");

//调用对应的handler进行节点处理,递归调用就在这块

handler.handleNode(child, contents);

isDynamic = true;

//创建MixedSqlNode

return new MixedSqlNode(contents);

// 下面我们看下IfHandler是如何处理,IfHandler是XMLScriptBuilder的内部类

private class IfHandler implements NodeHandler {

public IfHandler() {

// Prevent Synthetic Access

//我们着重分析这个方法

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

//调用parseDynamicTags进行节点解析。这里就是递归,又调用了上面的方法。

MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

//获取if对应的表达式

String test = nodeToHandle.getStringAttribute("test");

//创建IfSqlNode

IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);

targetContents.add(ifSqlNode);

下面我们根据Sql脚本和执行结果来分析。

// 静态Sql脚本和嵌套的动态Sql脚本

select * from user where id > #{ user.id}AND name = #{ user.name}AND name = #{ user.name}AND name = #{ user.name}

下面我们分析下执行结果:

上面递归结果已经用不通颜色标记了,大家自己看下。特别需要看下IfSqlNode的属性。

动态Sql解析

动态Sql解析主要是执行数据库操作的时【关注尚硅谷,轻松学IT】候把动态Sql转换成JDBC能识别的Sql脚本。Mybatis中主要是通过SqlSource来解析Sql脚本,替换成JDBC能识别的Sql脚本。我们先看下类图。

SqlSource:提供了Sql解析的行为。
RawSqlSource:静态Sql脚本的编译,只生成一次StaticSqlSource。
DynamicSqlSource:每次调用都会生成StaticSqlSource。每次调用传入参数可能不一样。需要每次生成StaticSqlSource。
ProviderSqlSource:第三方脚本语言的集成。
FreeMarkerSqlSource:对FreeMarker的支持。
StaticSqlSource:StaticSqlSource只是对上面4中类型做了层封装。博主没有这个类会更清爽些。
我们这次主要对StaticSqlSource、RawSqlSource、和DynamicSqlSource进行分析。

StaticSqlSource

其实StaticSqlSource就是对其他几种类型Sql处理器结果进行包装。我们看下源码。

//我们主要分析下getBoundSql

public class StaticSqlSource implements SqlSource {

private final String sql;

private final List

parameterMappings;

private final Configuration configuration;

public StaticSqlSource(Configuration configuration, String sql) {

this(configuration, sql, null);

public StaticSqlSource(Configuration configuration, String sql, List

parameterMappings) {

this.sql = sql;

this.parameterMappings = parameterMappings;

this.configuration = configuration;

//getBoundSql就是创建一个BoundSql对象。

@Override

public BoundSql getBoundSql(Object parameterObject) {

return new BoundSql(configuration, sql, parameterMappings, parameterObject);

看完是不是非常简单,其实有些代码确实没有我们想象中那么难。

RawSqlSource

// 我们着重分析RawSqlSource方法

public class RawSqlSource implements SqlSource {

private final SqlSource sqlSource;

public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class parameterType) {

this(configuration, getSql(configuration, rootSqlNode), parameterType);

//这里实现了对静态脚本的解析,所谓的静态脚本解析就是把 #{}解析成?静态Sql解析是在解析Mapper.xml的时候执行的

public RawSqlSource(Configuration configuration, String sql, Class parameterType) {

SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);

Class clazz = parameterType == null ? Object.class : parameterType;

//通过调用SqlSourceBuilder的parse方法来解析Sql

sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap

private static String getSql(Configuration configuration, SqlNode rootSqlNode) {

DynamicContext context = new DynamicContext(configuration, null);

rootSqlNode.apply(context);

return context.getSql();

@Override

public BoundSql getBoundSql(Object parameterObject) {

return sqlSource.getBoundSql(parameterObject);

下面我们来看下SqlSourceBuilder的parse方法

public SqlSource parse(String originalSql, Class parameterType, Map

additionalParameters) {

ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);

//找到Sql脚本中#{}符号的脚本用?号进行替代。GenericTokenParser里面代码比较复杂,博主也没有研究。

//有兴趣自己可以研究下。

GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);

String sql = parser.parse(originalSql);

return new StaticSqlSource(configuration, sql, handler.getParameterMappings());

DynamicSqlSource

动态Sql解析主要由DynamicSqlSource来完成。这里面又是通过递归调进行sql解析。我们还是延用上面的Sql给大家讲解。

public class DynamicSqlSource implements SqlSource {

private final Configuration configuration;

private final SqlNode rootSqlNode;

public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {

this.configuration = configuration;

this.rootSqlNode = rootSqlNode;

@Override

public BoundSql getBoundSql(Object parameterObject) {

//动态Sql解析上下文

DynamicContext context = new DynamicContext(configuration, parameterObject);

//rootSqlNode就是我们前面讲解的,把动态Sql解析成SqlNode对象。外层为MixedSqlNode节点,节点存储了

//节点下的所有子节点。里面递归调用并根据传入参数的属性检查是否需要拼接sql

rootSqlNode.apply(context);

//这块代码和上面静态Sql接代码一致。

SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);

Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass();

//把我们动态Sql中的#{}替换成?

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

BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

for (Map.Entry

entry : context.getBindings().entrySet()) {

boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());

return boundSql;

动态Sql解析apply方法博主只根据场景介绍下MixedSqlNode和IfSqlNode的apply方法。其他有兴趣自己去研究下。逻辑大体一致,实现有些区别。

public class MixedSqlNode implements SqlNode {

private final List

contents;

public MixedSqlNode(List

contents) {

this.contents = contents;

//获取循环SqlNode列表的所有SqlNode,调用apply方法根据传入参数和条件进行静态sql的拼接。

//列表中的SqlNode可能是一个简单的SqlNode对象,也可能是一个MixedSqlNode或者有更多的嵌套。

//博主的例子就是3个嵌套If查询。根据博主的Sql脚本,这里直接会调用IfSqlNode的apply方法。

//我们接下来看下IfSqlNode是如何实现的。

@Override

public boolean apply(DynamicContext context) {

for (SqlNode sqlNode : contents) {

sqlNode.apply(context);

return true;

IfSqlNode的apply

public class IfSqlNode implements SqlNode {

//ExpressionEvaluator会调用ognl来对表达式进行解析

private final ExpressionEvaluator evaluator;

private final String test;

private final SqlNode contents;

public IfSqlNode(SqlNode contents, String test) {

this.test = test;

this.contents = contents;

this.evaluator = new ExpressionEvaluator();

@Override

public boolean apply(DynamicContext context) {

//context.getBindings()里面就存储这请求参数,这里是一个HashMap,OGNl里面代码博主没有研究。

//如果条件if成立,直接获取contents中的SqlNode的apply方法进行动态脚本处理。

if (evaluator.evaluateBoolean(test, context.getBindings())) {

contents.apply(context);

return true;

return false;

这块代码很多递归调用,博主自认为讲的不太透彻,所以大家看完务必自己去调试下。

总结

Mybatis动态Sql从解析到执行分为2个过程下面对这个2个过程进行简单总结。
1.动态Sql生成SqlNode信息,这个过程发生在对select、update等Sql语句解析过程。如果是静态Sql直接会把#{}替换成?。
2.动态Sql解析在获取BoundSql时候触发。会调用SqlNode的apply进行Sql解析成静态Sql,然后把#{}替换成?,并绑定ParameterMapping映射。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
普京透露:近70万俄罗斯军人参与特别军事行动

普京透露:近70万俄罗斯军人参与特别军事行动

参考消息
2024-06-15 12:26:07
警方通报“男子卧铺脱衣面对女乘客裸睡”:行拘5日

警方通报“男子卧铺脱衣面对女乘客裸睡”:行拘5日

极目新闻
2024-06-16 13:41:50
F16进场第一件事就是和宇内第一S500碰一下,会如何?

F16进场第一件事就是和宇内第一S500碰一下,会如何?

邵旭峰域
2024-06-15 14:00:02
性爱宝典:性交时应适时插入

性爱宝典:性交时应适时插入

福大菽
2024-06-16 16:14:40
女主播爆料行业内幕,大量女主播都有价格,周淑怡确实值百万

女主播爆料行业内幕,大量女主播都有价格,周淑怡确实值百万

新游戏大妹子
2024-06-14 11:43:32
上任不到24小时,印度外长对华喊话,称将重点解决中印边境问题

上任不到24小时,印度外长对华喊话,称将重点解决中印边境问题

简读视觉
2024-06-16 16:26:25
四个永不倒闭的行业,选择对了就能过上无忧无虑的生活!

四个永不倒闭的行业,选择对了就能过上无忧无虑的生活!

趣说世界哈
2024-06-16 07:55:06
一颗子弹别想运进台湾!美国军火马上就到,大陆早已准备海上拦截

一颗子弹别想运进台湾!美国军火马上就到,大陆早已准备海上拦截

小阿文热点军
2024-06-15 19:13:11
雷克萨斯新车太美!2.4T混合动力+顶尖马克音响,穷人也买得起

雷克萨斯新车太美!2.4T混合动力+顶尖马克音响,穷人也买得起

户外小阿隋
2024-06-15 11:38:00
决定俄乌命运时刻,1万集装箱武器:横穿西伯利亚铁路塞满弹药库

决定俄乌命运时刻,1万集装箱武器:横穿西伯利亚铁路塞满弹药库

农村雯雯的vlog
2024-06-16 03:46:13
【紧要】大暴雨!今日抵达广州!广州一地紧急泄洪!接下来天气……

【紧要】大暴雨!今日抵达广州!广州一地紧急泄洪!接下来天气……

江粤平台
2024-06-16 15:19:39
江苏“新增”号牌“苏X”?

江苏“新增”号牌“苏X”?

江南晚报
2024-06-16 11:49:18
106国参加瑞士和会:中方拒绝参会,与世界文明为伍,勿忘雅尔塔

106国参加瑞士和会:中方拒绝参会,与世界文明为伍,勿忘雅尔塔

大风文字
2024-06-03 10:27:47
"最美女婴"刚出生就成网红,凭颜值征服网友,护士:难得一遇

"最美女婴"刚出生就成网红,凭颜值征服网友,护士:难得一遇

大果小果妈妈
2024-06-15 08:51:31
承重柱“一踢就烂”、钢筋“锈迹斑斑”,业主:毫无安全感!恒大海花岛有小区被疑“海砂楼”,官方最新通报

承重柱“一踢就烂”、钢筋“锈迹斑斑”,业主:毫无安全感!恒大海花岛有小区被疑“海砂楼”,官方最新通报

每日经济新闻
2024-06-15 13:27:16
九人迈阿密国际2-1逆转费城联合,莱奥-阿方索补时奔袭绝杀

九人迈阿密国际2-1逆转费城联合,莱奥-阿方索补时奔袭绝杀

懂球帝
2024-06-16 09:46:35
笑死!阿信当众唱大s代表作,汪小菲若有所思,马筱梅表情亮了

笑死!阿信当众唱大s代表作,汪小菲若有所思,马筱梅表情亮了

娱记掌门
2024-06-16 16:02:10
人社部发布最新养老金数据,2024年企退休人员平均养老金是多少?

人社部发布最新养老金数据,2024年企退休人员平均养老金是多少?

社保小达人
2024-06-15 12:19:45
6月16日赛程公布:中国女排冲4连胜,总决赛席位将确定,美日大战

6月16日赛程公布:中国女排冲4连胜,总决赛席位将确定,美日大战

草根体育
2024-06-16 00:09:44
现在月薪1万在中国是什么水平?

现在月薪1万在中国是什么水平?

陌小尘桑
2024-01-03 18:50:03
2024-06-16 17:26:44
IT爱好者小尚
IT爱好者小尚
分享IT教育类信息
630文章数 55关注度
往期回顾 全部

科技要闻

iPhone 16会杀死大模型APP吗?

头条要闻

G7峰会意总理向马克龙投去"死亡凝视" 视频在外网疯传

头条要闻

G7峰会意总理向马克龙投去"死亡凝视" 视频在外网疯传

体育要闻

没人永远年轻 但青春如此无敌还是离谱了些

娱乐要闻

上影节红毯:倪妮好松弛,娜扎吸睛

财经要闻

打断妻子多根肋骨 上市公司创始人被公诉

汽车要闻

售17.68万-21.68万元 极狐阿尔法S5正式上市

态度原创

教育
游戏
亲子
家居
公开课

教育要闻

山东夏季高考选择题已阅完,整体评卷工作将于6月20日结束,6月25日公布成绩

魔兽国服停服前WLK服务器人口和排队数据,回归选服参考

亲子要闻

事实证明音乐确实影响人类情绪,短短四十秒 萌娃情绪大起大落

家居要闻

空谷来音 朴素留白的侘寂之美

公开课

近视只是视力差?小心并发症

无障碍浏览 进入关怀版