MyBatis动态SQL避坑:为什么List用`[0]`而不是`get(0)`

在Java后端开发中,MyBatis的动态SQL是日常高频使用的功能,但很多开发者都会在集合参数(List/数组/Map)的取值上踩坑。比如最近有同学遇到这样的问题:

List<LocalDateTime>作为时间范围参数,在XML里写#{submitDate.get(0)}时直接报错,换成#{submitDate[0]}反而正常了。更奇怪的是,之前用LocalDateTime[]数组时还触发了TypeHandler异常。

这背后其实是MyBatis动态SQL的核心语法------OGNL表达式在起作用。本文将从实际报错场景出发,彻底讲清楚OGNL与Java原生语法的区别,以及集合参数在动态SQL中的正确姿势。

一、场景还原:从一个TypeHandler报错说起

先看一段真实的业务代码:

java 复制代码
// Mapper接口(原错误写法:用数组作为参数)
List<RepairOrder> queryOrders(
    @Param("submitDate") LocalDateTime[] submitDate,
    @Param("repairDate") LocalDateTime[] repairDate
);

对应的XML映射文件:

xml 复制代码
<if test="submitDate != null">
    AND b.submit_date >= #{submitDate[0]} 
    AND b.submit_date <= #{submitDate[1]}
</if>
<if test="repairDate != null">
    AND b.repair_date >= #{repairDate[0]} 
    AND b.submit_date <= #{repairDate[1]} <!-- 此处还存在笔误 -->
</if>

运行后直接抛出异常:

复制代码
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.lang.IllegalStateException: Type handler was null on parameter mapping for property 'repairDate[0]'. It was either not specified and/or could not be found for the javaType ([Ljava.time.LocalDateTime;) : jdbcType (null) combination.

问题拆解

  1. 表层问题 :MyBatis默认不支持LocalDateTime[]数组类型的TypeHandler,导致参数解析失败。
  2. 深层问题:开发者混淆了MyBatis的OGNL语法与Java原生语法,同时对集合参数的处理逻辑不清晰。
  3. 额外笔误repairDate的结束条件错误关联到submit_date字段,导致业务逻辑错误。

二、核心原理:MyBatis的OGNL表达式不是Java语法

MyBatis的动态SQL(<if>/#{}/${}等)底层依赖**OGNL(Object-Graph Navigation Language)**表达式引擎,而非原生Java代码。OGNL为了简化对象与集合的访问,设计了一套更简洁的语法规则,这也是很多开发者踩坑的根源。

关键区别:OGNL vs 原生Java语法

操作场景 原生Java语法 MyBatis OGNL语法(推荐) 底层解析逻辑
List元素取值 list.get(0) list[0] 自动调用list.get(0)
数组元素取值 array[0] array[0] 原生数组下标访问
Map元素取值 map.get("key") map.keymap["key"] 自动调用map.get("key")
对象方法调用 user.getName() user.nameuser.getName() 优先调用getter方法,无getter则直接访问字段

为什么List不能用get(0)

OGNL虽然支持调用对象方法(如list.size()),但在#{}参数占位符中,submitDate.get(0)会被MyBatis解析为参数名的一部分,而非方法调用。例如:

xml 复制代码
<!-- 错误写法:MyBatis会认为参数名是"submitDate.get(0)" -->
AND b.submit_date >= #{submitDate.get(0)}

运行后会抛出参数找不到的异常:

复制代码
Could not find parameter 'submitDate.get(0)' in parameter map

#{submitDate[0]}是OGNL专门为集合设计的语法糖,MyBatis会自动识别submitDate是List类型,并调用get(0)方法获取元素,这才是合法的取值方式。

三、实战验证:List/数组在动态SQL中的正确姿势

方案1:优先用List替代数组(推荐)

MyBatis对List的支持远好于数组,无需额外配置TypeHandler,只需修改Mapper接口的参数类型:

java 复制代码
// 修正后的Mapper接口(数组→List)
List<RepairOrder> queryOrders(
    @Param("submitDate") List<LocalDateTime> submitDate,
    @Param("repairDate") List<LocalDateTime> repairDate
);

对应的XML映射文件(含笔误修复):

xml 复制代码
<!-- 正确写法:用OGNL的[]语法访问List元素 -->
<if test="submitDate != null and submitDate.size() > 1">
    AND b.submit_date >= #{submitDate[0]} 
    AND b.submit_date <= #{submitDate[1]}
</if>
<if test="repairDate != null and repairDate.size() > 1">
    AND b.repair_date >= #{repairDate[0]} 
    AND b.repair_date <= #{repairDate[1]} <!-- 修复笔误:submit_date→repair_date -->
</if>
  • 关键优化 :新增submitDate.size() > 1判断,避免List元素不足时出现下标越界。
  • 运行效果 :MyBatis自动解析submitDate[0]submitDate.get(0),无需额外TypeHandler,参数解析正常。

方案2:必须用数组时的兼容处理

如果业务场景强制要求用数组(如老系统兼容),需自定义TypeHandler来支持LocalDateTime[]类型:

java 复制代码
@MappedTypes(LocalDateTime[].class)
@MappedJdbcTypes(JdbcType.TIMESTAMP)
public class LocalDateTimeArrayTypeHandler extends BaseTypeHandler<LocalDateTime[]> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime[] parameter, JdbcType jdbcType) throws SQLException {
        if (parameter.length > 0) {
            ps.setObject(i, parameter[0]); // 适配数组下标访问
        }
    }
    // 省略其他方法...
}

