MybatisPlus字段自动填充失效,填充值为null的解决方案

 更新时间:2024年01月13日 09:23:36   作者:庸人冲  
这篇文章主要介绍了MybatisPlus字段自动填充失效,填充值为null的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

问题描述

有一个实体类UserEntity 对其属性 UserEntity#createTime 字段注解了 @TableField(fill = FieldFill.INSERT)

image-20220521091544321

根据业务需要定义了 UserMapper#inserUser() 方法,参数注解了 @Param(“user”) 如下:

image-20220521091425353

当调用该方法时,无法给 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 信息:

image-20220521093717931

而这个 TableInfoHelper.getTableInfo() 方法只有当传入的 Class 对象是实体类对象时才能获取到 tableInfo :

image-20220521094104208

那么问题来了,我的参数确实是实体类,但是为什么获取不到 TableInfo 呢?

因为 MybatisParameterHandler#process() 方法的 parameter 参数在调用我自定义的方法时,传入的是一个 Map ,这个 Mapkey 是一个字符串表示,而 value 是我自定义方法的参数实例,可以看到下图中我的 Map 有两个 Entry 一个 key=user 一个 key=param1

image-20220521094353846

而最为重要的是,在这个 process() 方法中,如果传入的是一个 Map,Mybatis-plus 会从其中取 key="et" 的值,这就是问题的原因所在!!!

image-20220521094759755

而传入的这个 Map 不存在 key="et" 的映射关系。因此两个 TableInfoHelper.getTableInfo() 方法都进不去,所以也就不会进行自动填充。

image-20220521095247096

那么如何建立 "et" -> entity 的映射关系呢?我们Map中原本的两个的映射关系又是从哪里来的?

根据方法的调用链,一直回退到 Mybatis 框架中的 MapperMethod#execute() 方法中的一行代码:

image-20220521095621950

上面的 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 如下:

image-20220521101650953

解决方法

通过上面的分析,我们就知道了为什么咱们传给 MybatisParameterHandler#process() 的参数是一个 Map,并且也知道了为什么自动填充失败的根本原因,那么解决方法也就很明确了:

给实体类参数注解为 @Param(“et”),修改后记得 Mapper 文件中占位符中也要改成 #{et.property}

image-20220521102147149

或者方法只有一个实体类参数时就别标注 @Param 注解了,这样返回的就是实体类的实例而不是一个 Map,同样记得 Mapper 文件中占位符直接写属性 #{property} 即可。

image-20220521103258068

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Springboot集成Jasypt实现配置文件加密的方法

    Springboot集成Jasypt实现配置文件加密的方法

    Jasypt是一个java库,它允许开发员以最少的努力为他/她的项目添加基本的加密功能,并且不需要对加密工作原理有深入的了解,这篇文章主要介绍了Springboot集成Jasypt实现配置文件加密,需要的朋友可以参考下
    2023-04-04
  • Java动态循环队列是如何实现的

    Java动态循环队列是如何实现的

    今天带大家学习java队列的相关知识,文章围绕着如何实现Java动态循环队列展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • 不了解这12个语法糖,别说你会Java

    不了解这12个语法糖,别说你会Java

    这篇文章主要介绍了不了解这12个语法糖,别说你会Java,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • 深入理解Spring Cloud Zuul过滤器

    深入理解Spring Cloud Zuul过滤器

    这篇文章主要给大家介绍了关于Spring Cloud Zuul过滤器的相关资料,通过阅读本文您将了解:Zuul过滤器类型与请求生命周期、如何编写Zuul过滤器、如何禁用Zuul过滤器和Spring Cloud为Zuul编写的过滤器及其功能,需要的朋友可以参考下。
    2017-02-02
  • 解决Spring国际化文案占位符失效问题的方法

    解决Spring国际化文案占位符失效问题的方法

    本篇文章主要介绍了解决Spring国际化文案占位符失效问题的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • Java手写线程池的实现方法

    Java手写线程池的实现方法

    这篇文章主要为大家详细介绍了Java手写线程池的实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • SSH框架网上商城项目第8战之查询和删除商品类别功能实现

    SSH框架网上商城项目第8战之查询和删除商品类别功能实现

    SSH框架网上商城项目第8战之查询和删除商品类别功能实现,为项目增加功能,添加、更新、删除和查询操作,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • Java用递归方法解决汉诺塔问题详解

    Java用递归方法解决汉诺塔问题详解

    汉诺塔问题是一个经典的问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。本文将用Java递归方法求解这一问题,感兴趣的可以学习一下
    2022-04-04
  • java通过DelayQueue实现延时任务

    java通过DelayQueue实现延时任务

    本文主要介绍了java通过DelayQueue实现延时任务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • Jackson自定义序列化与反序列化注解详解

    Jackson自定义序列化与反序列化注解详解

    这篇文章主要介绍了Jackson自定义序列化与反序列化注解详解,某些场景下,我们使用Jackson对数据进行序列化或反序列化的时候,需要对某些数据进行特殊处理,需要的朋友可以参考下
    2023-11-11

最新评论