MyBatis执行动态SQL的方法
作者:isea533 发布时间:2021-05-29 04:02:17
大家基本上都知道如何使用 MyBatis 执行任意 SQL,使用方法很简单,例如在一个 XXMapper.xml 中:
<select id="executeSql" resultType="map">
${_parameter}
</select>
你可以如下调用:
sqlSession.selectList("executeSql", "select * from sysuser where enabled = 1");
或者你可以在 XXMapper.java 接口中定义如下方法:
List<Map> executeSql(String sql);
然后使用接口调用方法:
xxMapper.executeSql("select * from sysuser where enabled = 1");
上面这些内容可能都会,下面在此基础上再复杂一点。
假如像上面SQL中的enabled = 1
我想使用参数方式传值,也就是写成enabled = #{enabled}
,如果你没有遇到过类似这种需求,可能不明白为什么要这么写,举个例子,要实现一种动态查询,可以在前台通过配置 SQL,提供一些查询条件就能实现一个查询的功能(为了安全,这些配置肯定是开发或者实施做的,不可能让用户直接操作数据库)。
针对这个功能,使用 MyBatis 实现起来相当容易。配置 SQL 肯定要执行,用上面讲的这种方式肯定可以执行 SQL,如何提供参数呢?参数就是enabled = #{enabled}
中的#{enabled}
部分。如果再多一些条件,一个配置好的 SQL 如下:
select * from sysuser
where enabled = #{enabled}
and userName like concat('%',#{userName},'%')
这种情况下,该怎么用 MyBatis 实现呢?
首先 XML 中修改如下:
<select id="executeSql" resultType="map">
${sql}
</select>
接口中的方法修改为:
List<Map> executeSql(Map map);
然后调用方法:
Map map = new HashMap();
//这里的 sql 对应 XML 中的 ${sql}
map.put("sql", "select * from sysuser "
+ " where enabled = #{enabled} "
+ " and userName like concat('%',#{userName},'%')");
//#{enabled}
map.put("enabled", 1);
//#{userName}
map.put("userName", "admin");
//接口方式调用
List<Map> list = xxMapper.executeSql(map);
//sqlSession方式调用
sqlSession.selectList("executeSql", map);
有了这个SQL之后,就可以将 enabled 和 userName 作为条件提供给用户。这两个条件显然是必填的。如果是可选的,那该怎么写?
也许有人想到了是不是可以用 MyBatis 中的动态 SQL,使用<if>
标签等等?
再回答这个问题前,我们先看处理动态 SQL 的DynamicSqlSource
中的代码:
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class < ?>parameterType =
parameterObject == null ? Object.class: parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(),
parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry < String, Object > entry: context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
MyBatis 处理动态 SQL 时,所有动态 SQL 的标签都会处理为 SqlNode (这里的rootSqlNode
)对象,包含${}
的也会处理为TextSqlNode
对象,在上面方法的前两行,就是 MyBatis 处理动态 SQL 的地方。
因此如果我们在${sql}
中的内容包含嵌套的${}
和<if>
,<where>
等标签时,他们在 MyBatis 解析 XML 为 SqlNode 对象时,XML <select>
元素包含的内容只有${sql},只有${sql}会被解析,在运行时这个参数字符串中可能包含的${}和<if>,<where>等标签,但是这都发生在 MyBatis 解析后,因此当这些内容作为字符串中的一部分出现时,他们不会被特殊处理,他们只是SQL中的一部分,只是原样输出(由于数据库不认会报错)无法被处理,因此没法通过 MyBatis 自带的这种方式来写动态 SQL。
提示
在上面的代码中:
sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
这一段代码是处理动态参数(#{})的
这个处理在动态 SQL 处理之后,
因此可以在 SQL 中使用这种类型的参数。
既然不能用 MyBatis 动态 SQL 方式,该怎么实现动态 SQL 呢?
这里提供一个简单的思路,在 SQL 中使用模板标记语言来实现动态SQL(例如freemarker),在 SQL 交给 MyBatis 执行之前,使用模板对 SQL 进行处理生成最终执行的 SQL(需要避免处理#{}
参数),将这个SQL交给 MyBatis 执行。
举一个Freemarker模板的例子,仍然以上面的SQL为基础:
select * from sysuser
where 1 = 1
<#if enabled??>
enabled = #{enabled}
</#if>
<#if userName?? && userName != ''>
and userName like concat('%',#{userName},'%')
</#if>
注意,这里的<#if>
是Freemarker的元素。在不考虑SQL注入的情况下,上面的SQL还可以写成:
select * from sysuser
where 1 = 1
<#if enabled??>
enabled = #{enabled}
</#if>
<#if userName?? && userName != ''>
and userName like '%${userName}%'
</#if>
区别就是'%${userName}%'
,因为 Freemarker 也会处理${userName}
,也会用实际的值来替换这里的参数。
在前面调用的代码中,这里修改如下:
//#{enabled}
map.put("enabled", 1);
//#{userName}
map.put("userName", "admin");
//这里的 sql 对应 XML 中的 ${sql}
String sql = "上面两个复杂SQL中的一个";
//使用Freemarker处理sql
sql = processSqlByFreemarker(sql, map);
//将处理后的sql放到map中
map.put("sql", sql);
//执行方法
List<Map> list = xxMapper.executeSql(map);
注:processSqlByFreemarker
方法就是根据map中的数据来处理sql字符串,实现方式可以自己搜索。
到这里,一个不是很复杂的动态SQL功能就实现了。
不知道有没有更贪心的人,你会不会想,上面返回值都是List<Map>
类型,能不能返回一个我指定的实体类呢?
例如在map中:
map.put("class", "tk.mybatis.model.SysUser");
能不能通过这种方式让返回值变成SysUser
类型呢?由于这篇文章耗时已经太长,这里就提供一个方案,不深入。
你可以使用 * 实现,获取MappedStatement
后,复制一份,然后修改resultMaps
中resultMap
的type
属性为你指定的class
类型就能实现,说起来容易,实际操作起来能有 PageHelper 分页插件 1/10 左右的工作量。
注:如果是动态的update,insert,delete 语句,可以将上面的<select>
改为<update>
(不需要使用<delete>
和<insert>
),返回值用int
,比select
的情况容易很多。
来源:https://blog.csdn.net/isea533/article/details/52368341
猜你喜欢
- 先给大家展示下效果图,大家觉效果满意,请参考实现代码。直接上代码:private void setDialog(){View view =
- <?xml version="1.0" encoding="utf-8"?><Ed
- 这篇文章主要介绍了Java实现发送手机短信语音验证功能代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- 本文实例讲述了spring AOP的After增强实现方法。分享给大家供大家参考,具体如下:一 配置<?xml version=&qu
- 本文实例讲述了Java实现的简单音乐播放器功能。分享给大家供大家参考,具体如下:应用名称:Java简单的音乐播放器用到的知识:Java GU
- 目录引入依赖Java中使用GraphQL的API无参数简单查询带参数简单查询GraphQL可以通过Java的API来实现数据的查询,通过特定
- 这里的问题:当我点击确定按钮,也就是 AlertDialog 里的 PositiveButton 的时候,我们需要判断用户是输入是否符合我们
- 枚举的基本用法回顾以下是一个常见的 C# 枚举(enum)的示例:enum Weekday{
- 本文汇总了几个WinForm中常见的实用技巧,对于C#程序开发有着很好的参考借鉴价值。具体分析如下:一、屏蔽窗体右上角关闭按钮1.重写OnC
- 将IDEA的默认快捷键设置 设置成为 Eclipse的默认快捷键设置题意有些拗口,但没关系,就是将idea中的快捷键转为自己刚学Java时使
- 文件写入为提供相对较高性能的文件读写操作,这里果断选择了 NIO 对文件的操作,因为业务背景需要数据的安全落盘。这里主要采用 ByteBuf
- 1.连接池在实际开发中都会使用连接池因为它可以减少我们获取连接所消耗的时间连接池就是用于存储连接的一个容器,容器其实就是一个集合对象,该集合
- 发现公司集成apollo后原来的@value注入的属性不用做任何变动,也没有换成apollo的注解,遂略看源码后大致了解,做此笔记1、根据文
- 这篇文章主要介绍了新手学习微服务SpringCloud项目架构搭建方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学
- 1.android 消息循环有4个重要的类Handler、Message、Looper、MessageQueuehandler 用来发送、处
- C# 的类型系统可分为两种类型,一是值类型,一是引用类型,这个每个C#程序员都了解。还有托管堆,栈,ref,out等等概念也是每个C#程序员
- 前言以键值对Dictionary<[key], [value]>形式存值,和哈希表很像也是一种无序的结构。要使用Dictiona
- 在"C#中,什么时候用yield return"中,我们了解到:使用yield return返回集合,不是一次性加载到内
- 概述从今天开始, 小白我将带大家开启 Jave 数据结构 & 算法的新篇章.链表链表 (Linked List) 是一种递归的动态数
- Maven 的生命周期maven 将项目的生命周期(Lifecycle)抽象为了三种,每种生命周期中又包含了多个阶段(Phase)。也就是说