MyBatis 执行动态 SQL

MyBatis 执行动态 SQL应老婆要求写的这篇博客。大家基本上都知道如何使用MyBatis执行任意SQL,使用方法很简单,例如在一个XXMapper.xml中:${_parameter}你可以如下调用:sqlSession.selectList(“executeSql”,”select*fromsy

大家好,又见面了,我是你们的朋友全栈君。

应老婆要求写的这篇博客。

大家基本上都知道如何使用 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 后,复制一份,然后修改 resultMapsresultMaptype属性为你指定的class类型就能实现,说起来容易,实际操作起来能有 PageHelper 分页插件 1/10 左右的工作量。

因为这篇是应媳妇要求所写,所以假如媳妇有最后的这个需求,我就协助媳妇实现这个插件,然后再共享出来(这个插件已经实现,参考 MyBatis 返回动态结果类型插件 )。

注:如果是动态的update,insert,delete 语句,可以将上面的<select>改为<update>(不需要使用<delete><insert>),返回值用int,比 select 的情况容易很多。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/152496.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • java标识符是什么_Java 标识符「建议收藏」

    java标识符是什么_Java 标识符「建议收藏」什么是标识符标识符就是程序员在编写Java程序时,自定义的一些名字。例如第一个Java程序里关键字class后跟的HelloWorld,就是我们自定义的类名。类名就属于标识符的一种。标识符除了应用在类名上,还可以用在接口、变量、方法名、包名上。(要求同学们先记住,以后会详细见到这些)。标识符命名规则标识符由字母、下划线、美元符或数字组成标识符不能以数字开头,不能是关键字标识符严格区分大…

  • android terminal emulator_android自启动

    android terminal emulator_android自启动头文件:#includeprobe中//data->early_suspend.level=EARLY_SUSPEND_LEVEL_BLANK_SCREEN+1;data->early_suspend.suspend=gxx_suspend;data->early_suspend.resume=gxx_ts_resume;register_early_s

  • Python开源爬虫框架:Scrapy架构分析

    Python开源爬虫框架:Scrapy架构分析

  • stat函数百度百科_strel函数

    stat函数百度百科_strel函数stat函数的简单使用stat函数是用来获取文件的各种属性的一个linux下的常用API函数。函数原型为intstat(constchar*path,structstat*buf);stat定义如下:structstat{dev_tst_dev;//文件的设备编号ino_tst_ino;//节点mode_tst_m…

  • Django(69)最好用的过滤器插件Django-filter[通俗易懂]

    Django(69)最好用的过滤器插件Django-filter[通俗易懂]前言如果需要满足前端各种筛选条件查询,我们使用drf自带的会比较麻烦,比如查询书名中包含“国”字,日期大于“2020-1-1”等等诸如此类的请求,Django-filter这个组件就是要解决这样的问

  • 【Activity】Activity的启动模式:SingleTask

    【Activity】Activity的启动模式:SingleTask目录1.概述2.详细过程2.1启动MainActivity2.1.1生命周期2.1.2Activity栈2.2MainActivity中启动SecondActivity2.2.1生命周期2.2.2Activity栈2.3SecondActivity中启动MainActivity2.3.1生命周期2.3.2Activity栈2.4…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号