buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

未给entity的主属性赋值,Mybatisplus却抛出了type mismatch异常。——————分享一下Mybatisplus主键填充机制

📚 问题介绍

今天在客户联调时,程序出现一个ReflectionException: argument type mismatch 异常。 

通过分析异常堆栈及代码得知,在使用Mybatisplus的com.baomidou.mybatisplus.core.mapper.BaseMapper#insert保存LevyAccountRecharge数据时,当程序未给主键字段“orderNo”赋值时,就会抛出来这个异常。

具体异常message是:Could not set property 'orderNo' of 'class com.emax.trans.provider.modules.accountrecharge.entity.LevyAccountRecharge' with value '2003347475481600001' Cause: java.lang.IllegalArgumentException: argument type mismatch。

 

先贴出来异常stacktrace:

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: Could not set property 'orderNo' of 'class com.emax.trans.provider.modules.accountrecharge.entity.LevyAccountRecharge' with value '2003347475481600001' Cause: java.lang.IllegalArgumentException: argument type mismatch
 
    at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)
    at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
    at com.sun.proxy.$Proxy206.insert(Unknown Source)
    at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:278)
    at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:58)
    at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:62)
    at com.sun.proxy.$Proxy207.insert(Unknown Source)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:56)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy208.insert(Unknown Source)
    at com.baomidou.mybatisplus.extension.service.impl.ServiceImpl.save(ServiceImpl.java:104)
    at com.emax.trans.provider.modules.accountrecharge.service.LevyAccountRechargeManager.save(LevyAccountRechargeManager.java:200)
    ...
Caused by: org.apache.ibatis.reflection.ReflectionException: Could not set property 'orderNo' of 'class com.emax.trans.provider.modules.accountrecharge.entity.LevyAccountRecharge' with value '2003347475481600001' Cause: java.lang.IllegalArgumentException: argument type mismatch
    at org.apache.ibatis.reflection.wrapper.BeanWrapper.setBeanProperty(BeanWrapper.java:185)
    at org.apache.ibatis.reflection.wrapper.BeanWrapper.set(BeanWrapper.java:59)
    at org.apache.ibatis.reflection.MetaObject.setValue(MetaObject.java:140)
    at com.baomidou.mybatisplus.core.MybatisDefaultParameterHandler.populateKeys(MybatisDefaultParameterHandler.java:181)
    at com.baomidou.mybatisplus.core.MybatisDefaultParameterHandler.processBatch(MybatisDefaultParameterHandler.java:123)
    at com.baomidou.mybatisplus.core.MybatisDefaultParameterHandler.<init>(MybatisDefaultParameterHandler.java:52)
    at com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver.createParameterHandler(MybatisXMLLanguageDriver.java:34)
    at com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver.createParameterHandler(MybatisXMLLanguageDriver.java:28)
    at org.apache.ibatis.session.Configuration.newParameterHandler(Configuration.java:563)
    at org.apache.ibatis.executor.statement.BaseStatementHandler.<init>(BaseStatementHandler.java:69)
    at org.apache.ibatis.executor.statement.PreparedStatementHandler.<init>(PreparedStatementHandler.java:41)
    at org.apache.ibatis.executor.statement.RoutingStatementHandler.<init>(RoutingStatementHandler.java:46)
    at org.apache.ibatis.session.Configuration.newStatementHandler(Configuration.java:576)
    at com.baomidou.mybatisplus.core.executor.MybatisSimpleExecutor.doUpdate(MybatisSimpleExecutor.java:52)
    at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
    at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
    ...
Caused by: java.lang.IllegalArgumentException: argument type mismatch
    ...

 

LevyAccountRecharge 实体类定义如下,其中 orderNo上有注解@TableId。orderNo对应的mysql数据表字段是varchar(32)。

@Data
@TableName(value = "levy_account_recharge",autoResultMap = true)
public class LevyAccountRecharge {
 
    /**业务订单号,主键*/
    @TableId
    private String orderNo;
 
    // 其他field
}

 

用下面的junit测试代码,可以再现出来这个异常:

@Test
public void save() {
    LevyAccountRecharge byId = levyAccountRechargeManager.getById("2025112319260202940444");
    byId.setOrderNo(null);
    levyAccountRechargeManager.save(byId);
}

 

 

我的疑问是:为什么 orderNo 是null 时,Mybatisplus会抛出这个异常,而不是类似“不能将null值插入主键字段”的异常呢?

于是,立即开启排障之旅。

