Mybatis是如何实现SQL语句复用功能的?
点击上方蓝色“程序猿DD”,选择“设为星标”
回复“资源”获取独家整理的学习资料!
来源 | https://my.oschina.net/zudajun/blog/687326
今天,我们将分析Mybatis之sqlFragment,可以翻译为sql片段,它的存在价值在于可复用sql片段,避免到处重复编写。
在工作中,往往有这样的需求,对于同一个sql条件查询,首先需要统计记录条数,用以计算pageCount,然后再对结果进行分页查询显示,看下面一个例子。
<sql id="studentProperties"><!--sql片段-->select stud_id as studId, name, email, dob, phonefrom students</sql><select id="countAll" resultType="int">select count(1) from (<include refid="studentProperties"></include><!--复用-->) tmp</select><select id="findAll" resultType="Student" parameterType="map">select * from (<include refid="studentProperties"></include><!--复用-->) tmp limit #{offset}, #{pagesize}</select>
这就是sqlFragment,它可以为select|insert|update|delete标签服务,可以定义很多sqlFragment,然后使用include标签引入多个sqlFragment。在工作中,也是比较常用的一个功能,它的优点很明显,复用sql片段,它的缺点也很明显,不能完整的展现sql逻辑,如果一个标签,include了四至五个sqlFragment,其可读性就非常差了。
sqlFragment里的内容是可以随意写的,它不需要是一个完整的sql,它可以是“,phone”这么简单的文本。
1.sqlFragment的解析过程
sqlFragment存储于Configuration内部。
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
解析sqlFragment的过程非常简单。
org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement(XNode)方法部分源码。
// 解析sqlFragment
sqlElement(context.evalNodes("/mapper/sql"));
// 为select|insert|update|delete提供服务
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
sqlFragment存储于Map<String, XNode>结构当中。其实最关键的,是它如何为select|insert|update|delete提供服务的。
2.select|insert|update|delete标签中,解析include标签的过程
org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode()方法源码。
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
// 重点关注的方法
includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
注释“pre: <selectKey> and <include> were parsed and removed”,含义为解析完,并移除。为什么要移除呢?秘密都隐藏在applyIncludes()方法内部了。
org.apache.ibatis.builder.xml.XMLIncludeTransformer.applyIncludes(Node, Properties)方法源码。
/*** Recursively apply includes through all SQL fragments.* @param source Include node in DOM tree* @param variablesContext Current context for static variables with values*/private void applyIncludes(Node source, final Properties variablesContext) {if (source.getNodeName().equals("include")) {// new full context for included SQL - contains inherited context and new variables from current include nodeProperties fullContext;String refid = getStringAttribute(source, "refid");// replace variables in include refid valuerefid = PropertyParser.parse(refid, variablesContext);Node toInclude = findSqlFragment(refid);Properties newVariablesContext = getVariablesContext(source, variablesContext);if (!newVariablesContext.isEmpty()) {// merge contextsfullContext = new Properties();fullContext.putAll(variablesContext);fullContext.putAll(newVariablesContext);} else {// no new context - use inherited fullyfullContext = variablesContext;}// 递归调用applyIncludes(toInclude, fullContext);if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {toInclude = source.getOwnerDocument().importNode(toInclude, true);}// 将include节点,替换为sqlFragment节点source.getParentNode().replaceChild(toInclude, source);while (toInclude.hasChildNodes()) {// 将sqlFragment的子节点(也就是文本节点),插入到sqlFragment的前面toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);}// 移除sqlFragment节点toInclude.getParentNode().removeChild(toInclude);} else if (source.getNodeType() == Node.ELEMENT_NODE) {NodeList children = source.getChildNodes();for (int i=0; i<children.getLength(); i++) {// 递归调用applyIncludes(children.item(i), variablesContext);}} else if (source.getNodeType() == Node.ATTRIBUTE_NODE && !variablesContext.isEmpty()) {// replace variables in all attribute valuessource.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));} else if (source.getNodeType() == Node.TEXT_NODE && !variablesContext.isEmpty()) {// replace variables ins all text nodessource.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));}}
上面是对源码的解读,为了便于理解,我们接下来采用图示的办法,演示其过程。
3.图示过程演示
①解析节点
<select id="countAll" resultType="int">select count(1) from (<include refid="studentProperties"></include>) tmp</select>
②include节点替换为sqlFragment节点
<select id="countAll" resultType="int">select count(1) from (<sql id="studentProperties">select stud_id as studId, name, email, dob, phonefrom students</sql>
) tmp</select>
③将sqlFragment的子节点(文本节点)insert到sqlFragment节点的前面。注意,对于dom来说,文本也是一个节点,叫TextNode。
<select id="countAll" resultType="int">select count(1) from (select stud_id as studId, name, email, dob, phonefrom students<sql id="studentProperties">select stud_id as studId, name, email, dob, phonefrom students</sql>
) tmp</select>
④移除sqlFragment节点
<select id="countAll" resultType="int">select count(1) from (select stud_id as studId, name, email, dob, phonefrom students
) tmp</select>
⑤最终结果如图所示
(Made In QQ截图及时编辑)
如此一来,TextNode1 + TextNode2 + TextNode3,就组成了一个完整的sql。遍历select的三个子节点,分别取出TextNode的value,append到一起,就是最终完整的sql。
这也是为什么要移除<selectKey> and <include>节点的原因。
这就是Mybatis的sqlFragment,以上示例,均为静态sql,即static sql。
往期推荐
扛住100亿次请求?我们来试一试!
SpringBoot + Mybatis + Druid + PageHelper 实现多数据源分页
Java 中的 BigDecimal,你真的会用吗?
华为阿里下班时间曝光:所有的光鲜,都有加班的味道
MySQL 的 Binlog 日志处理工具(Canal,Maxwell,Databus,DTS)对比
Serverless:为我们到底带来了什么
星球限时拼团优惠进行中
我的星球是否适合你?
点击阅读原文看看我们都聊过啥?
Mybatis是如何实现SQL语句复用功能的?相关推荐
- sql if 和insert_拼多多面试:Mybatis是如何实现SQL语句复用功能的?
在工作中,往往有这样的需求,对于同一个sql条件查询,首先需要统计记录条数,用以计算pageCount,然后再对结果进行分页查询显示,看下面一个例子. <sql id="student ...
- MyBatis超详细介绍——SQL语句构建器类
MyBatis超详细介绍--SQL语句构建器类 (本文作为学习笔记,了解更多请参考:MyBatis参考文档) MyBatis3提供了SQL类帮助构造SQL语句: private String sele ...
- Mybatis映射文件动态SQL语句-01
因为在很多业务逻辑复杂的项目中,往往不是简单的sql语句就能查询出来自己想要的数据,所有mybatis引入了动态sql语句, UserMapper.xml <?xml version=" ...
- mybatis log4j 在日志中打印sql_SpringBoot整合MyBatis+详细打印执行SQL语句
图片来源网路 为啥写这篇文章呢,有人可能会说springBoot的mybatis的starter都有了写这篇文章不是多此一举吗?难道是真的吗?其实我一开始也是使用mybatis的starter的,但是 ...
- Mybatis 开启控制台打印sql语句
概述 springboot+mybatis整合过程中,开启控制台sql语句打印的多种方式: 方法1 在springboot+mybatis整合中,可以将springboot的配置文件添加如下一段也可: ...
- 使用Mybatis相关类生成sql语句
项目中有时需要实现动态sql功能(非mybatis动态sql片段): sql语句中可以使用mybatis的各种标签,注意<>等符号的转义,最后会根据输入的参数生成最终的sql,需要引入my ...
- 黑马程序员视频教程学习mybatis框架常用注释SQL语句学习笔记?
mybatis学习笔记 常用注释增删改查SQL语句 常用注释拓展SQL语句 解决实体类属性和数据库表中的属性名称不相同的问题: mybatis注解之一对一查询: mybatis注解之一对多查询: my ...
- Mybatis拦截器修改sql语句
拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能. MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用.默认情况下,MyBatis 允许 ...
- 解决Mybatis结合db2时sql语句换行出现的问题
最近项目用到mybatis3.1和db28.1版本的数据库,出现一个问题,我研究半天把mybatis源码给改了,竟然成功解决. 直接入正题:在mybatis映射文件中写sql语句时如果有折行情况就报错 ...
最新文章
- 新闻智能分类练习赛开始报名啦!最先达到80分就可以领GPU,技术书籍!
- 2020移动apn接入点哪个快_为什么都是4G网你的就没别人快?跟我这样设置,网速直线提升...
- Array.splice()--删除数组中重复的数据
- SAP Fiori Elements - smart filter bar study - filter display logic
- c语言程序输入n个数字排序,输入n个数字然后进行排序,用C语言编写。注意是n个数啊,不是确定的个数。...
- 右键单击文件夹进入命令行窗口
- java sql分页_mysql、sqlserver、oracle分页,java分页统一接口实现
- MYSQL返回指定时间间隔函数DATE_SUB和TO_DAYS详解
- java ArrayList的实现
- java基础——Vector集合知识点
- 第2章[2.2] Ext JS多类型终端-电脑、移动端(手机、平板)
- python3除法运算_Python2和Python3中除法操作/的不同
- sql取整数_SQL高级功能
- r语言作业:出租车数据基础数据分析、时间处理等
- 部分大学上演造假运动闯关本科评估(图)
- 做支付行业这些支付方式你需要知道!
- 在n*n方阵里填入1,2,...n*n,要求填成蛇形
- Synergy:亚马逊云服务(AWS)在亚太地区排名第一
- win7在网上邻居上看不到别的电脑如何解决
- ERD Commander 2005 Boot CD 剖析及汉化试验
热门文章
- linux 命令输出 保存到文件 日志记录
- golang 代码安全审计
- Linux里如何查找文件内容 grep
- linux kernel makefile analysis
- fast system call 快速系统调用
- Xen与KVM虚拟化技术调研报告
- dellr320故障_dell r320服务器,idrac问题
- eBPF学习——抓取内核网络中的socket信息
- 计算机专业知识考试 一,计算机专业知识试题(事业单位计算机专业知识考试试卷)(1)...
- matlab 图像显著性检测ft_图像显著性检测总结