Java枚举意外触发了Mybatis Plus的BUG

点击关注公众号:互联网架构师,后台回复 2T获取2TB学习资源!

上一篇:Alibaba开源内网高并发编程手册.pdf

 
 
 
 
 
 
 
 
 
 
 
 
 
 

问题

昨天用mybatis-plus写了一段crud,代码如下:

@Transactional@Overridepublic boolean updateTaskStatus(Integer taskId, TaskStatusEnum taskStatusEnum) {// 查询任务Task task = taskMapper.selectById(taskId);if (Objects.isNull(task)) {throw new IllegalArgumentException("没有查询到任务!");}// 检查状态是否正常if (!task.getStatus().nextStatus().contains(taskStatusEnum)) {throw new IllegalStateException("不能修改当前任务的状态!");}// 状态正常就修改状态到下一个状态task.setStatus(taskStatusEnum);// 更新任务状态int result = taskMapper.updateById(task);return result > 0;}

结果一直报错:

java.sql.SQLException: Incorrect integer value: 'COMPLETED' for column 'status' at row 1
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:916)
at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:354)
at

这个报错的意思是说,我的数据表中对应的status表字段类型是integer value,但是传进来的值却是COMPLETED字符串;

通过不断地测试,发现该BUG出现的现象如下:

1.项目重启后,该报错出现在执行updateById()这一段代码上;

2.报错后如果继续调用该接口,selectById()将出现以下报错:

org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'status' from result set.  Cause: java.lang.IllegalArgumentException: No enum constant com.example.awesomespring.enums.TaskStatusEnum.0
at org.apache.ibatis.type.BaseTypeHandler.getResult(BaseTypeHandler.java:87)
at org.apache.ibatis.executor.resultset.DefaultResultSetHandler.applyAutomaticMappings(DefaultResultSetHandler.java:561)
at

提前申明一下,项目中已经配置了该枚举类型对应的TypeHandler;

排查过程

  • DEBUG自定义TypeHandler

    出现第一个报错的时候,第一时间想到的就是TaskStatusEnum枚举没有匹配到对应的TypeHandler,所以在自定义TypeHandler中打上断点,再次发起请求后,出现了第二个报错,并且该线程并没有进入TypeHandler的断点当中;此后无论请求多少次,始终在selectById()上报错;

    为此不得不重启项目后重新断点,重启项目后第一次请求,在selectById()后进入断点,成功拿到解析后的结果task;并在执行updateById()前后都未进入断点,此后无论如何请求都没有进入断点;

    根据上述现象,我有以下两个判断:

    1.可能在selectById()执行过程中引入了变量导致updateById()没有找到对应的TypeHandler

    2.可能是updateById()产生的错误影响了全局配置,导致后续无论如何都无法找到TypeHandler

  • 深入源码

    我们都知道,mybatis-plus也是基于mybatis实现的,所以mybatis的那一套理论我们还是用得上的;mybatis在处理参数和结果集的时候都需要通过TypeHandler来处理;

    mybatis-plus中,我们可以找到MybatisParameterHandler.setParameters()中的这一段代码:

    TypeHandler typeHandler = parameterMapping.getTypeHandler();

    通过Debug我们发现它最终拿到的是UnknownTypeHandler:

    bff61495456efb5e449fcc7afcc98938.jpeg
    UnknownTypeHandler
  • 查看TypeHandlerRegistry

    通过上述手段,我们发现mybatis-plus确实没有拿到正确的TypeHandler,这不得不让我们怀疑TypeHandler是否成功地注册到配置中了,随即我们在Debug变量表中展开Configuration对象,准备查看里面的TypeHandler:

    5531867ce72f6199bbc79b1099db0993.jpeg
    TypeHandler

    我们在处理完参数后再次查看TypeHandlerRegistry,发现该枚举对应的TypeHandler已经发生改变了:

    1a4bbba04d1647cc6c7797fd5140d9fe.jpeg
    TypeHandlerRegistry

    另外值得一提的是,另一个枚举类型对应的TypeHandler始终没有改变:

    8556e645714764d509ef568c140b9b44.jpeg
    TypeHandler
  • 对比枚举类型差异

