MybatisPlus字段自動填充失效,填充值為null的解決方案
問題描述
有一個實體類UserEntity
對其屬性 UserEntity#createTime
字段注解了 @TableField(fill = FieldFill.INSERT)
根據(jù)業(yè)務(wù)需要定義了 UserMapper#inserUser()
方法,參數(shù)注解了 @Param(“user”)
如下:
當調(diào)用該方法時,無法給 createTime 字段自動填充值,報錯信息如下:
### Cause: java.sql.SQLIntegrityConstraintViolationException: Column 'create_time' cannot be null<LF>; Column 'create_time' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'create_time' cannot be null]
問題剖析
在檢查了 MetaObjectHandler
實現(xiàn)類的重寫的方法無誤后,開始嘗試跟蹤 Mybatis-plus 的源碼。
發(fā)現(xiàn)在 MybatisParameterHandler#process()
中完成了自動填充的功能,在自動填充前需要先獲取 tableInfo
信息:
而這個 TableInfoHelper.getTableInfo()
方法只有當傳入的 Class
對象是實體類對象時才能獲取到 tableInfo
:
那么問題來了,我的參數(shù)確實是實體類,但是為什么獲取不到 TableInfo 呢?
因為 MybatisParameterHandler#process()
方法的 parameter
參數(shù)在調(diào)用我自定義的方法時,傳入的是一個 Map
,這個 Map
的 key
是一個字符串表示,而 value
是我自定義方法的參數(shù)實例,可以看到下圖中我的 Map
有兩個 Entry
一個 key=user
一個 key=param1
而最為重要的是,在這個 process()
方法中,如果傳入的是一個 Map
,Mybatis-plus 會從其中取 key="et"
的值,這就是問題的原因所在?。?!
而傳入的這個 Map 不存在 key="et"
的映射關(guān)系。因此兩個 TableInfoHelper.getTableInfo()
方法都進不去,所以也就不會進行自動填充。
那么如何建立 "et" -> entity
的映射關(guān)系呢?我們Map中原本的兩個的映射關(guān)系又是從哪里來的?
根據(jù)方法的調(diào)用鏈,一直回退到 Mybatis 框架中的 MapperMethod#execute()
方法中的一行代碼:
上面的 convertArgsToSqlCommandParam()
方法就是通過我們方法的實際參數(shù) args
轉(zhuǎn)換為執(zhí)行 sql 語句需要的參數(shù)格式,而返回值 param
就是之前傳入的那個 map
。
我們跟蹤該方法的調(diào)用鏈,發(fā)現(xiàn)最終調(diào)用了 ParamNameResolver#getNamedParams()
方法,該方法有3個分支,決定了我們最終得到參數(shù)是怎樣的,源碼如下:
public Object getNamedParams(Object[] args) { final int paramCount = names.size(); // 1. 方法是空參時直接返回 if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { // 2. 方法的參數(shù)沒有注解 @Param 并且只有一個參數(shù)時,直接返回這個參數(shù)實例 Object value = args[names.firstKey()]; return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null); } else { // 3. 否則,就建立映射關(guān)系(要么注解了 @Param,要么就是多個參數(shù)) final Map<String, Object> param = new ParamMap<>(); int i = 0; // 遍歷 names 中每一個 entry, 這個 names 是一個 sortedMap,該 Map 會保存方法參數(shù)的索引 -> 參數(shù)名稱的映射關(guān)系,如果參數(shù)注解了 @Param,則值時 @Param("xxx") 中的 xxx,如果沒有注解 @Param 則值也為參數(shù)索引,例如: // aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}} // aMethod(int a, int b) -> {{0, "0"}, {1, "1"}} for (Map.Entry<Integer, String> entry : names.entrySet()) { // key = 參數(shù)名稱(@Param("xxx"))或 參數(shù)的索引 // value = 參數(shù)實例 param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) // 下面就是添加 "paramN" -> 參數(shù)實例的映射,我們知道在 mapper 文件中可以使用 #{paramN} 來獲取參數(shù)列表的值,這就是原因。 final String genericParamName = GENERIC_NAME_PREFIX + (i + 1); // ensure not to overwrite parameter named with @Param if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
那么顯然,本次調(diào)用返回的 param 如下:
解決方法
通過上面的分析,我們就知道了為什么咱們傳給 MybatisParameterHandler#process()
的參數(shù)是一個 Map,并且也知道了為什么自動填充失敗的根本原因,那么解決方法也就很明確了:
給實體類參數(shù)注解為 @Param(“et”)
,修改后記得 Mapper 文件中占位符中也要改成 #{et.property}
:
或者方法只有一個實體類參數(shù)時就別標注 @Param
注解了,這樣返回的就是實體類的實例而不是一個 Map,同樣記得 Mapper 文件中占位符直接寫屬性 #{property}
即可。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
基于自定義校驗注解(controller、method、(groups)分組的使用)
這篇文章主要介紹了基于自定義校驗注解(controller、method、(groups)分組的使用),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10詳解Spring整合Quartz實現(xiàn)動態(tài)定時任務(wù)
本篇文章主要介紹了詳解Spring整合Quartz實現(xiàn)動態(tài)定時任務(wù),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-03-03Mybatis Plus select 實現(xiàn)只查詢部分字段
這篇文章主要介紹了Mybatis Plus select 實現(xiàn)只查詢部分字段的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09Java中ByteArrayInputStream和ByteArrayOutputStream用法詳解
這篇文章主要介紹了Java中ByteArrayInputStream和ByteArrayOutputStream用法詳解,?ByteArrayInputStream?的內(nèi)部額外的定義了一個計數(shù)器,它被用來跟蹤?read()?方法要讀取的下一個字節(jié)2022-06-06