MybatisPlus 字段自动填充失效,填充值为 null 的一种解决方法
Posted 庸人冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MybatisPlus 字段自动填充失效,填充值为 null 的一种解决方法相关的知识,希望对你有一定的参考价值。
文章目录
问题描述
有一个实体类UserEntity
对其属性 UserEntity#createTime
字段注解了 @TableField(fill = FieldFill.INSERT)
根据业务需要定义了 UserMapper#inserUser()
方法,参数注解了 @Param(“user”)
如下:
当调用该方法时,无法给 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
实现类的重写的方法无误后,开始尝试跟踪 Mybatis-plus 的源码。
发现在 MybatisParameterHandler#process()
中完成了自动填充的功能,在自动填充前需要先获取 tableInfo
信息:
而这个 TableInfoHelper.getTableInfo()
方法只有当传入的 Class
对象是实体类对象时才能获取到 tableInfo
:
那么问题来了,我的参数确实是实体类,但是为什么获取不到 TableInfo 呢?
因为 MybatisParameterHandler#process()
方法的 parameter
参数在调用我自定义的方法时,传入的是一个 Map
,这个 Map
的 key
是一个字符串表示,而 value
是我自定义方法的参数实例,可以看到下图中我的 Map
有两个 Entry
一个 key=user
一个 key=param1
而最为重要的是,在这个 process()
方法中,如果传入的是一个 Map
,Mybatis-plus 会从其中取 key="et"
的值,这就是问题的原因所在!!!
而传入的这个 Map 不存在 key="et"
的映射关系。因此两个 TableInfoHelper.getTableInfo()
方法都进不去,所以也就不会进行自动填充。
那么如何建立 "et" -> entity
的映射关系呢?我们Map中原本的两个的映射关系又是从哪里来的?
根据方法的调用链,一直回退到 Mybatis 框架中的 MapperMethod#execute()
方法中的一行代码:
上面的 convertArgsToSqlCommandParam()
方法就是通过我们方法的实际参数 args
转换为执行 sql 语句需要的参数格式,而返回值 param
就是之前传入的那个 map
。
我们跟踪该方法的调用链,发现最终调用了 ParamNameResolver#getNamedParams()
方法,该方法有3个分支,决定了我们最终得到参数是怎样的,源码如下:
public Object getNamedParams(Object[] args)
final int paramCount = names.size();
// 1. 方法是空参时直接返回
if (args == null || paramCount == 0)
return null;
else if (!hasParamAnnotation && paramCount == 1)
// 2. 方法的参数没有注解 @Param 并且只有一个参数时,直接返回这个参数实例
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
else
// 3. 否则,就建立映射关系(要么注解了 @Param,要么就是多个参数)
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// 遍历 names 中每一个 entry, 这个 names 是一个 sortedMap,该 Map 会保存方法参数的索引 -> 参数名称的映射关系,如果参数注解了 @Param,则值时 @Param("xxx") 中的 xxx,如果没有注解 @Param 则值也为参数索引,例如:
// 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 = 参数名称(@Param("xxx"))或 参数的索引
// value = 参数实例
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
// 下面就是添加 "paramN" -> 参数实例的映射,我们知道在 mapper 文件中可以使用 #paramN 来获取参数列表的值,这就是原因。
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;
那么显然,本次调用返回的 param 如下:
解决方法
通过上面的分析,我们就知道了为什么咱们传给 MybatisParameterHandler#process()
的参数是一个 Map,并且也知道了为什么自动填充失败的根本原因,那么解决方法也就很明确了:
-
给实体类参数注解为
@Param(“et”)
,修改后记得 Mapper 文件中占位符中也要改成#et.property
: -
或者方法只有一个实体类参数时就别标注
@Param
注解了,这样返回的就是实体类的实例而不是一个 Map,同样记得 Mapper 文件中占位符直接写属性#property
即可。
以上是关于MybatisPlus 字段自动填充失效,填充值为 null 的一种解决方法的主要内容,如果未能解决你的问题,请参考以下文章
MybatisPlus 字段自动填充失效,填充值为 null 的一种解决方法