MyBatis 经典踩坑:有参构造函数引发列顺序错位类型转换异常
一、现象日志执行数据库查询抛出异常org.springframework.dao.DataIntegrityViolationException: Error attempting to get column ret_msg from result set. Cause: java.sql.SQLDataException: Cannot determine value type from string 系统异常null Caused by: com.mysql.cj.exceptions.DataConversionException: Cannot determine value type from string 系统异常null堆栈特征at org.apache.ibatis.type.IntegerTypeHandler.getNullableResult at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.createUsingConstructor直观感受实体字段明明类型和数据库一致数据库ret_msg是varchar、实体retMsg是String却报字符串无法转为数字。很多人第一反应实体属性类型写错这是典型误判。根因不是字段定义是MyBatis构造函数实例化机制。二、环境还原数据库表CREATETABLEsys_account_deactivate_log(idvarchar(32),interface_namevarchar(255),emp_codevarchar(255),paramvarchar(255),ret_msgvarchar(255),ret_statusvarchar(255),call_timedatetime(3),PRIMARYKEY(id));实体代码DataTableName(sys_account_deactivate_log)publicclassAccountDeactivateLogEntity{privateStringid;privateStringinterfaceName;privateStringempCode;privateStringparam;privateStringretMsg;privateIntegerretStatus;privateDatecallTime;// 自定义有参构造【风险源头】publicAccountDeactivateLogEntity(StringinterfaceName,StringempCode,Stringparam,StringretMsg,IntegerretStatus){this.interfaceNameinterfaceName;this.empCodeempCode;this.paramparam;this.retMsgretMsg;this.retStatusretStatus;this.callTimenewDate();}}三、底层原理MyBatis DefaultResultSetHandler源码解读核心源码位置org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createUsingConstructorprivateObjectcreateByConstructorSignature(ResultSetWrapperrsw,Class?resultType,ListClass?constructorArgTypes,ListObjectconstructorArgs)throwsSQLException{// 1. 获取实体所有构造函数finalConstructor?[]constructorsresultType.getDeclaredConstructors();// 2. 查找合适的构造函数// --- 如果实体只有唯一一个构造器直接返回它// --- 多个构造器时寻找带有AutomapConstructor注解的构造器finalConstructor?defaultConstructorfindDefaultConstructor(constructors);if(defaultConstructor!null){// 实体类存在构造returncreateUsingConstructor(rsw,resultType,constructorArgTypes,constructorArgs,defaultConstructor);}else{// ❌ 没有构造被迫遍历所有构造方法尝试匹配for(Constructor?constructor:constructors){// 判断当前构造器参数类型列表 是否能和查询结果JDBC类型兼容if(allowedConstructorUsingTypeHandlers(constructor,rsw.getJdbcTypes())){// 找到一个合适的有参构造进入创建逻辑returncreateUsingConstructor(rsw,resultType,constructorArgTypes,constructorArgs,constructor);}}}// 所有构造器都无法匹配抛出异常thrownewExecutorException(No constructor found in resultType.getName() matching rsw.getClassNames());}privateObjectcreateUsingConstructor(ResultSetWrapperrsw,Class?resultType,ListClass?constructorArgTypes,ListObjectconstructorArgs,Constructor?constructor)throwsSQLException{booleanfoundValuesfalse;// 循环遍历构造器每一个入参for(inti0;iconstructor.getParameterTypes().length;i){// 获取【构造器第i个参数】的Java类型Class?parameterTypeconstructor.getParameterTypes()[i];// 【致命一行】// rsw.getColumnNames()SQL查询返回的所有列名称有序列表// i循环下标【按下标顺序拿第i列不是按名称匹配】StringcolumnNamersw.getColumnNames().get(i);// // 根据参数Java类型获取对应的TypeHandler例如 Integer→IntegerTypeHandlerString→StringTypeHandlerTypeHandler?typeHandlerrsw.getTypeHandler(parameterType,columnName);// 使用TypeHandler从ResultSet读取这一列的值ObjectvaluetypeHandler.getResult(rsw.getResultSet(),columnName);// 收集参数类型与参数值后续反射调用构造器constructorArgTypes.add(parameterType);constructorArgs.add(value);foundValuesvalue!null||foundValues;}// 反射调用构造方法创建实体对象returnfoundValues?objectFactory.create(resultType,constructorArgTypes,constructorArgs):null;}}privatebooleanallowedConstructorUsingTypeHandlers(finalConstructor?constructor,finalListJdbcTypejdbcTypes){// 获取当前构造器所有入参Java类型finalClass?[]parameterTypesconstructor.getParameterTypes();// 条件1构造器参数个数 必须 SQL查询返回的列数量if(parameterTypes.length!jdbcTypes.size()){returnfalse;}// 条件2逐个位置校验Java类型 -- JDBC类型 是否存在可用TypeHandlerfor(inti0;iparameterTypes.length;i){// parameterTypes[i]构造器第i个参数Java类型// jdbcTypes.get(i)查询结果第i列的JDBC类型if(!typeHandlerRegistry.hasTypeHandler(parameterTypes[i],jdbcTypes.get(i))){returnfalse;}}// 两个条件全部满足允许使用该构造器实例化对象returntrue;}重中之重一条规则记住当MyBatis使用有参构造反射创建对象时构造器第N个参数 ← 查询结果集中第N列数据【只按下标顺序绑定不匹配列名】本次事故流程演示你的实体手写有参构造没有无参构造MyBatis 进入else分支匹配到你的有参构造进入循环构造器参数[0] ← 查询列[0] 构造器参数[1] ← 查询列[1] 构造器参数[2] ← 查询列[2] 构造器参数[3] ← 查询列[3] 构造器参数[4] ← 查询列[4]类型IntegerSQL select 字段顺序 和 构造器参数顺序不一致发生列错位查询第4列的值实际是字符串系统异常null当前参数类型是Integer启用IntegerTypeHandler底层调用resultSet.getInt(列名)MySQL驱动无法把文本转为数字抛出异常Cannot determine value type from string 系统异常null额外迷惑点很多人踩坑异常日志打印get column ret_msg会误导开发认为retMsg属性类型写错真实原因只是错位之后这一列刚好是 ret_msg实体本身字段定义完全没问题四、容易混淆的误区❌ 误区1实体字段和数据库字段名称一致就会按名称映射错有参构造实例化模式下只认顺序不认名称。只有「无参构造 setter赋值」才会根据列名映射。❌ 误区2报错信息写了get column ret_msg代表实体retMsg类型错误报错只是JDBC驱动执行getInt操作时当前游标对应的列恰好是ret_msg不代表实体retMsg定义错误非常具有迷惑性❌ 误区3MP自带自动映射手写SQL顺序无关紧要MP自动映射只作用于setter注入模式构造器模式不受MybatisPlus控制底层依然遵循MyBatis原生规则。五、解决方案推荐优先级从高到低方案1实体必须保留无参构造删除业务自定义有参构造DataTableName(sys_account_deactivate_log)publicclassAccountDeactivateLogEntity{privateStringid;privateStringinterfaceName;privateStringempCode;privateStringparam;privateStringretMsg;privateIntegerretStatus;privateDatecallTime;// Lombok Data 默认提供无参构造不要手动删除// 删除所有自定义有参构造}业务构建对象方式AccountDeactivateLogEntityentitynewAccountDeactivateLogEntity();entity.setInterfaceName(xxx);entity.setRetMsg(系统异常null);entity.setRetStatus(3);优势MyBatis优先选择无参构造实例化之后通过setter方法按数据库列名匹配赋值和SQL字段顺序完全无关从根源杜绝该类问题。方案2实体仍须保留有参构造定义一个无常构造函数DataTableName(sys_account_deactivate_log)publicclassAccountDeactivateLogEntity{privateStringid;privateStringinterfaceName;privateStringempCode;privateStringparam;privateStringretMsg;privateIntegerretStatus;privateDatecallTime;publicAccountDeactivateLogEntity(){}publicAccountDeactivateLogEntity(StringinterfaceName,StringempCode,Stringparam,StringretMsg,IntegerretStatus){this.interfaceNameinterfaceName;this.empCodeempCode;this.paramparam;this.retMsgretMsg;this.retStatusretStatus;this.callTimenewDate();}}

相关新闻

基于ADS866x的隔离式数据采集系统设计:应对高压共模噪声挑战

基于ADS866x的隔离式数据采集系统设计:应对高压共模噪声挑战

1. 项目概述:在高压噪声中“捞”出纯净信号在工业自动化、电力系统监测或者电机驱动测试台上,我们经常需要测量一些“浮”在很高电压上的微小信号。比如,你想监测一个三相电机驱动器中某相电流,这个电流信号可能只有几伏的幅度&am…

2026/7/24 11:22:30阅读更多 →
YOLOv8木材表面缺陷检测系统开发与应用

YOLOv8木材表面缺陷检测系统开发与应用

1. 项目背景与核心价值木材加工业一直面临着表面缺陷检测的难题。传统人工质检方式存在效率低、漏检率高、标准不统一等问题,特别是在节疤、裂缝等细微缺陷的识别上,人工检测的准确率往往不足70%。我们团队开发的这套基于YOLO的木材缺陷识别系统&#xf…

2026/7/24 11:20:29阅读更多 →
AM570x OCMC架构解析:ECC内存保护与循环缓冲区优化实践

AM570x OCMC架构解析:ECC内存保护与循环缓冲区优化实践

1. 项目概述与核心价值在嵌入式系统,尤其是像TI AM5706/AM5708这样的高性能异构多核处理器设计中,片上内存控制器(On-Chip Memory Controller, OCMC)的角色远比我们想象的要复杂和关键。它不仅仅是连接CPU核心与内部SRAM的“桥梁”…

2026/7/24 11:20:29阅读更多 →
MPS、MRP与APS:企业生产管理的三大核心系统

MPS、MRP与APS:企业生产管理的三大核心系统

1. 引言在现代制造业中,高效的生产计划和资源管理是企业保持竞争力的关键。MPS(主生产计划)、MRP(物料需求计划)和APS(高级计划与排程)作为企业资源计划(ERP)系统的核心组…

2026/7/24 14:21:15阅读更多 →
APS与ERP中的MRP执行决策:关键考量因素

APS与ERP中的MRP执行决策:关键考量因素

1. MRP的核心作用与流程物料需求计划(MRP)是制造业资源规划的核心模块,其核心流程是:需求输入:接收主生产计划(MPS)或独立需求。净需求计算:将总需求与现有库存、在途库存、安全库存…

2026/7/24 14:21:15阅读更多 →
CC1020窄带射频收发器硬件设计:从原理图到PCB布局的实战指南

CC1020窄带射频收发器硬件设计:从原理图到PCB布局的实战指南

1. 项目概述与核心价值在无线通信系统的开发中,射频收发器的硬件设计往往是决定项目成败的关键一环。它不像软件,可以后期通过OTA升级来修复Bug;一旦PCB打样回来,射频性能不达标,轻则导致通信距离大幅缩水,…

2026/7/24 14:21:15阅读更多 →
RynnWorld-Teleop:一种用于数字遥操作的动作条件世界模型

RynnWorld-Teleop:一种用于数字遥操作的动作条件世界模型

26年6月来自阿里达摩院、香港具身智能实验室、港中文、阿里湖畔实验室和蚂蚁集团的论文“RynnWorld-Teleop: An Action-Conditioned World Model for Digital Teleoperation”。 扩展机器人学习规​​模需要海量且多样化的轨迹数据,但目前数据采集受限于物理遥操作模…

2026/7/24 14:21:15阅读更多 →
我的C语言入门之路

我的C语言入门之路

编程是一场长期的底层修行。现阶段我正式开启C语言系统学习,并以博客记录自己的学习全过程。一方面用于自我复盘,沉淀知识点、总结踩坑经验;另一方面也希望我的自学规划和实操方法,能给零基础备考、深耕编程的同学提供一份可落地的…

2026/7/24 14:21:15阅读更多 →
第04章 初始化程序(5):按下 Ctrl+C 的连锁反应 —— 拆解进程组、会话期与控制终端

第04章 初始化程序(5):按下 Ctrl+C 的连锁反应 —— 拆解进程组、会话期与控制终端

引子:当我们在命令行中按下 “CtrlC” 时,究竟发生了什么? 假设你已经成功登录进了 Linux 0.11 系统,你迫不及待地想在终端里试一试新学到的管道命令。于是你输入了: [plinux root]# cat main.c | grep for | more 然后…

2026/7/24 14:19:14阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 0:58:53阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 0:58:53阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/24 0:58:53阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:06阅读更多 →
【LeetCode 54】螺旋矩阵

【LeetCode 54】螺旋矩阵

问题描述: 解法: 1、模拟(参考自【LeetCode 54】螺旋矩阵-CSDN博客) int *spiralOrder(int **matrix, int matrixSize, int *matrixColSize, int *returnSize) {static const int dirs[4][2] {{0, 1}, {1, 0}, {0, -1}, {-1, …

2026/7/24 0:00:06阅读更多 →
2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

2026 WAIC:模型隐身、智能体疯野,厂商竞赛聚焦办公场景与商业闭环

知春路不相信模型领先今年WAIC大会,昔日AI六小龙来了五家,分别是Kimi、阶跃星辰、Minimax、百川智能、零一万物。连放弃基模的百川和零一万物都来了,唯一缺席的竟是近几个月来风光无限的智谱。(DeepSeek一直不参加)WAI…

2026/7/24 0:00:06阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/23 22:58:43阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/23 18:58:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/23 18:58:18阅读更多 →