背景
最近有一个数据统计服务需要升级 SpringBoot
的版本,由 1.5.x.RELEASE
直接升级到 2.3.0.RELEASE
,考虑到没有用到 SpringBoot
的内建 SPI
,升级过程算是顺利。但是出于代码洁癖和版本洁癖,看到项目中依赖的 MyBatis
的版本是 3.4.5
,相比当时的最新版本 3.5.5
大有落后,于是顺便把它升级到 3.5.5
。升级完毕之后,执行所有现存的集成测试,发现有部分 Offset本文来源gao@daima#com搞(%代@#码网@DateTime
类型入参的查询方法出现异常,于是进行源码层面的 DEBUG
找到最终的问题并且解决。
问题复现
项目中有一个查询方法类似下面的演示例子:
public interface OrderMapper { List<Order> selectByCreateTime(@Param("startCreateTime") OffsetDateTime startCreateTime, @Param("endCreateTime") OffsetDateTime endCreateTime); }
对应的 XML
文件中的 SQL
代码段如下:
<select id="selectByCreateTime" resultMap="BaseResultMap"> SELECT * FROM t_order WHERE deleted = 0 AND create_time <![CDATA[>=]]> #{startCreateTime} AND create_time <![CDATA[<=]]> #{e ndCreateTime} </select>
上面的 OrderMapper#selectByCreateTime()
方法在 MyBatis
版本为 3.4.5
的前提下执行没有任何异常,当 MyBatis
版本升级为 3.5.5
后再次执行,在 SQL
执行日志输出正确的前提下返回了一个空集合,具体的内容如下:
查询订单列表:[]
虽然上帝视角是确认了入参解析有问题,但是基于第一次发生异常的日志,其实定位不到具体发生问题的位置,当时条件反射认为有几处地方会出现这类异常( SQL
比较简单,可以排除人为写错 SQL
占位符的情况):
MyBatis
解析OffsetDateTime
类型方法参数的方法有版本兼容问题。MySQL
驱动包解析OffsetDateTime
类型的参数有版本兼容问题。- 前面两种情况混合相互影响导致的,其实这里也可以理解为同一种情况,因为
MyBatis
归根到底是对MySQL
驱动包进行了封装。
当时项目中使用的 mysql-connector-java
版本为 8.0.18
,并未升级为当前的最新版本 8.0.21
,所以当时也有怀疑是低版本 MySQL
驱动包没有兼容解析 OffsetDateTime
类型的参数。
简析MyBatis的执行流程
MyBatis
的源码并不复杂,如果省去分析它的配置和映射文件解析模块,一个查询 SQL
( SelectList
)的执行流程大致如下:
当然,因为问题出现在参数解析部分,只需要关注 StatementHandler
的处理逻辑即可。 StatementHandler
的父类 BaseStatementHandler
构造函数中,初始化了 ParameterHandler
和 ResultSetHandler
实例,提交到 SimpleExecutor
中的 doQuery()
方法中执行,使用了占位符参数的查询会经由 doQuery()
方法中的 prepareStatement()
方法然后调用 PreparedStatementHandler#parameterize()
,最终委托到 DefaultParameterHandler#setParameters()
方法进行参数设置,这个 setParameters()
方法会用到 ParameterMapping
和 TypeHandler
。