源码解读 MyBatis List 列表 in 查询实现的注意事项

2022年 4月 5日 52点热度 0人点赞

在 SQL 开发过程中, 动态构建 in 集合条件查询是比较常见的用法, 在 Mybatis 中提供了 foreach 功能, 该功能比较强大, 它允许你指定一个集合, 声明集合项和索引变量, 它们可以用在元素体内. 它也允许你指定开放和关闭的字符串, 在迭代之间放置分隔符. 这个元素是很智能的, 它不会偶然地附加多余的分隔符. 下面是一个演示示例:

<select id="findByIdsMap" resultMap="BaseResultMap">
    SELECT
        <include refid="Base_Column_List" />
    FROM jria
    WHERE id IN
        <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
            #{item}
        </foreach>
 </select>

但由于官方文档对这块的使用, 描述的比较简短, 细节上也被忽略掉了 (可能是开源项目文档一贯的问题吧), 也使用不少同学在使用中遇到了问题. 特别是 foreach 这个函数中, collection 属性做什么用, 有什么注意事项. 由于文档不全, 这块只能通过源代码剖析的方式来分析一下各个属性的相关要求.

collection 属性的用途是接收输入的数组或是 List 接口实现. 但对于其名称的要求, Mybatis 在实现中还是有点不好理解的, 所以需要特别注意这一点.

下面开始分析源代码 (笔记使用的是 Mybatis 3.0.5 版本)

先找到 Mybatis 执行 SQL 配置解析的入口. MapperMethod.java 类中 public Object execute(Object[] args) 该方法是执行的入口. 针对 in 集合查询, 对应用就是 selectForListSelectForMap 方法.

file

但不管调用哪个方法, 都会对原来 JDK 传入的参数 Object[] 类型, 通过 getParam 方法转换成一个 Object, 那这个方法是做什么的呢? 分析源码如下:

file

上图中标红的两处, 很惊讶的发现, 一个参数与多个参数的处理方式是不同的 (后续很多同学遇到的问题, 就有一大部分出自这个地方). 如果参数个数大于一个, 则会被封装成 Map, key 值如果使用了 MyBatis 的 Param 注解, 则会使用该 key 值, 否则默认统一使用数据序号, 从 1 开始. 这个问题先记下, 继续分析代码, 接下来如果是 selectForList 操作 (其它操作就对应用相应方法), 会调用 DefaultSqlSessionpublic List selectList(String statement, Object parameter, RowBounds rowBounds) 方法

又一个发现, 见源代码如下:

file

上图标红部分, 对参数又做了一次封装, 我们看一下代码

file

现在有点清楚了, 如果参数类型是 List, 则必须在 collecion 中指定为 list, 如果是数组, 则必须在 collection 属性中指定为 array.

现在就问题就比较清楚了, 如果是一个参数的话, collection 的值取决于你的参数类型.

如果是多个值的话, 除非使用注解 Param 指定, 否则都是数字开头, 所以在 collection 中指定什么值都是无用的. 下图是 debug 显示结果.

file

针对上面分析的结果, 下面给出了一个使用的解决方案, 希望对大家对帮助.

在使用这个功能是需要特别注意以下规则.

当查询的参数只有一个时

findByIds(List<Long> ids)

如果参数的类型是 List, 则在使用时,collection 属性要必须指定为 list

<select id="findByIdsMap" resultMap="BaseResultMap">
    SELECT <include refid="Base_Column_List" />
    FROM jria
    WHERE id IN
        <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
            #{item}
        </foreach>
</select>

如果参数的类型是 Array, 比如 findByIds(Long[] ids), 则在使用时,collection 属性要必须指定为 array:

<select id="findByIdsMap" resultMap="BaseResultMap">
    SELECT <include refid="Base_Column_List" />
    FROM jria
    WHERE id IN
        <foreach item="item" index="index" collection="array" open="(" separator="," close=")">
            #{item}
        </foreach>
</select>

当查询的参数有多个时, 例如 findByIds(String name, Long[] ids)

这种情况需要特别注意, 在传参数时, 一定要改用 Map 方式, 这样在 collection 属性可以指定名称, 下面是一个示例

Map<String, Object> params = new HashMap<String, Object>(2);
params.put("name", name);
params.put("ids", ids);
mapper.findByIdsMap(params);
<select id="findByIdsMap" resultMap="BaseResultMap">
    SELECT <include refid="Base_Column_List" />
    FROM jria
    WHERE id IN
        <foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
            #{item}
        </foreach>
</select>

例如有一个查询功能, Mapper 接口文件定义如下方法:

List<Jria> findByIds(Long... ids);

使用 in 查询的 SQL 拼装方法如下:

<select id="findbyIds" resultMap="BaseResultMap">
    SELECT <include refid="Base_Column_List" />
    FROM jria
    WHERE id IN
        <foreach item="item" index="index" collection="array" open="(" separator="," close=")">
            #{item}
        </foreach>
</select>

评论

posted on 2011-08-31 14:38 x.matthew 阅读 (50256) 评论 (4)  编辑  收藏 所属分类: Spring|Hibernate|Other framework

这个博客发布于 2011 年, 那个时候就开始用 Java 和 MyBatis 写查询, 感觉是大佬了, 我应该还是在大二完了的那个暑假.

rainbow

这个人很懒,什么都没留下

文章评论