发现两个枚举类型的不同表现后,我尝试对比一下两个枚举类型的差异:

TaskTypeEnum.java:

public enum TaskTypeEnum implements IEnum<Integer, String> {QUERY(1, "查询任务"),UPDATE(2, "更新任务");private final Integer code;private final String value;TaskTypeEnum(Integer code, String value) {this.code = code;this.value = value;}@Overridepublic Integer getCode() {return this.code;}@Overridepublic String getValue() {return this.value;}
}

TaskStatusEnum.java:

public enum TaskStatusEnum implements IEnum<Integer, String> {START(0, "开始") {@Overridepublic List<TaskStatusEnum> nextStatus() {return Arrays.asList(COMPLETED);}},COMPLETED(1, "完成") {@Overridepublic List<TaskStatusEnum> nextStatus() {return Arrays.asList(END);}},END(2, "结束") {@Overridepublic List<TaskStatusEnum> nextStatus() {return null;}};TaskStatusEnum(Integer code, String value) {this.code = code;this.value = value;}private final Integer code;private final String value;@Overridepublic Integer getCode() {return this.code;}@Overridepublic String getValue() {return this.value;}public abstract List<TaskStatusEnum> nextStatus();
}

两者的不同点在于出问题的枚举有一个抽象方法,每个实例都要实现该抽象方法;为此我尝试把第二个枚举改造成和第一个枚举一样,删掉抽象方法后,重新调用接口,竟然真的成功了!

定位问题

对于这种莫名其妙的情况下就把问题解决了,我是不甘心的。当我准备找到问题根源重新DEBUG时,无意间我发现了一点小小的线索:

63f17d03681ddc53cffe10979061488f.jpeg
定位问题

这个传进来的枚举类型是TaskStatusEnum$2.class,它应该是TaskStatusEnum.class;一开始我以为是spring mvc在做请求参数解析的时候做了一层包装,我尝试把代码改成这样:

task.setStatus(TaskStatusEnum.valueOf(taskStatusEnum.name()));

结果发现我的猜测是错误的,类型依旧是TaskStatusEnum$2.class;只有当枚举中没有抽象方法时,类型才是正确的;

并且我们发现,TaskStatusEnum中所有的实例类型都不一样:

76568dcc7a8dfe3d0d04fbd3a7cb73ee.jpeg
TaskStatusEnum

解释现象

为了能够了解这个现象出现的原因,我简单看了一下源码,大概过程如下:

private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {// 传进来的type为TaskStatusEnum$2.class,jdbcHandlerMap为nullMap<JdbcType, TypeHandler<?>> jdbcHandlerMap = (Map)this.typeHandlerMap.get(type);if (jdbcHandlerMap != null) {return NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap) ? null : jdbcHandlerMap;} else {if (type instanceof Class) {Class<?> clazz = (Class)type;// 判断是否时枚举类型if (Enum.class.isAssignableFrom(clazz)) {// TaskStatusEnum$2.class是匿名类,所以找到父类TaskStatusEnum.classClass<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;// 返回null,至于为啥直接返回null,一看代码便知jdbcHandlerMap = this.getJdbcHandlerMapForEnumInterfaces(enumClass, enumClass);if (jdbcHandlerMap == null) {// 给这个类型TaskStatusEnum.class注册上默认的枚举类型处理器this.register(enumClass, this.getInstance(enumClass, this.defaultEnumTypeHandler));// 返回默认的枚举类型处理器EnumTypeHandlerreturn (Map)this.typeHandlerMap.get(enumClass);}} else {jdbcHandlerMap = this.getJdbcHandlerMapForSuperclass(clazz);}}this.typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);return jdbcHandlerMap;}
}