我注意到异常stacktrace里有MybatisDefaultParameterHandler,于是加上断点进行debug。结果惊奇地发现,其idType 竟然是 ID_WORKER。显然,由于 ID_WORKER 会生成Long数值,在给String赋值时,就会出现类型转换异常。

image

 

那么,为什么 LevyAccountRecharge.orderNo 的IdType是默认的IdType.NONE,而程序跑出来的却是 IdType.ID_WORKER 呢?

为了简单,我直接使用 TableInfo tableInfo = TableInfoHelper.getTableInfo(LevyAccountRecharge.class);来看看,发现其IdType也是 IdType.ID_WORKER 。

为什么是ID_WORKER呢?为什么是ID_WORKER呢?为什么是ID_WORKER呢?

 

💡 核心问题分析

上面遇到的 ReflectionException: argument type mismatch 异常,根本原因在于 MyBatis-Plus 的主键自动填充机制实体类字段类型不匹配。

当执行 save 操作且 orderNo 为 null 时,事件的完整流程如下:

  1. 策略触发:MyBatis-Plus 检测到主键字段 orderNo 的值为 null
  2. 策略选择:由于orderNo字段的 @TableId 注解未显式指定 type,MyBatis-Plus 需要确定使用哪种策略来生成主键值。
  3. 值生成:系统根据确定的策略(在这里是 ID_WORKER)生成了一个 Long 类型的数字(如 2003347475481600001)。
  4. 赋值失败:MyBatis-Plus 尝试通过反射将这个 Long 型 的值设置到您实体类中定义的 String 类型 的 orderNo 字段上。由于 Java 是强类型语言,Long 无法直接赋给 String,因此在反射调用 setOrderNo 方法时抛出了 IllegalArgumentException: argument type mismatch。这个异常最终被包装成了 ReflectionException

🔍 深入探索:为什么默认策略是 ID_WORKER?

当未指定 @TableId 的 IdType 时,其默认值是 IdType.NONE,其javadoc明确指出:表示未设置主键类型(将跟随全局)。

注意其中的4个大字“跟随全局”。IdType.NONE 的含义是“未设置”,其行为就是回退到使用全局配置的策略

我们来了解一下Mybatisplus的主键填充机制。这背后的逻辑在于 MyBatis-Plus 的 策略查找链。优先级从高到低如下:

1. 局部注解(最高) 实体类字段的 @TableId(type = ...) 直接为该字段指定策略。orderNo 未设置,故跳过。
2. 全局配置 application.yml/properties 中的 mybatis-plus.global-config.db-config.id-type 如果在此处配置了策略,则所有未显式指定策略的主键都使用它。
程序设置的是3-ID_WORKER。
3. 框架默认(最低) MyBatis-Plus 框架内部默认 当上述两级都未配置时,框架会启用其内置的默认策略
默认全局策略就是 ASSIGN_ID(即旧版本中的 ID_WORKER。见com.baomidou.mybatisplus.core.config.GlobalConfig.DbConfig#idType

ID_WORKER策略是使用雪花算法生成 Long 类型 的全局唯一 ID。 这就是为什么注解上的 @TableId(等价于 @TableId(type = IdType.NONE))运行时实际生效的策略是 ID_WORKER 的原因。

🛠️ 解决方案

显然,使用 ID_WORKER_STR 策略 来确保主键生成策略与实体字段类型、数据库字段类型三者一致

将注解修改为 @TableId(type = IdType.ID_WORKER_STR)。这个策略专为字符串类型设计,它同样基于雪花算法,但生成的是 String 类型 的 ID,完美匹配您的 varchar(32) 数据库字段和 String orderNo

@Data
@TableName(value = "levy_account_recharge", autoResultMap = true)
public class LevyAccountRecharge {
    /** 业务订单号,主键 */
    @TableId(type = IdType.ID_WORKER_STR) // 明确指定使用字符串版本的雪花算法
    private String orderNo;
    // ... 其他字段
}

💎 总结与最佳实践

  • 根本原因orderNo 字段为 String 类型,但因其主键策略未显式设置,MyBatis-Plus 使用了默认的 ID_WORKER 策略来生成 Long 类型的值,导致类型不匹配的赋值异常。
  • 核心解决方案:在 @TableId 注解中显式指定 type = IdType.ID_WORKER_STR
  • 最佳实践:建议在定义主键时,养成显式指定 @TableId 的 type 的习惯。这可以避免因依赖全局默认配置(可能被不同人无意中修改)而导致的意外错误,使代码更加健壮和可读。

 

posted on 2025-12-24 21:03  buguge  阅读(0)  评论(0)    收藏  举报