MybatisPlus字段自動(dòng)填充失效,填充值為null的解決方案
問題描述
有一個(gè)實(shí)體類UserEntity
對(duì)其屬性 UserEntity#createTime
字段注解了 @TableField(fill = FieldFill.INSERT)
根據(jù)業(yè)務(wù)需要定義了 UserMapper#inserUser()
方法,參數(shù)注解了 @Param(“user”)
如下:
當(dāng)調(diào)用該方法時(shí),無法給 createTime 字段自動(dòng)填充值,報(bào)錯(cuò)信息如下:
### 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
實(shí)現(xiàn)類的重寫的方法無誤后,開始嘗試跟蹤 Mybatis-plus 的源碼。
發(fā)現(xiàn)在 MybatisParameterHandler#process()
中完成了自動(dòng)填充的功能,在自動(dòng)填充前需要先獲取 tableInfo
信息:
而這個(gè) TableInfoHelper.getTableInfo()
方法只有當(dāng)傳入的 Class
對(duì)象是實(shí)體類對(duì)象時(shí)才能獲取到 tableInfo
:
那么問題來了,我的參數(shù)確實(shí)是實(shí)體類,但是為什么獲取不到 TableInfo 呢?
因?yàn)?MybatisParameterHandler#process()
方法的 parameter
參數(shù)在調(diào)用我自定義的方法時(shí),傳入的是一個(gè) Map
,這個(gè) Map
的 key
是一個(gè)字符串表示,而 value
是我自定義方法的參數(shù)實(shí)例,可以看到下圖中我的 Map
有兩個(gè) Entry
一個(gè) key=user
一個(gè) key=param1
而最為重要的是,在這個(gè) process()
方法中,如果傳入的是一個(gè) Map
,Mybatis-plus 會(huì)從其中取 key="et"
的值,這就是問題的原因所在!?。?/p>
而傳入的這個(gè) Map 不存在 key="et"
的映射關(guān)系。因此兩個(gè) TableInfoHelper.getTableInfo()
方法都進(jìn)不去,所以也就不會(huì)進(jìn)行自動(dòng)填充。
那么如何建立 "et" -> entity
的映射關(guān)系呢?我們Map中原本的兩個(gè)的映射關(guān)系又是從哪里來的?
根據(jù)方法的調(diào)用鏈,一直回退到 Mybatis 框架中的 MapperMethod#execute()
方法中的一行代碼:
上面的 convertArgsToSqlCommandParam()
方法就是通過我們方法的實(shí)際參數(shù) args
轉(zhuǎn)換為執(zhí)行 sql 語句需要的參數(shù)格式,而返回值 param
就是之前傳入的那個(gè) map
。
我們跟蹤該方法的調(diào)用鏈,發(fā)現(xiàn)最終調(diào)用了 ParamNameResolver#getNamedParams()
方法,該方法有3個(gè)分支,決定了我們最終得到參數(shù)是怎樣的,源碼如下:
public Object getNamedParams(Object[] args) { final int paramCount = names.size(); // 1. 方法是空參時(shí)直接返回 if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { // 2. 方法的參數(shù)沒有注解 @Param 并且只有一個(gè)參數(shù)時(shí),直接返回這個(gè)參數(shù)實(shí)例 Object value = args[names.firstKey()]; return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null); } else { // 3. 否則,就建立映射關(guān)系(要么注解了 @Param,要么就是多個(gè)參數(shù)) final Map<String, Object> param = new ParamMap<>(); int i = 0; // 遍歷 names 中每一個(gè) entry, 這個(gè) names 是一個(gè) sortedMap,該 Map 會(huì)保存方法參數(shù)的索引 -> 參數(shù)名稱的映射關(guān)系,如果參數(shù)注解了 @Param,則值時(shí) @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ù)實(shí)例 param.put(entry.getValue(), args[entry.getKey()]); // add generic param names (param1, param2, ...) // 下面就是添加 "paramN" -> 參數(shù)實(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ù)是一個(gè) Map,并且也知道了為什么自動(dòng)填充失敗的根本原因,那么解決方法也就很明確了:
給實(shí)體類參數(shù)注解為 @Param(“et”)
,修改后記得 Mapper 文件中占位符中也要改成 #{et.property}
:
或者方法只有一個(gè)實(shí)體類參數(shù)時(shí)就別標(biāo)注 @Param
注解了,這樣返回的就是實(shí)體類的實(shí)例而不是一個(gè) Map,同樣記得 Mapper 文件中占位符直接寫屬性 #{property}
即可。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
基于自定義校驗(yàn)注解(controller、method、(groups)分組的使用)
這篇文章主要介紹了基于自定義校驗(yàn)注解(controller、method、(groups)分組的使用),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10詳解SpringBoot應(yīng)用服務(wù)啟動(dòng)與安全終止
這篇文章主要介紹了SpringBoot應(yīng)用服務(wù)啟動(dòng)與安全終止,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)
本篇文章主要介紹了詳解Spring整合Quartz實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03Mybatis Plus select 實(shí)現(xiàn)只查詢部分字段
這篇文章主要介紹了Mybatis Plus select 實(shí)現(xiàn)只查詢部分字段的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Java中ByteArrayInputStream和ByteArrayOutputStream用法詳解
這篇文章主要介紹了Java中ByteArrayInputStream和ByteArrayOutputStream用法詳解,?ByteArrayInputStream?的內(nèi)部額外的定義了一個(gè)計(jì)數(shù)器,它被用來跟蹤?read()?方法要讀取的下一個(gè)字節(jié)2022-06-06SpringBoot實(shí)現(xiàn)AOP切面的三種方式
Spring,SpringBoot框架憑借多種高效機(jī)制,顯著增強(qiáng)了代碼的功能性,并實(shí)現(xiàn)了切面編程(AOP)的精髓,其核心亮點(diǎn)之一,是運(yùn)用動(dòng)態(tài)代理技術(shù),無需觸動(dòng)源代碼即可在Bean的運(yùn)行時(shí)為其動(dòng)態(tài)織入額外功能,本文給大家介紹了SpringBoot通過3種方式實(shí)現(xiàn)AOP切面,需要的朋友可以參考下2024-08-08