我们可以简单总结一下:

1.mybatis-plus处理枚举类型参数时,是直接通过传进来的参数值对应的类型去TypeHandlerRegistry中查找对应的TypeHandler的;

2.当没有找到该匿名类型对应的TypeHandler时,获取了父类类型,但是getJdbcHandlerMapForEnumInterfaces()显然是从枚举中的接口去找对应的TypeHandler,这一步让这个匿名类枚举实例完美地错过了它的TypeHandler

3.最后一步是指定了默认的枚举类型处理器org.apache.ibatis.type.EnumTypeHandler,并且执行力register()操作,那么TypeHandlerRegistryTaskStatusEnum.class对应的TypeHandler被修改了;

根据上述总结,我们对于前面的问题就很好理解了,正是因为匿名枚举类型造成了TypeHandler被动态修改了,才导致了后面无论如何执行,都无法成功地执行selectById(),因为在结果集解析时,通过TaskStatusEnum.class找到的org.apache.ibatis.type.EnumTypeHandler无法构建TaskStatusEnum实例;

对比mybatis

当今天我希望通过mybatis复现该问题时,我发现mybatis完全没有问题,说明这个问题仅仅出现在mybatis-plus上面,完全不是mybatis的锅,也不是枚举的锅;

通过对比发现,mybatis在项目启动时,就已经把对应的实体类中属性字段类型和TypeHandler放进缓存中了,在SQL执行阶段,直接拿出对应的TypeHandler来处理参数值,它的参数解析是不依赖参数类型的;而Mybatis-plus是通过参数类型从TypeHandlerRegistry中取TypeHandler的,这就导致了获取到不正确的TypeHandler

Mybatisstatus字段对应的参数类型:

51df9b969eb8f586c2de5050aba7c1a7.jpeg
参数类型

metaClass是已经解析好的实体类元数据,可以直接从里面获取对应的属性字段类型;

Mybatis-Plusstatus字段对应的数据类型:

67d309a46c1bdcb5034fbe201d7bca99.jpeg
数据类型

mybatis-plus会将参数包装成ParamMap类型,导致返回的数据类型是Object.class,最后匿名枚举类型匹配不到TypeHandler,导致BUG出现;

如何避免

现在我们已经知道了导致这个问题的原因了,也就很容易就给出以下解决方案:

1.在使用枚举时,尽可能不要使用抽象方法,导致枚举实例都是匿名类型;这是代价最小的方案;

2.直接给父级接口配置TypeHandler;因为它找不到匿名类对应的TypeHandler就会找父级接口对应的TypeHandler;这个也算是比较好的解决方案了;

3.动态地给所有的匿名类型也配置上TypeHandler;代价也很小,调用TypeHandlerRegistry.register就可以;

4.将mybatis替换掉mybatis-plus;这个代价很大,意味着你的项目中要改很多代码以及调整相关配置;

来源:juejin.cn/post/7173449167618965534

最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。

正文结束

推荐阅读 ↓↓↓

1.ChatGPT 遭教育部“拉黑”:师生禁用!

2.从零开始搭建创业公司后台技术栈

3.程序员一般可以从什么平台接私活?

4.流程引擎的架构设计

5.为什么国内 996 干不过国外的 955呢?

6.中国的铁路订票系统在世界上属于什么水平?                        

7.15张图看懂瞎忙和高效的区别!

0b3996758d8c884f1b321522ebe3c0e9.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/53579.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

亚马逊加入大模型竞赛/ 滴滴可打自动驾驶车/ 百度副总裁回应王小川…今日更多新鲜事在此...

日报君 发自 凹非寺量子位 | 公众号 QbitAI 大家好&#xff0c;今天是4月14日星期五&#xff0c;马上又要到周末啦~ 今天科技圈有哪些新鲜事儿&#xff0c;和日报君一起来看看~ 百度副总裁回应王小川&#xff1a;确实不在一个宇宙 无论国内还是国外&#xff0c;人工智能的竞争可…

