带你重新认识MyBatis的foreach

 更新时间:2022年11月01日 08:27:09   作者:BillySir  
这篇文章主要介绍了重新认识MyBatis的foreach,本文提出了一种简化<foreach>写法的设想,更重要的是通过解决空集时生成的SQL语法问题,更深刻地理解MyBatis的foreach的生成机制,需要的朋友可以参考下

用了MyBatis的同行,应该见过foreach,它一般是这样用的:

<select id="foreachTest" resultType="Blog">
    select * from t_blog where id in
    <foreach collection="list" index="index" item="item" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

难记

有没有人跟我一样,觉得

<foreach collection="list" index="index" item="item" open="(" separator="," close=")">

这一串很啰嗦?每次写<foreach>都因为记不住和怕记不牢,要查找和复制现有的来改。假如可以简化,就不至于要这样了。
一项项来分析:

  • foreach这个是xml标签,少不了。
  • collection可以通过探测参数类型+数量,从而在大多数情况下,知道对应哪个参数而省略。
  • index这个就更加不用写了,幸好它真的是用时才写,不用不写。
  • item假如缺省值就是item,则可能会有一些冲突,比如某个表名叫item,会不会让人掉坑里才豁然知道缘由?想不清楚。
  • open难道不是通常都是"("吗?但只要不写open,缺省值是没有,即是空串,很无语,强迫我们几乎每个foreach都要写open="("
  • separator难道不是绝大多数都是","吗?缺省值也是空串,更无语
  • closeopen同理。

空集合问题

还有一个问题,当传入的集合是空集的时候,比如上面这个例子,某些情况计算出来的list是空集合。按道理,既然list是id的集合,空集合意味着应该select到0条记录。但结果却是报SQL语法错误。原因是生成的SQL长这样:

select * from t_blog where id in ;      -- 为了明显,我补了个分号

是的,按SQL语法,in后面的小括号是不能没有内容的,小括号本身也是不能省的,抓狂。

在以前,我接触的项目有DAO层(指java interface mapper之上的一层),我会在DAO层先判断,若list是Empty,直接返回空集,否则再执行mybatis的查询。后来的一些项目,推崇简单化,没有DAO层,Service直接调用java interface mapper。把这个“判空返回空”的逻辑写在Service就显得别扭。难道MyBatis就不能优雅地解决这个问题吗?

解法

直到后来看到了这种写法:

<select id="foreachTest" resultType="Blog">
    select * from t_blog
  <where>
    <foreach collection="list" index="index" item="item" open=" id in (" separator="," close=")">
        #{item}
    </foreach>
  </where>
</select>

特别之处在于open="id in (",与一开始的id in写在foreach外面有什么不同呢?经过实验,结论是这样的:

  • 当list非空时,两者并无区别。
  • 当list为空时,既不生成open值,也不生成close值!

所以,当list为空时,SQL就是:select * from t_blog,也就没有违反语法。但是查到的是全部的记录,而不是预期的“0条记录”。
改进为这样写

<select id="foreachTest" resultType="Blog">
    select * from t_blog
  <where>
    1 = 0 
    <foreach collection="list" index="index" item="item" open=" or id in (" separator="," close=")">
        #{item}
    </foreach>
  </where>
</select>

如果,还有其它的and条件,则需要在or的两边加上小括号。

<select id="foreachTest" resultType="Blog">
    select * from t_blog
  <where>
    (1 = 0 
    <foreach collection="list" index="index" item="item" open=" or id in (" separator="," close=")">
        #{item}
    </foreach>
    )
    and ...
  </where>
</select>

这样,不论list是否empty,都会生成正确语法和功能的SQL语句。有兴趣的朋友可自行推导。

优雅的解法

但我觉得上面的写法不够优雅,经过实验,找到一种我认为更优雅的写法:

<select id="foreachTest" resultType="Blog">
    select * from t_blog
  <where>
    <foreach collection="list" index="index" item="item" open=" and id in (" close="-1)">
        #{item},
    </foreach>
    and ...
  </where>
</select>

留意:

  • separator没了,缺省就是空串
  • #{item}后面有了个逗号(,)
  • close里多了个-1

是这样的思路,我们要解决的是in后面是空的问题,假如在list为empty时,也能“塞”入一个不存在的值,比如这里规定id不可能是-1,那么生成的sql就是where id in (-1),语法没错,且查不到数据。而当list非空时,生成的sql如where id in (1,2,3,-1),虽然有-1,但不影响查出来的结果。只是本来3是最后一个,不需要加逗号,由于后面固定有-1,所以每个#{item}后面固定要有逗号,如果逗号放在separator,则只出现在各个id之间,放在#{item}就可以出现在每个item后了。

