找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4564

积分

0

好友

586

主题
发表于 2 小时前 | 查看: 7| 回复: 0

背景介绍

一个老项目,数据库用的是 MySQL 5.7.36,ORM 框架用的 MyBatis 3.5.0,mysql-connector-java 版本是 5.1.26。

新来了一个干练的小伙,精力充沛,看着就是个喜欢折腾的主。

他觉得 MyBatis 用起来不够顺手,要写的代码还不少,于是琢磨着把它替换成 MyBatis-Plus。

MyBatis-Plus 替换 MyBatis

先准备一张表 tbl_order,然后初始化 2 条数据。

tbl_order 建表 SQL 与初始化数据

为了简化演示,直接用 MyBatis-Plus 搭一个示例 demo,来模拟“小伙”当时的替换过程。

替换范围只在 MyBatis 这一层,其他组件版本暂时不动。MyBatis-Plus 版本就用“小伙”引用的 3.1.1,mysql-connector-java 继续保持 5.1.26。

示例代码:playitsafe

此时运行 com.qsl.OrderTest#orderListAllTest,直接报错,异常信息如下。

注意看 Caused by

MyBatis-Plus 替换后 LocalDateTime 转换异常堆栈

不支持的转换类型:java.time.LocalDateTime

谁不支持?mysql-connector-java 不支持!

mysql-connector-java 从哪个版本开始支持了?答案是 5.1.37

mysql-connector-j 5.1.37 支持 JDBC 4.2 java.time 类型的提交记录

升级 mysql-connector-java

mysql-connector-java 升级到 5.1.37,重新执行 com.qsl.OrderTest#orderListAllTest

升级 mysql-connector-java 后测试通过

不再报异常,查询结果也正确。

MyBatis-Plus 替换 MyBatis 似乎就这样完成了,顺利得让人有点怀疑。

Conversion not supported for type java.time.LocalDateTime

回过头再琢磨一下前面那个异常:Conversion not supported for type java.time.LocalDateTime

替换之前没人报这个,替换之后就冒出来了——难道这是 MyBatis-Plus 的问题?

要找根因很简单,直接从异常堆栈入手。

测试失败堆栈中 LocalDateTimeTypeHandler 的异常位置

点进去之后,你会发现这个方法简单得不能再简单了。

LocalDateTimeTypeHandler 源码中直接调用 getObject

这么简单的代码能出什么问题?

注意看图中左上角 MyBatis 的版本,是 3.5.1,并不是最初的 3.5.0。

有人可能想问了:不是用 MyBatis-Plus 替换了 MyBatis 吗?怎么还有 MyBatis?

这个问题问得真好,我只想给你个大嘴巴子。

你瞅一眼 MyBatis-Plus 的官方说明就明白了。

MyBatis-Plus 官方介绍:在 MyBatis 的基础上只做增强不做改变

既然基于 MyBatis 3.5.0 没抛异常,基于 3.5.1 却抛了,那 LocalDateTimeTypeHandler 在 3.5.1 里肯定动了手脚。

来看看到底改了什么:

MyBatis 3.5.0 与 3.5.1 中 LocalDateTimeTypeHandler 的代码差异对比

看出问题了吗?

MyBatis 3.5.0 会自己处理 LocalDateTime 类型的转换,也就是把 java.sql.Timestamp 转成 java.time.LocalDateTime

然而,注意了,然而来了!!!

从 MyBatis 3.5.1 开始,它不再处理 LocalDateTime(还有 LocalDateLocalTime)的转换了,转而交给 JDBC 组件,也就是 mysql-connector-java 去实现。

巧就巧在,mysql-connector-java 5.1.26 偏偏不支持 LocalDateTime

那它支持哪些类型?我们还是从异常堆栈往里钻。

异常堆栈中 ResultSetImpl.getObject 的调用位置

JDBC42ResultSet 中 getObject 方法实现

点进去之后,能看到下面这张图。

ResultSetImpl 中类型支持的源码逻辑

往上滑动鼠标,就能看到支持的类型列表了。

确实没有 LocalDateTimeLocalDateLocalTime

mysql-connector-java 5.1.37 开始支持这几个时间类型,前面已经提过,不重复了。

小结一下异常根因:MyBatis 3.5.1 开始不再处理 LocalDateTimeLocalDateLocalTime 的转换,而 mysql-connector-java 5.1.37 之前的版本都不支持这些类型。