不用任何软件,批量转化图片格式

不用任何软件&#xff0c;批量转化图片格式 文件夹里有上百张图片&#xff0c;如何批量修改格式&#xff1f; 其实不用任何软件&#xff0c;不用任何转化工具就能实现这一目的。 这是Windows系统自带ren&#xff08;rename-重命名&#xff09;命令&#xff0c;利用rename命令…

3个无敌实用的图片转换器,上百种图片格式任意转

分享3个万能的图片格式转换工具&#xff0c;每个基本上都提供了上百种图片格式转换&#xff0c;而且压缩后的图片一点都不会损坏原画质&#xff0c;压缩完成后还能自动下载保存到相册&#xff0c;转换速度不仅快而且还提供了其他的图片处理功能。 1、万能图片转换器 一个超级丰…

使用手机将图片转换成PNG格式怎么做?教你三种转换方法

怎么使用手机把图片的格式转换成PNG格式呢&#xff1f;现如今的图片格式种类非常之多&#xff0c;有很多种格式的图片我们甚至都打不开它。有时我们需要上传一些照片文件&#xff0c;会有要求照片只能是PNG格式&#xff0c;遇到这种情况我们该怎么使用手机就能够就能将图片格式…

PNG图片怎么转成ICO?分享两种思路

ICO作为一种图标文件随处可见&#xff0c;在设计的时候偶尔会需要将PNG图片转为ICO格式。那么PNG图片怎么转成ICO呢&#xff1f;我们可以使用转换工具或登录在线网站进行处理&#xff0c;下面推荐两种方式&#xff0c;点赞收藏不迷路哦。 一、格式转换工具拥有对PDF文件全面的格…

微信电脑版DAT图片文件转JPG/PNG图片的简单工具

平常微信聊天中发出和接收到的图片&#xff0c;都是经过编码后再保存在电脑中的&#xff0c;普通方法打不开 如果需要整理&#xff0c;则是个问题 本文详细说明如何快速地把平常微信聊天中存储在电脑中的聊天图片转换成普通图片 支持JPG和PNG 文件存储位置 首先找到这些图片 …

IMZO创建高品质免费图片库,图片可全球范围免版税商业使用

美通社消息&#xff1a;IMZO是免费提供海量高分辨率图像的全球平台。该平台无需订阅&#xff0c;无附加条件&#xff0c;无隐藏收费。用户只需点击并下载最大8100x5400像素的300dpi最佳创意照片。 平台的全部收藏中包括生活方式、商业、医疗保健、技术等诸多类别的海量图片。IM…

iOS 摸鱼周报 #84 | 开箱即用的云服务 AirCode

本期概要 本期话题&#xff1a;开发加速器&#xff1a;在 App 或游戏内创造无障碍体验本周学习&#xff1a;Any 和 AnyObject 以及 AnyClass 内容推荐&#xff1a;依赖注入、自定义 Button 外观与行为、正则表达式、创建 XCFramework 以及创建可配置的小组件文章推荐摸一下鱼&a…

企业流程中心BPM产品搭建

一、为什么要搭建企业流程中心 企业发展初期&#xff0c;会制定一堆制度&#xff0c;用来规范企业管理&#xff0c;但制度更多是奖励和惩罚的作用&#xff0c;并不能提升管理和业务效率。 比如&#xff0c;我们开车走高速的时候&#xff0c;超速会扣分、会罚款&#xff0c;这是…

Python彩色图片转手绘风格

Python彩色图片转手绘风格 from PIL import Image # 图像的手绘 """黑白风格边界的位置比较重相同或相近色彩趋近于白色咯有光源效果 """ # 读取彩色图片并转化为np数组 a np.array(Image.open(r114362.jpg).convert(L)).astype(float)depth 1…