通过此案例,回头一看,思路一下子就打开了。open里的写法可以很灵活,只要符合SQL语法的,理论上都可以往open里放。separatorclose也一样。可能作者就是因为这些灵活的用法,所以干脆让它们的缺省值是空串。这本无标准,所以不能说这样做就是对或错的。

一种简化<foreach>的设想

但如果允许我修改设计的话,我会让open的缺省值为"("separator的缺省值为", "close的缺省值为")"。因为大多数情况下它们的值就是这样。假如特殊情况下,就想要它的值为空串,可以这么写open="",何尝不可。这样做的好处是,通常情况下foreach会很简洁:

<foreach collection="list" item="item">
    #{item}
</foreach>

例如,像文章开头分析的那样,还可以去掉collection,甚至是item,那成为这样的极简:

<foreach>
    #{item}
</foreach>

整体这样:

<select id="foreachTest" resultType="Blog">
    select * from t_blog where id in
    <foreach>
        #{item}
    </foreach>
</select>

简化成这样,谁还不敢直接写<foreach>了?

总结

本文提出了一种简化<foreach>写法的设想,更重要的是通过解决空集时生成的SQL语法问题,更深刻地理解MyBatis的foreach的生成机制。打开思路,更灵活地利用openseparatorclose,得到符合预期的SQL,还兼顾到代码的优雅。

到此这篇关于重新认识MyBatis的foreach的文章就介绍到这了,更多相关MyBatis的foreach内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java使用Jasypt进行加密和解密的技术指南

    Java使用Jasypt进行加密和解密的技术指南

    Jasypt (Java Simplified Encryption) 是一个简化 Java 应用中加密工作的库,它支持加密和解密操作,易于与 Spring Boot 集成,通过 Jasypt,可以安全地管理敏感信息,比如数据库密码、API 密钥等,本文介绍了Java使用Jasypt进行加密和解密的技术指南,需要的朋友可以参考下
    2025-03-03
  • springboot2.x集成swagger的方法示例

    springboot2.x集成swagger的方法示例

    这篇文章主要介绍了springboot2.x集成swagger的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • Java基于迭代器模式实现的访问人员列表操作示例

    Java基于迭代器模式实现的访问人员列表操作示例

    这篇文章主要介绍了Java基于迭代器模式实现的访问人员列表操作,简单描述了迭代器模式的概念、原理以及使用迭代器模式实现访问人员列表的相关操作技巧,需要的朋友可以参考下
    2018-05-05
  • SpringBoot3集成ElasticSearch的方法详解

    SpringBoot3集成ElasticSearch的方法详解

    Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎,适用于各种数据类型,数字、文本、地理位置、结构化数据、非结构化数据,本文给大家详解介绍了SpringBoot3集成ElasticSearch的方法,需要的朋友可以参考下
    2023-08-08
  • Spring Boot简介与快速搭建详细步骤

    Spring Boot简介与快速搭建详细步骤

    SpringBoot其本身没有添加什么新的技术,就是整合了一些现有的框架,并提供了一些默认的配置,就是这些默认的配置,极大的提高了我们的开发效率。这篇文章主要介绍了Spring Boot简介与快速搭建,需要的朋友可以参考下
    2021-05-05
  • SpringBoot向容器注册bean的方法详解

    SpringBoot向容器注册bean的方法详解

    这篇文章主要利用示例为大家详细介绍了SpringBoot如何向容器注册bean(即:将对象加入容器)的四种方法,文中的示例代码讲解详细,需要的可以参考一下
    2022-05-05
  • java中将一个List等分成n个list的工具方法(推荐)

    java中将一个List等分成n个list的工具方法(推荐)

    下面小编就为大家带来一篇java中将一个List等分成n个list的工具方法(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • Java抽象类和普通类区别、 数组跟List的区别解析

    Java抽象类和普通类区别、 数组跟List的区别解析

    这篇文章主要介绍了Java抽象类和普通类区别、 数组跟List的区别,在这里需要注意List是一个接口,不能直接实例化,需要使用具体的实现类来创建对象,本文结合示例代码介绍的非常详细,需要的朋友参考下吧
    2023-09-09
  • SpringBoot日程管理Quartz与定时任务Task实现详解

    SpringBoot日程管理Quartz与定时任务Task实现详解

    定时任务是企业级开发中必不可少的组成部分,诸如长周期业务数据的计算,例如年度报表,诸如系统脏数据的处理,再比如系统性能监控报告,还有抢购类活动的商品上架,这些都离不开定时任务。本节将介绍两种不同的定时任务技术
    2022-09-09
  • java.lang.IllegalStateException异常原因和解决办法

    java.lang.IllegalStateException异常原因和解决办法

    这篇文章主要给大家介绍了关于java.lang.IllegalStateException异常原因和解决办法,IllegalStateException是Java标准库中的一个异常类,通常表示在不合适或无效的情况下执行了某个方法或操作,需要的朋友可以参考下
    2023-07-07

最新评论