浅谈浅谈Mybatis版本升级踩坑及背后原理分析版本升级踩坑及背后原理分析
主要介绍了浅谈Mybatis版本升级踩坑及背后原理分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学
习学习吧
1、背景、背景
某一天的晚上,系统服务正在进行常规需求的上线,因为发布时,提示统一的pom版本需要升级,于是从 1.3.9.6 升级至 1.4.2.1。
当服务开始上线后,开始陆续出现了一些更新系统交互日志方面的报警,属于系统辅助流程,报警下图所示, 具体系统数据已脱敏,内容是Mybatis相关的报警,在进行类型转换的时候,产生了强转错
误。
更新开票请求返回日志, id:{#######}, response:{{"code":XXX,"data":
{"callType":3,"code":XXX,"msg":"XXXX","shopId":XXXXX,"taxPlateDockType":"XXXXXXX"},"msg":"XXXXX","success":XXXX}}
nested execption is org.apache.ibatis.type.TypeException: Could not set parameters for mapping: ParameterMapping{property='updateTime', mode=IN, javaType=class java.lang.String,
jdbcTyp=null,resultMapId='null',jdbcTypeName='null',expression='null'}.Cause org.apache.ibatis.type.TypeException,Error setting non null parameter #2 with JdbcType null. Try setting a
different Jdbc Type for this parameter or a different configuration property.Cause java.lang.ClassCastException:java.time.LocalDateTime cannot be cast to java.lang.String
报警的一块代码,属于历史功能,失败并不会影响主流程,但在定位期间,会频繁报警,造成一定的干扰,因此当时首先采取回滚操作,将统一的pom版本回滚至历史版本,报警消失,再进行问题的定
位和分析。
以下章节是对报警原因的定位及原因详细分析的介绍。
2、报警原因定位、报警原因定位
首先是具体的报警原因:
由于mybatis版本由inf-bom引入而来,在inf-bom升级后,由3.2.3 升级至了 3.4.6版本,而Mybatis自3.2.4开始就不支持目前系统内的SQL Mapper的用法,因此上线后,线上出现频繁报警。接下来是定位
的过程。
回滚完毕后,开始具体分析报警产生的主要原因,进行了以下几步的排查。
1.查看了报警的Mapper方法,如下代码所示, 这个是接收返回参数,根据主键id,更新具体响应内容和时间的代码,入参有3个,类型分别为long, String 和 LocalDateTime
int updateResponse(@Param("id")long id, @Param("response")String response, @Param("updateTime")LocalDateTime updateTime);
2.查看了Mapper方法对应的XML文件,如下代码,对应的parameterType类型是String,而实际参数的类型有Long,有String,也有LocalDateTime。
<update id="updateResponse" parameterType="java.lang.String">
UPDATE invoice_log
SET response = #{response}, update_time = #{updateTime}
WHERE id = #{id}
</update>
3.查看了Mybatis上线前后的版本,因为报警的内容是Mybatis处理sql语句时,发现不能将LocalDateTime转型为String,这一段逻辑在上线前是ok的,上线的业务逻辑对这段历史代码无改动,因此猜测是
统一pom的升级,导致Mybatis的版本发生了变化,某些历史功能不支持了。 mybatis版本上线前后的变化,1.3.9.6对应的版本是3.2.3,1.4.2.1对应的版本是3.4.6。
4.通过第3步可以得到,在这次inf-bom的版本升级中,mybatis3的版本直接升了两个大版本,因此可以基本将原因猜测为 Mybatis升级跨度大,导致部分历史功能没有兼容支持,引起的线上sql更新报
错。
5.为了具体验证第4步的想法,通过UT的方式,通过将Mybatis的版本不断从3.4.6往下降,直至没有报错位置,最终定位是Mybatis版本为3.2.3时,线上代码是正常可用的,只要升一个版本也就是自3.2.4
开始,就开始不兼容目前的用法。(这个当时思路不是很好,应该从小版本逐个往上升,可以去加速定位版本的效率)
最后定位报警原因,由于mybatis版本由统一pom引入而来,在统一pom升级后,由3.2.3 升级至了 3.4.6版本,而Mybatis自3.2.4开始就不支持目前系统内的SQL Mapper的用法,因此上线后,线上出现
频繁报警。
报警原因已定位,但为什么版本升级后就不兼容历史的用法,并且具体不兼容的是哪一块内容,背后的原理又是什么,请看接下来章节的详细分析。
3、详细分析、详细分析
3.1 Mybatis 升级升级3.2.4版本的官方版本的官方Release公告公告
首先从报错的原因上来看,Caused by: java.lang.ClassCastException: java.lang.LocalDateTime cannot be cast to java.lang.String ,是Mybatis在构建sql语句时,发现时间字段 类型为LocalDateTime 不
能强制转为String类型。 这个SQL XML的配置在3.2.3的版本是正常可以用,那么首先是从Mybatis 的 release log上查看3.2.4版本 发生了什么变化。
An special remark about this feature. Previous versions ignored the "parameterType" attribute and used the actual parameter to calculate bindings. This version builds the binding
information during startup and the "parameterType" attribute is used if present (though it is still optional), so in case you had a wrong value for it you will have to change it.
从官网的Release Log可以看出,Mybatis在3.2.4以前的版本,是忽略XML中的parameterType这个属性,并且使用真实的变量类型进行值的处理,在3.2.4及以后的版本中,这个属性会被启用,因此如果
出现类型不匹配的话,就会出现转型失败的报错,也提示我们开发者在升级到这个版本及以上时,需要检查系统内的XML配置,使类型相匹配,或者不设置该属性,让Mybatis自行进行计算。
从以上内容,可以了解到,在版本升级后,mybatis在构建sql语句,获取字段值的时候逻辑发生了变化,那么接下来通过一个普通的示例,了解mybatis在获取字段值这一块的具体代码流程是怎样的,以
3.2.3版本为例。
3.2 以版本以版本3.2.3为例,为例,mybatis构建构建SQL语句过程的原理分析语句过程的原理分析
首先,先看以下配置,定义了一个通过主键id获取学生信息的方法,仿造系统内的历史代码,也将parameterType定义为 java.lang.String 和 方法对应的参数 int 并不相同。
public StudentEntity getStudentById(@Param("id") int id);
<select id="getStudentById" parameterType="java.lang.String" resultType="entity.StudentEntity">
SELECT id,name,age FROM student WHERE id = #{id}
</select>
mybatis框架要做的事情就是在运行getStudentById(2)的时候,将 #{id}进行替换,使SQL语句变成 SELECT id,name,age FROM student WHERE id = 2 。Mybatis要将SQL语句完整替换成带参数值的版
本,需要经历框架初始化以及实际运行时动态替换两个部分。因为Mybatis的代码非常多,接下来主要阐释和本次案例相关的内容。
在框架初始化阶段,主要有以下流程,如下图所示