值得拥有的手绘风格画图工具

一直在找一款手绘风格的画图工具&#xff0c;好的图表总能传递更多的信息。最初用微软 Visio 工具画图&#xff0c;但依赖于安装软件&#xff0c;无法轻量级使用&#xff0c;效果也有些僵硬&#xff0c;后来改用 ProcessOn 在线画图&#xff0c;免费版有数量限制。后面使用 Dra…

为什么有计算机绘图了还要学手绘,设计师有必要学习手绘吗?

文/刘伦利 当前&#xff0c;电脑及相关软件的应用已经越来越普遍&#xff0c;特别是艺术设计专业&#xff0c;各种设计软件已作为设计专业中的辅助工具&#xff0c;占有重要的地位和作用。很多初学者认为只要学了计算机就可以做所想的设计图&#xff0c;并没有认识到没有原始的…

教你如何几行python代码实现图片转手绘

这里是运行效果 先展示一下完整代码&#xff0c;后面在进行分析 # -*- coding: utf-8 -*- from PIL import Image import numpy as npim Image.open(really.jpg).convert(L)#really.jpg是这里将用于转换的原图 a np.asarray(im).astype(float) # 将图像以灰度图的方式打开并…

如何用计算机制作pop海报,ai怎么制作pop海报字体?ai简单快速制作pop字体教程

版权申明&#xff1a;本文原创作者“xifenu”&#xff0c;感谢“xifenu”的原创经验分享&#xff01; 怎么用ai制作海报上的pop字体&#xff1f;接下来小编就给大家分享一篇非常实用的ai简单快速制作pop字体教程&#xff0c;希望对你们学习ai制作pop海报字体有帮助&#xff0c;…

diy机器人图片 手绘纸箱_环保手工制作大全:手绘纸箱机器人

这是我的第一个机器人。性别男。 跟真人比一下大小。原本的纸箱全是快递用的&#xff0c;我把那些质地不错无明显褶皱损伤的纸箱都留着&#xff0c;有空就画个。 上帝造物&#xff0c;有男就得有女。于是。。。给他找了个不错的老婆。 然后生了个儿子。 机器人的繁殖能力不错&a…

python实例练习(9)图像的手绘效果

文章目录 简介图像的数组表示图像的手绘处理 简介 在之前的学习笔记的实例中&#xff0c;我们曾经使用PIL库获取了图像的轮廓&#xff0c;虽然我们成功提取出来了&#xff0c;但是这个轮廓缺少了立体感&#xff0c;视觉效果上缺少了丰满度&#xff0c;光线照射的明暗变化是空间…

python 实现图像的手绘效果

图像的数组表示 1、图像一般使用RGB色彩模式&#xff0c;即每个像素点的颜色由红(R)、绿(G)、蓝(B)组成。 RGB三个颜色通道的变化和叠加得到各种颜色&#xff0c;其中 • R 红色&#xff0c;取值范围&#xff0c;0‐255 • G 绿色&#xff0c;取值范围&#xff0c;0‐255 •…

《应试教育洗礼的“好”学生的学习行为特征分析》 2019-11-02

1 引言 小明是在应试教育中凭借自己的努力一步步考上了重点大学的研究生&#xff0c;每年都要学习好几门课程而且考试成绩都非常好排名数一数二获得很多荣誉&#xff0c;是经常被各种称赞的好学生&#xff0c;但小明真的是"好"学生吗&#xff0c;是具备科研素质能够…

ChatGPT 和 Whisper 模型的区别

ChatGPT和Whisper模型是两个不同的模型&#xff0c;但都是由OpenAI开发的基于自然语言处理&#xff08;NLP&#xff09;的人工智能技术。 ChatGPT是一种基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构的语言模型&#xff0c;它可以生成自然流畅的文本…

微信公众号抓包

2.打开burp&#xff0c;打开公众号 直接逮住