java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis/MyBatis Plus报错target is null

MyBatis/MyBatis-Plus报错:java.lang.NullPointerException: target is null for method size的解决方法

作者:李少兄

在使用 MyBatis 或 MyBatis-Plus 进行数据库操作时,开发者经常需要根据传入的参数动态构建 SQL 查询语句,然而,如果对集合参数未做充分的空值校验,就直接调用其方法,程序在运行时会抛出经典异常,所以本文给大家介绍了具体的解决方法,需要的朋友可以参考下

一、问题背景

在使用 MyBatisMyBatis-Plus 进行数据库操作时,开发者经常需要根据传入的参数动态构建 SQL 查询语句。例如,根据一组用户 ID(ids)进行批量查询。此时,通常会在 XML 映射文件中使用 <if> 标签配合 OGNL 表达式进行条件判断。

然而,如果对集合参数(如 List<Long> ids)未做充分的空值校验,就直接调用其方法(如 .size()),程序在运行时会抛出如下经典异常:

java.lang.NullPointerException: target is null for method size
    at org.apache.ibatis.ognl.OgnlRuntime.callMethod(OgnlRuntime.java:1620)
    at org.apache.ibatis.ognl.ASTMethod.getValueBody(ASTMethod.java:72)
    at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:171)
    at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:206)
    at org.apache.ibatis.ognl.ASTChain.getValueBody(ASTChain.java:128)
    at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:171)
    at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:206)
    at org.apache.ibatis.ognl.ASTGreater.getValueBody(ASTGreater.java:34)
    at org.apache.ibatis.ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:171)
    at org.apache.ibatis.ognl.SimpleNode.getValue(SimpleNode.java:206)
    at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:408)
    at org.apache.ibatis.ognl.Ognl.getValue(Ognl.java:383)
    at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:47)
    at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateBoolean(ExpressionEvaluator.java:32)
    at org.apache.ibatis.scripting.xmltags.IfSqlNode.apply(IfSqlNode.java:34)
    at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32)
    at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:39)
    at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:320)
    at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:69)
    at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:59)
    at jdk.proxy2/jdk.proxy2.$Proxy183.query(Unknown Source)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:154)
    ... 95 common frames omitted

关键报错信息

java.lang.NullPointerException: target is null for method size
at org.apache.ibatis.ognl.OgnlRuntime.callMethod

只要你在项目日志或控制台中看到上述堆栈信息,基本可以断定:你在 MyBatis 的 OGNL 表达式中直接调用了可能为 null 的集合对象(如 ids)的 .size() 方法

二、问题原因分析

2.1 OGNL 表达式执行机制简述

MyBatis 使用 OGNL(Object-Graph Navigation Language) 来解析 XML 中 <if test="..."> 等标签内的表达式。当你在 XML 中写:

<if test="ids.size() > 0">

MyBatis 会在运行时尝试执行 ids.size()。但如果传入的 ids 参数为 null,JVM 就无法在 null 引用上调用任何实例方法,从而抛出 NullPointerException

2.2 典型错误场景示例

假设你有一个根据用户 ID 列表查询用户的接口:

// Mapper 接口
List<User> selectByIds(@Param("ids") List<Long> ids);

对应的 XML 映射文件如下:

<!-- 错误写法 ❌ -->
<select id="selectByIds" resultType="User">
    SELECT * FROM user
    <where>
        <if test="ids.size() > 0">
            id IN
            <foreach collection="ids" item="id" open="(" separator="," close=")">
                #{id}
            </foreach>
        </if>
    </where>
</select>

当调用方传入 null 时:

userMapper.selectByIds(null); // ids = null

MyBatis 在解析 <if test="ids.size() > 0"> 时,会尝试调用 null.size(),立即触发 NPE。

注意:即使你认为“调用方不会传 null”,但在实际系统中,前端参数缺失、DTO 转换失败、单元测试遗漏等情况都可能导致 ids 为 null。因此,防御性编程是必须的

三、正确解决方案

方案一:在 OGNL 表达式中显式判空(最常用且推荐)

修改 XML 中的 <if> 条件,先判断 ids 不为 null,再判断其大小

<!-- 正确写法 ✅ -->
<if test="ids != null and ids.size() > 0">
    id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</if>

或者使用更语义化的 isEmpty() 方法(MyBatis 支持):

<if test="ids != null and !ids.isEmpty()">
    ...
</if>

为什么安全?
OGNL 表达式支持逻辑短路(short-circuit evaluation):

若 ids != null 为 false,则整个表达式直接为 false,不会继续执行 ids.size(),从而避免 NPE。

方案二:Java 层保证集合非 null(防御性初始化)

在业务层或 Controller 层,确保传给 Mapper 的集合不为 null

public List<User> getUsers(List<Long> ids) {
    if (ids == null) {
        ids = Collections.emptyList(); // 或 new ArrayList<>()
    }
    return userMapper.selectByIds(ids);
}

这样即使 XML 中写了 ids.size() > 0,也不会崩溃(因为 Collections.emptyList().size() 返回 0)。
仍建议配合方案一使用,以增强代码鲁棒性和可读性。

方案三:使用 MyBatis-Plus 的条件构造器(彻底规避 XML 风险)

如果你使用的是 MyBatis-Plus,强烈建议优先使用其内置的 条件构造器(Wrapper),它天然支持条件判断,且类型安全、可读性强。

MyBatis-Plus 的 QueryWrapper / LambdaQueryWrapper 方法通常提供一个 boolean 类型的第一个参数,用于控制该条件是否生效。例如:

public List<User> selectUsersByIds(List<Long> ids) {
    return lambdaQuery()
        .in(ids != null && !ids.isEmpty(), User::getId, ids)
        .list();
}

关键说明(来自 MyBatis-Plus 官方行为):

因此,正确的做法是在第一个参数中完成完整的判空逻辑

// ✅ 安全写法
lambdaQuery()
    .eq(name != null && !name.trim().isEmpty(), User::getName, name)
    .ge(age != null && age >= 0, User::getAge, age)
    .in(ids != null && !ids.isEmpty(), User::getId, ids)
    .list();

这种方式:

四、最佳实践总结

场景推荐做法
手写 MyBatis XML始终使用 ids != null and ids.size() > 0ids != null and !ids.isEmpty()
集合参数来源不可控(如 HTTP 请求)Java 层主动初始化:ids = Optional.ofNullable(ids).orElse(Collections.emptyList())
使用 MyBatis-Plus优先使用 LambdaQueryWrapper,并在条件方法的第一个参数中完成判空逻辑
团队编码规范禁止在 test 表达式中直接调用 .size().isEmpty() 等方法而不判空

五、扩展知识:MyBatis 对集合的“真值”处理

MyBatis 内部对集合类型有特殊处理规则:

一个非 null 且非空的集合在布尔上下文中被视为 true;否则为 false

因此,以下写法在某些情况下也能工作:

<if test="ids">
    <!-- 相当于 ids != null && ids.size() > 0 -->
</if>

但请注意:

结论:虽然 test="ids" 是合法的,但为了代码清晰、可维护、可移植,强烈建议显式写出 ids != null and !ids.isEmpty()

六、如何快速定位与修复

  1. 搜索关键字:在项目中全局搜索 .size().isEmpty() 出现在 XML 的 test= 属性中;
  2. 静态检查:可通过 SonarQube 或自定义 Checkstyle 规则禁止此类写法;
  3. 单元测试覆盖:编写测试用例,传入 null、空集合、正常集合三种情况,验证 SQL 生成正确性;
  4. 日志监控:在生产环境中监控 NullPointerException,若堆栈包含 OgnlRuntime.callMethod,优先排查动态 SQL。

七、附录:OGNL —— MyBatis 动态 SQL 的底层引擎

很多开发者在使用 MyBatis 时知道 <if test="xxx"> 可以写表达式,但并不清楚其背后的原理。

7.1 什么是 OGNL?

OGNL(Object-Graph Navigation Language)是一种开源的表达式语言,最初由 Drew Davidson 创建,现由 Apache Commons 维护(Apache Commons OGNL)。它允许你通过简洁的字符串表达式访问和操作 Java 对象图中的属性、方法、集合等。

MyBatis 从早期版本起就采用 OGNL 作为其动态 SQL 表达式的解析引擎(替代了早期的 JSTL 和自定义语法)。

7.2 OGNL 在 MyBatis 中的应用场景

在 MyBatis 的 XML 映射文件中,以下标签的 testcollection 等属性均使用 OGNL 表达式:

例如:

<if test="user.age > 18 and user.active == true">
<foreach collection="user.roles" item="role">
<bind name="likePattern" value="'%' + keyword + '%'" />

这些表达式都会被 MyBatis 交给 OGNL 引擎在运行时求值。

7.3 OGNL 表达式语法要点

表达式说明
param访问参数对象的属性(如 @Param("user") User useruser.name
param != null判空
list.size()调用集合的 size() 方法(⚠️ 若 list 为 null 会抛 NPE)
list.isEmpty()调用 isEmpty()(同样需先判空)
map['key']map.key访问 Map
@java.lang.Math@max(a, b)调用静态方法(需开启 static method access)
true and false逻辑运算(支持 and/or/not,也支持 &&/`
a > b ? 'yes' : 'no'三元运算符

重要限制:出于安全考虑,MyBatis 默认禁用 OGNL 的静态方法调用和构造函数调用,防止表达式注入攻击。

7.4 OGNL 的求值上下文(Context)

MyBatis 在执行 OGNL 表达式时,会构建一个特殊的上下文对象(ContextMap),其中包含:

例如:

void update(@Param("id") Long id, @Param("name") String name);

在 XML 中可直接使用 idname,因为它们已被注入到 OGNL 上下文中。

7.5 为什么ids.size()会报 NPE?

OGNL 在执行 ids.size() 时,会:

  1. 从上下文中获取 ids 变量;
  2. ids == null,则 target = null
  3. 尝试调用 target.size() → 即 null.size() → JVM 抛出 NullPointerException
  4. MyBatis 捕获该异常并包装为带有明确提示的错误信息:target is null for method size

这本质上是一个 Java 层面的方法调用失败,而非 OGNL 本身的 bug。

7.6 如何安全使用 OGNL?

7.7 OGNL 与 SpEL 的区别

有些开发者会混淆 OGNL 和 Spring 的 SpEL(Spring Expression Language)。两者都是表达式语言,但:

特性OGNLSpEL
所属生态Apache / MyBatisSpring Framework
主要用途MyBatis 动态 SQLSpring 配置、注解、Thymeleaf 等
静态方法调用默认禁用(MyBatis)支持(T(java.lang.Math).random()
安全性较高(受限)需注意表达式注入

记住:MyBatis 不使用 SpEL,只使用 OGNL。

以上就是MyBatis/MyBatis-Plus报错:java.lang.NullPointerException: target is null for method size的解决方法的详细内容,更多关于MyBatis/MyBatis Plus报错target is null的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文