注册TypeHandler后,在XML中指定类型处理器:

xml 复制代码
<if test="submitDate != null">
    AND b.submit_date >= #{submitDate[0],typeHandler=com.xxx.handler.LocalDateTimeArrayTypeHandler}
</if>

四、避坑指南:OGNL表达式的常见误区

1. 误区:判断List非空只用list != null

正确写法需同时判断size() > 0,避免空List导致下标越界:

xml 复制代码
<!-- 错误:空List会导致submitDate[0]报错 -->
<if test="submitDate != null">

<!-- 正确:确保List有元素 -->
<if test="submitDate != null and submitDate.size() > 0">

2. 误区:Map取值用map.get("key")

推荐用更简洁的map.keymap["key"]

xml 复制代码
<!-- 推荐写法 -->
#{params.startTime}
#{params["startTime"]}

<!-- 不推荐写法 -->
#{params.get("startTime")}

3. 误区:对象属性访问用getter方法

OGNL优先调用getter方法,但直接用属性名更简洁:

xml 复制代码
<!-- 推荐写法 -->
#{user.name}

<!-- 等价写法(不推荐) -->
#{user.getName()}

五、总结

MyBatis动态SQL的坑,本质上是OGNL语法与Java原生语法的差异导致的。记住这几个核心结论:

  1. 集合取值用[] :List/数组统一用list[0],Map用map.keymap["key"]
  2. 优先用List:MyBatis对List的支持更完善,避免数组的TypeHandler问题。
  3. 判断非空要严谨 :List需同时判断!= nullsize() > 0,防止下标越界。
  4. 避免语法混淆 :OGNL不是Java,不要在#{}中写get()方法。
相关推荐
独断万古他化6 分钟前
【SSM开发实战:博客系统】(三)核心业务功能开发与安全加密实现
spring boot·spring·mybatis·博客系统·加密
怣501 小时前
MySQL多表连接:全外连接、交叉连接与结果集合并详解
数据库·sql
证榜样呀3 小时前
2026 中专大数据技术专业可考的证书有哪些,必看!
大数据·sql
Codefengfeng3 小时前
数据安全知识点速通
sql
fengxin_rou4 小时前
[Redis从零到精通|第四篇]:缓存穿透、雪崩、击穿
java·redis·缓存·mybatis·idea·多线程
田野追逐星光4 小时前
STL容器list的模拟实现
开发语言·c++·list
逍遥德5 小时前
Sring事务详解之02.如何使用编程式事务?
java·服务器·数据库·后端·sql·spring
驾数者5 小时前
Flink SQL实时数仓实战:基于Flink SQL的完整项目案例
sql·flink·linq
老毛肚14 小时前
MyBatis插件原理及Spring集成
java·spring·mybatis