MyBatis/MyBatis-Plus報錯:java.lang.NullPointerException: target is null for method size的解決方法
一、問題背景
在使用 MyBatis 或 MyBatis-Plus 進行數(shù)據(jù)庫操作時,開發(fā)者經(jīng)常需要根據(jù)傳入的參數(shù)動態(tài)構(gòu)建 SQL 查詢語句。例如,根據(jù)一組用戶 ID(ids)進行批量查詢。此時,通常會在 XML 映射文件中使用 <if> 標簽配合 OGNL 表達式進行條件判斷。
然而,如果對集合參數(shù)(如 List<Long> ids)未做充分的空值校驗,就直接調(diào)用其方法(如 .size()),程序在運行時會拋出如下經(jīng)典異常:
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
? 關(guān)鍵報錯信息:
java.lang.NullPointerException: target is null for method size at org.apache.ibatis.ognl.OgnlRuntime.callMethod
只要你在項目日志或控制臺中看到上述堆棧信息,基本可以斷定:你在 MyBatis 的 OGNL 表達式中直接調(diào)用了可能為 null 的集合對象(如 ids)的 .size() 方法。
二、問題原因分析
2.1 OGNL 表達式執(zhí)行機制簡述
MyBatis 使用 OGNL(Object-Graph Navigation Language) 來解析 XML 中 <if test="..."> 等標簽內(nèi)的表達式。當你在 XML 中寫:
<if test="ids.size() > 0">
MyBatis 會在運行時嘗試執(zhí)行 ids.size()。但如果傳入的 ids 參數(shù)為 null,JVM 就無法在 null 引用上調(diào)用任何實例方法,從而拋出 NullPointerException。
2.2 典型錯誤場景示例
假設(shè)你有一個根據(jù)用戶 ID 列表查詢用戶的接口:
// Mapper 接口
List<User> selectByIds(@Param("ids") List<Long> ids);
對應(yīng)的 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>
當調(diào)用方傳入 null 時:
userMapper.selectByIds(null); // ids = null
MyBatis 在解析 <if test="ids.size() > 0"> 時,會嘗試調(diào)用 null.size(),立即觸發(fā) NPE。
注意:即使你認為“調(diào)用方不會傳 null”,但在實際系統(tǒng)中,前端參數(shù)缺失、DTO 轉(zhuǎn)換失敗、單元測試遺漏等情況都可能導致 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,不會繼續(xù)執(zhí)行 ids.size(),從而避免 NPE。
方案二:Java 層保證集合非 null(防御性初始化)
在業(yè)務(wù)層或 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 的條件構(gòu)造器(徹底規(guī)避 XML 風險)
如果你使用的是 MyBatis-Plus,強烈建議優(yōu)先使用其內(nèi)置的 條件構(gòu)造器(Wrapper),它天然支持條件判斷,且類型安全、可讀性強。
MyBatis-Plus 的 QueryWrapper / LambdaQueryWrapper 方法通常提供一個 boolean 類型的第一個參數(shù),用于控制該條件是否生效。例如:
public List<User> selectUsersByIds(List<Long> ids) {
return lambdaQuery()
.in(ids != null && !ids.isEmpty(), User::getId, ids)
.list();
}
關(guān)鍵說明(來自 MyBatis-Plus 官方行為):
in(boolean condition, SFunction<T, ?> column, Collection<?> values)- 當
condition為true時,才將IN條件加入 SQL;否則忽略。 - 如果省略
condition參數(shù)(即只傳列和值),則默認condition = true,條件總是生效。
因此,正確的做法是在第一個參數(shù)中完成完整的判空邏輯:
// ? 安全寫法
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();
這種方式:
- 無需編寫 XML;
- 避免 OGNL 表達式陷阱;
- 編譯期即可發(fā)現(xiàn)類型錯誤;
- 邏輯清晰,易于維護。
四、最佳實踐總結(jié)
| 場景 | 推薦做法 |
|---|---|
| 手寫 MyBatis XML | 始終使用 ids != null and ids.size() > 0 或 ids != null and !ids.isEmpty() |
| 集合參數(shù)來源不可控(如 HTTP 請求) | Java 層主動初始化:ids = Optional.ofNullable(ids).orElse(Collections.emptyList()) |
| 使用 MyBatis-Plus | 優(yōu)先使用 LambdaQueryWrapper,并在條件方法的第一個參數(shù)中完成判空邏輯 |
| 團隊編碼規(guī)范 | 禁止在 test 表達式中直接調(diào)用 .size()、.isEmpty() 等方法而不判空 |
五、擴展知識:MyBatis 對集合的“真值”處理
MyBatis 內(nèi)部對集合類型有特殊處理規(guī)則:
一個非 null 且非空的集合在布爾上下文中被視為 true;否則為 false。
因此,以下寫法在某些情況下也能工作:
<if test="ids">
<!-- 相當于 ids != null && ids.size() > 0 -->
</if>
但請注意:
- 該行為依賴于 MyBatis 內(nèi)部的
OgnlOps.booleanValue()實現(xiàn); - 在復雜嵌套表達式或自定義類型中可能表現(xiàn)不一致;
- 可讀性較差,新成員可能不理解其含義。
? 結(jié)論:雖然 test="ids" 是合法的,但為了代碼清晰、可維護、可移植,強烈建議顯式寫出 ids != null and !ids.isEmpty()。
六、如何快速定位與修復
- 搜索關(guān)鍵字:在項目中全局搜索
.size()、.isEmpty()出現(xiàn)在 XML 的test=屬性中; - 靜態(tài)檢查:可通過 SonarQube 或自定義 Checkstyle 規(guī)則禁止此類寫法;
- 單元測試覆蓋:編寫測試用例,傳入
null、空集合、正常集合三種情況,驗證 SQL 生成正確性; - 日志監(jiān)控:在生產(chǎn)環(huán)境中監(jiān)控
NullPointerException,若堆棧包含OgnlRuntime.callMethod,優(yōu)先排查動態(tài) SQL。
七、附錄:OGNL —— MyBatis 動態(tài) SQL 的底層引擎
很多開發(fā)者在使用 MyBatis 時知道 <if test="xxx"> 可以寫表達式,但并不清楚其背后的原理。
7.1 什么是 OGNL?
OGNL(Object-Graph Navigation Language)是一種開源的表達式語言,最初由 Drew Davidson 創(chuàng)建,現(xiàn)由 Apache Commons 維護(Apache Commons OGNL)。它允許你通過簡潔的字符串表達式訪問和操作 Java 對象圖中的屬性、方法、集合等。
MyBatis 從早期版本起就采用 OGNL 作為其動態(tài) SQL 表達式的解析引擎(替代了早期的 JSTL 和自定義語法)。
7.2 OGNL 在 MyBatis 中的應(yīng)用場景
在 MyBatis 的 XML 映射文件中,以下標簽的 test 或 collection 等屬性均使用 OGNL 表達式:
<if test="..."><when test="..."><choose>/<when>/<otherwise><foreach collection="..."><bind name="..." value="..."/>
例如:
<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 | 訪問參數(shù)對象的屬性(如 @Param("user") User user → user.name) |
param != null | 判空 |
list.size() | 調(diào)用集合的 size() 方法(?? 若 list 為 null 會拋 NPE) |
list.isEmpty() | 調(diào)用 isEmpty()(同樣需先判空) |
map['key'] 或 map.key | 訪問 Map |
@java.lang.Math@max(a, b) | 調(diào)用靜態(tài)方法(需開啟 static method access) |
true and false | 邏輯運算(支持 and/or/not,也支持 &&/` |
a > b ? 'yes' : 'no' | 三元運算符 |
重要限制:出于安全考慮,MyBatis 默認禁用 OGNL 的靜態(tài)方法調(diào)用和構(gòu)造函數(shù)調(diào)用,防止表達式注入攻擊。
7.4 OGNL 的求值上下文(Context)
MyBatis 在執(zhí)行 OGNL 表達式時,會構(gòu)建一個特殊的上下文對象(ContextMap),其中包含:
- 所有通過
@Param注解命名的參數(shù); - 一些內(nèi)置變量,如
_parameter(整個參數(shù)對象)、_databaseId等。
例如:
void update(@Param("id") Long id, @Param("name") String name);
在 XML 中可直接使用 id 和 name,因為它們已被注入到 OGNL 上下文中。
7.5 為什么ids.size()會報 NPE?
OGNL 在執(zhí)行 ids.size() 時,會:
- 從上下文中獲取
ids變量; - 若
ids == null,則target = null; - 嘗試調(diào)用
target.size()→ 即null.size()→ JVM 拋出NullPointerException; - MyBatis 捕獲該異常并包裝為帶有明確提示的錯誤信息:
target is null for method size。
這本質(zhì)上是一個 Java 層面的方法調(diào)用失敗,而非 OGNL 本身的 bug。
7.6 如何安全使用 OGNL?
- 永遠先判空再調(diào)方法:
obj != null and obj.method() - 避免復雜邏輯:不要在 OGNL 中寫業(yè)務(wù)邏輯,應(yīng)移至 Java 層;
- 慎用靜態(tài)方法:除非明確配置允許;
- 理解短路求值:
a != null and a.b()是安全的,因為a == null時不會執(zhí)行a.b()。
7.7 OGNL 與 SpEL 的區(qū)別
有些開發(fā)者會混淆 OGNL 和 Spring 的 SpEL(Spring Expression Language)。兩者都是表達式語言,但:
| 特性 | OGNL | SpEL |
|---|---|---|
| 所屬生態(tài) | Apache / MyBatis | Spring Framework |
| 主要用途 | MyBatis 動態(tài) SQL | Spring 配置、注解、Thymeleaf 等 |
| 靜態(tài)方法調(diào)用 | 默認禁用(MyBatis) | 支持(T(java.lang.Math).random()) |
| 安全性 | 較高(受限) | 需注意表達式注入 |
記住:MyBatis 不使用 SpEL,只使用 OGNL。
以上就是MyBatis/MyBatis-Plus報錯:java.lang.NullPointerException: target is null for method size的解決方法的詳細內(nèi)容,更多關(guān)于MyBatis/MyBatis Plus報錯target is null的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot+MyBatis簡單數(shù)據(jù)訪問應(yīng)用的實例代碼
這篇文章主要介紹了SpringBoot+MyBatis簡單數(shù)據(jù)訪問應(yīng)用的實例代碼,需要的朋友可以參考下2017-05-05
SpringBoot+MQTT+apollo實現(xiàn)訂閱發(fā)布功能的示例
這篇文章主要介紹了SpringBoot+MQTT+apollo實現(xiàn)訂閱發(fā)布功能的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06

