
最近遇到一个挺有意思的案例,正好和 Java 项目中常用的 ORM 框架有关。一个老项目,数据库用的是 MySQL 5.7.36,ORM 框架是 MyBatis 3.5.0,mysql-connector-java 驱动版本是 5.1.26。
组里新来了一个精力充沛、喜欢折腾的小伙子,他觉得 MyBatis 用起来不够简洁,代码量偏多,于是打算把项目中的 MyBatis 替换成 MyBatis-Plus,顺便展示一下自己的技术实力。

MyBatis-Plus 替换 MyBatis 初尝试
先准备一张表 tbl_order,初始化两条数据。
DROP TABLE IF EXISTS `tbl_order`;
CREATE TABLE `tbl_order` (
`id` bigint(0) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`order_no` varchar(50) NOT NULL COMMENT '订单号',
`pay_time` datetime(3) DEFAULT NULL COMMENT '付款时间',
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT = '订单';
INSERT INTO `tbl_order` VALUES (1, '123456', '2024-02-21 18:38:32.000', '2024-02-21 18:37:34.000', '2024-02-21 18:40:01.720');
INSERT INTO `tbl_order` VALUES (2, '654321', '2024-02-21 19:33:32.000', '2024-02-21 19:32:12.020', '2024-02-21 19:34:03.727');
为了模拟替换过程,我们用 MyBatis-Plus 搭建一个简单的示例 Demo。注意,我们只替换 ORM 框架,其他组件版本暂时不变。MyBatis-Plus 版本就用小伙子选择的 3.1.1,mysql-connector-java 保持为 5.1.26。
此时运行单元测试 OrderTest#orderListAllTest,会报错。关键异常信息如下:
Caused by: java.sql.SQLException: Conversion not supported for type java.time.LocalDateTime
不支持的转换类型:java.time.LocalDateTime。谁不支持?是 mysql-connector-java 驱动不支持。
那么,哪个版本的 mysql-connector-java 才开始支持 java.time.LocalDateTime 呢?答案是:5.1.37。

升级驱动到 5.1.37
将 mysql-connector-java 升级到 5.1.37,再次执行测试。

这次不再报错,查询结果也正确。看起来,把 MyBatis 替换成 MyBatis-Plus 似乎就顺利完成了?顺利得有点让人怀疑。

深入探究异常根源
我们回过头仔细看看那个异常:Conversion not supported for type java.time.LocalDateTime。
在 MyBatis-Plus 替换 MyBatis 之前没有这个异常,替换之后才出现。这难道不是 MyBatis-Plus 的问题吗?
如何找到根本原因?直接从异常堆栈入手是最快的方法。

顺着堆栈点进去,你会发现核心代码非常简单,就在 LocalDateTimeTypeHandler 里。
public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getObject(columnName, LocalDateTime.class);
}
public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getObject(columnIndex, LocalDateTime.class);
}
public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getObject(columnIndex, LocalDateTime.class);
}

等等,大家注意看图中左上角 MyBatis 的版本,是 3.5.1,并不是我们项目最初的 3.5.0。
有小伙伴可能会问:我们不是用 MyBatis-Plus 替换了 MyBatis 吗,怎么还有 MyBatis?这个问题问得好,建议你先看看 MyBatis-Plus 的官方说明。

MyBatis-Plus 是基于 MyBatis 的增强工具,它本身会依赖特定版本的 MyBatis。既然基于 MyBatis 3.5.0 没有抛异常,而基于 3.5.1 却抛了异常,那说明 LocalDateTimeTypeHandler 在 3.5.1 版本中肯定做了调整。
我们来看看具体调整了什么。

看出区别了吗?MyBatis 3.5.0 会主动处理 LocalDateTime 类型的转换(将 java.sql.Timestamp 转换成 java.time.LocalDateTime)。然而,从 MyBatis 3.5.1 开始,它不再处理 LocalDateTime(还包括 LocalDate、LocalTime)类型的转换了,而是将转换工作完全委托给 JDBC 驱动(也就是 mysql-connector-java)去实现。
巧了不是,我们项目里用的 mysql-connector-java 5.1.26 恰好不支持 LocalDateTime 这个类型。

那 5.1.26 版本支持哪些类型呢?我们同样从异常堆栈入手查看源码。

点进去可以看到下图,向上滑动鼠标就能看到支持的类型列表,确实没有 LocalDateTime、LocalDate 和 LocalTime。

总结一下这个异常的根因:MyBatis 从 3.5.1 版本开始,不再处理 LocalDateTime、LocalDate 和 LocalTime 的转换,而 mysql-connector-java 在 5.1.37 版本之前都不支持这些类型。版本不匹配导致了这次异常。
弄清楚来龙去脉之后,是不是觉得之前的“顺利”只是假象?真正的“暴风雨”还在后面。
新的风暴:空指针异常
版本上线没两天,该来的问题终究还是来了。我们往 tbl_order 表中插入一条 pay_time 为 NULL 的记录:
INSERT INTO tbl_order VALUES (3, 'asdfgh', NULL, '2024-02-21 20:01:31.111', '2024-02-21 20:02:56.764');
再次执行查询测试。

新的异常出现了:NullPointerException。此刻就想问一句,刺不刺激?

碰到新异常,继续找原因。同样从异常堆栈入手。

看出问题了吗?在 JDBC42ResultSet.getObject 方法中,如果 getTimestamp(columnIndex) 得到的结果是 NULL,那么 toLocalDateTime() 方法就会抛出 NullPointerException。这代码的严谨性有待提高啊。
先别急着吐槽,修复问题要紧。我们看看哪个版本修复了这个问题。

将 mysql-connector-java 驱动升级到 5.1.42。

问题终于得以修复。经此一役,小伙子眼里的光似乎都暗淡了不少。
总结与思考
这次经历给我们提了个醒:对现有组件进行升级或对旧代码进行调整,往往牵一发而动全身。
MyBatis-Plus 替换 MyBatis 本身并不复杂,但背后隐含着复杂的版本依赖链。在这个案例中,问题链条是:
- MyBatis-Plus 3.1.1 依赖了 MyBatis 3.5.1+。
- MyBatis 从 3.5.1 开始,将
java.time.* 类型的转换工作交给了 JDBC 驱动。
- 项目原用的
mysql-connector-java 5.1.26 不支持 java.time.LocalDateTime,导致转换异常。
- 升级驱动到 5.1.37 后,虽然支持了类型,但 5.1.42 之前版本存在处理
NULL 值的空指针 Bug。
所以,我的观点是:对于稳定运行的老项目,能不动就不要动。改好了通常没有显性绩效,改出了问题却可能要背锅,吃力不讨好。如果到了不得不改的地步,就必须进行全面的测试,尤其要关注各组件的版本兼容性,比如 MySQL 驱动与 ORM 框架之间的协作细节。技术选型和升级,远不止替换一个依赖那么简单。
希望这个真实的技术踩坑案例对你有帮助。你在技术升级中遇到过哪些意想不到的“坑”呢?欢迎在 云栈社区 交流讨论。