把这个来龙去脉捋顺之后,前面的“顺利”是不是又显得有些理所当然了?

暴风雨的来临

版本上线还没两天,该来的还是来了。

给表 tbl_order 里插一条新记录:

INSERT INTO tbl_order VALUES (3, 'asdfgh', NULL, '2024-02-21 20:01:31.111', '2024-02-21 20:02:56.764');

再跑一遍 com.qsl.OrderTest#orderListAllTest

NullPointerException 测试失败堆栈

此时我就想问“小伙”一句:刺不刺激?

碰到异常,继续找原因,还是从异常堆栈入手。

如果 getTimestamp(columnIndex) 拿到的是 NULL,可不就 NullPointerException 了?这代码的严谨性跑哪去了?

修复要紧,先看看哪个版本把这个问题修掉了。

mysql-connector-j Bug#84189 空值修复的提交记录

mysql-connector-java 升级到 5.1.42。

升级后包含 null 值的查询测试通过

问题解决。

经此一役,“小伙”看起来是成长了不少,但眼里的光却暗淡了一些。

mybatis-plus-issues-1114

无意中翻到了这个 issue-1114,跟前面分析的 Conversion not supported for type java.time.LocalDateTime 是不是同一个问题?

只是我们这里默认用的连接池是 HikariCP,而不是 Druid。

结合 druid/issues/3302 来看,如果使用 Druid 作为数据库连接池,出现的异常可能跟前面分析的确实不一样。

所以还是要根据自己的实际情况来分析,不过异常排查的方法论是通用的。

修了“不该修的 Bug”

这是我亲身经历的一次事故,到现在都觉得这锅背得有点冤。

被甩锅的表情包:你背源码

文件分为主文件和附属文件,主文件生成之后,再生成附属文件。

附属文件生成时,会校验它依赖的主文件是否都生成了:只要有任意一个主文件没生成,依赖文件就不能生成并抛出异常。

这个业务逻辑不算复杂吧?

但就在附属文件校验的优化上,我硬生生背上了一个生产事故。

优化前的校验

优化前的依赖校验逻辑代码

listFileGenerateLog 的作用是根据参数查询文件生成记录,具体实现不用管。

这个校验逻辑是什么意思?只要有任意一个主文件生成,校验就算通过。可业务要求的是“主文件全部生成才算通过”呀。

这不是妥妥的 Bug 吗?

优化后的校验

碰到 Bug 你能忍?我是忍不了一点,反手就是一个优化。

优化后的依赖校验逻辑代码

这下是不是就符合业务要求了?

生产异常

中午更新上线之后,系统稳定运行了一段时间,文件也都正常生成,没出什么幺蛾子。

结果晚上 19 点,有个附属文件生成失败,异常提示:依赖的资源 abc_{yyyyMMdd}.txt 未生成。

第一眼看到这个异常,感觉既熟悉又陌生。熟悉的是异常信息的结构,陌生的是 abc_{yyyyMMdd}.txt——这不是文件名吗?

正常情况下这里应该是 fileId,一个自增的正整数,怎么会变成文件名?

脑子里瞬间闪过一个念头:数据库数据有问题?

一查吓一跳,这个附属文件关联主文件的字段值居然是:4356,abc_{yyyyMMdd}.txt,最终修改时间是 2021-08-21 15:22:12.652

4356 文件的文件名就是 abc_{yyyyMMdd}.txt,正常来说,这个关联字段的值应该是 4356。

敢情这个校验 Bug 一直完美地“兼容”了这条脏数据,所以几年下来从没出过异常。

是不是有点那个味儿了?

这可倒好,我把 Bug 修好了,反而冒出问题。你说我是不是手贱?

经此一役,我眼里的光又暗淡了些许。

总结

对组件的升级、对旧代码的调整,都可能牵一发动全身,影响面比你想的大得多。

我的观点很直白:能不动就不要动,改好没绩效,改出问题要背锅,吃力不讨好,又不是不能跑。

如果真到了非改不可的地步,那就必须做足全面测试。云栈社区里也常有类似的升级踩坑记录分享,建议动手之前多逛逛、多看看,把前人踩过的坑先扫一遍,能少交不少学费。





上一篇:27B本地Agent模型后训练拆解:如何在Qwen3.6上练出MCP第二?
下一篇:AlphaSeek:轨迹级自迭代重构LLM量化因子挖掘
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-12 04:22 , Processed in 0.410609 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表