MyBatis版本升级引发的线上告警回顾及原理分析

某天晚上,美团到店事业群某项系统服务正在进行常规需求的上线。因为在内部的Plus系统发布时,提示inf-bom版本需要升级,于是我们就将inf-bom版本从1.3.9.6升级至1.4.2.1,如下图1所示:

更新开票请求返回日志,id:{#######},response:{{"code":XXX,"data":{"callType":3,"code":XXX,"msg":"XXXX","shopId":XXXXX,"taxPlateDockType":"XXXXXXX"},"msg":"XXXXX","success":XXXX}}nestedexecptionisorg.apache.ibatis.type.TypeException:Couldnotsetparametersformapping:ParameterMapping{property='updateTime',mode=IN,javaType=classjava.lang.String,jdbcTyp=null,resultMapId='null',jdbcTypeName='null',expression='null'}.Causeorg.apache.ibatis.type.TypeException,Errorsettingnonnullparameter#2withJdbcTypenull.TrysettingadifferentJdbcTypeforthisparameteroradifferentconfigurationproperty.Causejava.lang.ClassCastException:java.time.LocalDateTimecannotbecasttojava.lang.String因为报警这一块代码,属于历史功能,如果失败并不会影响主流程。但在定位期间,如果频繁报警的话,就会造成一定的干扰。因此,我们马上采取了回滚操作,将inf-bom的版本回滚至历史版本,直至报警消失,然后再进行问题的定位和分析。以下章节就是我们对报警原因的定位及原因详细分析的介绍,希望这些思路能够对大家有所启发和帮助。

在回滚完毕后,我们开始具体分析报警产生的主要原因,于是进行了以下几步的排查。

intupdateResponse(@Param("id")longid,@Param("response")Stringresponse,@Param("updateTime")LocalDateTimeupdateTime);第二步,我们查看了Mapper方法对应的XML文件,如下代码段所示,对应的parameterType类型是String,而实际参数的类型包括long、String以及LocalDateTime。

UPDATEinvoice_logSETresponse=#{response},update_time=#{updateTime}WHEREid=#{id}第三步,我们查看了MyBatis上线前后的版本,报警的内容是:MyBatis在处理SQL语句时,发现不能将LocalDateTime转型为String,这一段逻辑在上线前是可以正常运行的,并且上线的业务逻辑对这段历史代码无改动。因此,我们猜测是因为inf-bom的升级,从而导致MyBatis的版本发生了变化,对某些历史功能不再支持了。MyBatis版本上线前后的变化如下表所示:

第四步,我们通过第三步可以得到,在这次inf-bom的版本升级中,MyBatis的版本直接升了两个大版本,因此我们可以基本将原因猜测为MyBatis升级跨度较大,导致部分历史功能没有兼容支持,从而引起线上SQL的更新报错。

第五步,为了具体验证第四步的想法,我们通过UT的方式,将MyBatis的版本不断从3.4.6往下降,直至没有报错的位置。最终的定位是:当MyBatis版本为3.2.3时,线上代码是正常可用的,但只要升一个版本,也就是自3.2.4开始,就开始不兼容目前的用法。不过,我们当时的思路并不是很好,应该从小版本逐个往上升或者使用二分法,可以加速定位版本的效率。

最后,我们定位到了产生报警的根本问题。总的来说,MyBatis版本由inf-bom引入而来,inf-bom从3.2.3升级到了3.4.6版本,而MyBatis自3.2.4开始就不支持目前系统内的SQLMapper的用法,因此在升级后,线上就出现了频繁报警的问题。

问题已经定位,但是还有很多事情我们需要弄清楚。为什么版本升级后就不兼容历史的用法?具体是哪一块内容不兼容?背后的原理又是什么?下文,我们会详细进行分析。

Anspecialremarkaboutthisfeature.Previousversionsignoredthe“parameterType”attributeandusedtheactualparametertocalculatebindings.Thisversionbuildsthebindinginformationduringstartupandthe“parameterType”attributeisusedifpresent(thoughitisstilloptional),soincaseyouhadawrongvalueforityouwillhavetochangeit.

从官网的ReleaseLog可以看到,MyBatis在3.2.4以前的版本,会忽略XML中的parameterType这个属性,并且使用真实的变量类型进行值的处理。但在3.2.4及以后的版本中,这个属性就被启用了,如果出现类型不匹配的话,就会出现转型失败的报错。这也提示我们开发者,在升级版本时,需要检查系统内的XML配置,使类型进行匹配,或者不设置该属性,让MyBatis自行进行计算。

我们看一下配置,首先定义一个通过主键id获取学生信息的方法,仿造系统内的历史代码,我们将parameterType定义为java.lang.String,这和方法对应的参数int并不相同。

在框架初始化阶段,主要包括以下流程,如下图2所示:

在框架初始化阶段,有一些组件会被构建,逐一做个简单的介绍:

在构建Configuration的过程中,会涉及到构建对应每一条SQL语句对应的MappedStatement,parameterTypeClass就是根据我们在XML配置中写的parameterType转换而来,值为java.lang.String,在构建SqlSource时,传入这个参数。如下图3所示:

在SqlSource的构建中,parameterType参数其实是被忽略不用的,并没有继续往下传递,这跟官方的描述是一致的。因为3.2.4之前这个parameterType属性被忽略了,然后就创建了DynamicSqlSource,这个类主要是用于处理MyBatis动态SQL的类。如下图4所示:

在框架初始化的阶段,需要介绍的内容,在3.2.3版本已经介绍完毕。当执行getStudentById方法时,MyBatis的流程如下图5所示。因受限于图片长度,我们对布局进行了一些调整:

在具体执行阶段,也涉及到一些组件,我们需要做简单的了解:

通过图6的代码,我们可以得知,parameterType在初始化阶段未被使用,而是在SQL执行时获取到的,但获取到的类型是parameterObject对应的类型,这个类是用来记录Mapper方法上对应的参数。如下图7所示,它并非在SQL配置文件中标注的java.lang.String。

然后我们通过SqlSourceBuilder的parse方法对SQL以及获取到的类型进行再次处理,其中的流程代码比较长。在这个过程中,我们主要去构建SQL的参数和Java类型的绑定关系,MyBatis依赖这个绑定关系,使用对应的TypeHandler去进行值的转换。

调用链路是SqlSourceParser.parse->内部类ParameterMappingTokenHandler.handleToken->私有方法buildParameterMapping,如下图8中的代码所示。因为当前的parameterType为MapperMethod$ParamMap,经过了多个if判断,判定当前propertyid的propertyType为Object.class类型。接下来,构建SQL的参数和Java类型的绑定关系ParameterMapping,再进行返回。

构建完成的ParameterMapping的结构如下图9中的代码所示,参数id对应的javaType类型为java.lang.Object,对应的TypeHander处理器为UnknownTypeHandler,也就是未找到合适的TypeHandler的兜底选项。

接下来,流程就会流转到Executor,在org.apache.ibatis.executor.SimpleExecutor#doQuery进行查询时,会根据当前的SQL类型,生成对应的StatementHandler。因为我们目前都是用的预编译SQL,因此生成的statementHandler就是PreparedStatementHandler,熟悉JDBC的小伙伴应该马上可以猜到对应的语句是什么类型了。然后,我们对这句SQL语句进行填充,如下图10中的代码所示。我们会通过PreparedStatementHandler的parameterize方法对Statement进行参数化,也就是进行填充。

在Typehandler的流程里,首先会进入BaseTypeHandler,然后在具体设置时,会进入子类的方法。在UnknownTypeHandler,首先会再次对参数parameter进行解析,判断最正确的TypeHandler类型,如下图12中的代码所示:

在resolveTypeHandler方法中,因为已知了参数值的类型,通过Integer这个class在typeHandlerRegistry中寻找对应的TypeHandler,TypeHandlerRegistry是MyBatis启动时内置好的,代表Java对象类型和TypeHandler的映射关系,有兴趣的同学可以进入这个类详细看下。在这个例子中,我们会直接获取到IntegerHandler,如下图13中的代码所示:

在获取到IntegerHandler后,我们就可以使用IntegerTypeHandler的setInt方法,对SQL语句中的参数进行替换。如图14中的代码所示,SQL语句被成功替换:

后续就是执行SQL并处理返回结果,这就不在本文的讨论范围内了。从上文的分析中,我们可以了解到,在3.2.3及以下版本,MyBatis会忽略parameterType,在真正进行SQL转换时,重新根据SQL方法入参类型,然后计算合适的TypeHandler处理器,所以本案例中的代码在3.2.3版本时,它在运行时是正常的。

在前一章节中,我们得知MyBatis在运行SQL阶段重新计算参数对应的TypeHandler,然后进行SQL参数的替换。那么,在版本3.2.4中,MyBatis做了什么改动,从而导致了原有的使用方式变得不可用呢?从官方的ReleaseLog来看,版本3.2.4做了这样的一个改动。

Thisversionbuildsthebindinginformationduringstartupandthe“parameterType”attributeisused

这个意思是说:parameterType会在框架初始化阶段阶段就被使用到。我们将分析的重点放在构建阶段,因为负责处理绑定关系的BoundSql由配置阶段的SqlSource生成,我们主要查看SqlSource的构建,在3.2.4中发生了什么变化。如图15所示,与3.2.3不同,3.2.4首先判断了是否为动态SQL,在非动态SQL情况下,才会将parameterTypejava.lang.String作为参数,传入SqlSource的构造方法。

而后续流程与3.2.3一致,因为parameter类型为java.lang.String,在构建parameterMapping时,使用的类型就是java.lang.String。

因为在框架初始化阶段,SqlSource的ParameterMapping中id对应的类型就是java.lang.String,这就导致在进行SQL语句的替换时,获取到的TypeHandler是StringTypeHandler,如下图17所示:

后面的报错原因就比较好理解了,在调用StringTypeHandler的setString方法时,报出了java.lang.ClassCastException:java.lang.Integercannotbecasttojava.lang.String的错误。

我们总结一下这个案例因:

MyBatis3.2.3版本支持parameterType和实际参数类型不匹配,在执行SQL阶段,动态计算值处理器类型。在大版本升级2个版本号后,parameterType实际的类型开始生效,使用对应这个类型的TypeHandler对SQL进行参数替换,会导致Mapper方法中的参数和XML中的parameterType不匹配时,进而会出现类型转换报错。

这一段排查的经历,对自己后续编写代码及在系统上线时也有一些启发,主要包括以下几个方面:

THE END
1.[新闻直播间]成都铁路公安局“微警务”平台轻松实现线上报警0:00 网络开小差了,请稍后再试 [新闻直播间]成都铁路公安局 “微警务”平台 轻松实现线上报警 选集 更多 《新闻直播间》 20241123 04:00 《新闻直播间》 20241123 05:00 《新闻直播间》 20241123 03:00 《新闻直播间》 20241123 02:00 《新闻直播间》 20241123 01:00 ...http://m.app.cctv.com/vsetv/detail/C10616/2ff6144382dd40c9b60847fe3d455ee3/index.shtml
2.记一次线上内存报警排查过程开始时间大概是从昨天晚上11点多开始的,而且持续到今天上午10点多,事出有因必有妖,下面看一下排查思路和排查过程。 1.查一下16.28的内存使用情况 1628new.jpg 确实如报警所说,内存不够了 2.排查最近是否有新上线服务,导致内存紧张 rpcservice list, ps -ef | tomcat 两个命令发现业务服务有7个,进程存活时间...https://www.jianshu.com/p/7aa8e4fd2d53
3.网上被骗110报案中心,网络诈骗110报警平台,线上举报入口一旦发现自己被骗,可通过邮箱“www110@188.com”进行网上报案【邮件:www110@188.com】如实汇报当时被骗的过程,进行报案追回损失。遭遇网络诈骗,应尽早向公安机关报案。同时也可通过对诈骗网站、诈骗分子的即时通讯账号举报,防止其他人继续遭受损失。注意事项如下:1、请立即拨打110或96110,第一时间向属地公安部门报案。2...https://www.wenjuan.com/s/UZBZJviK0oF/
4.6月1日起,河南全面推行轻微交通事故“线上视频快处”? 使用简单快捷。当事人只需点击【事故视频快处】现场报警,3到7分钟可以完成处理,省去现场等待民警出警或者驾车去线下事故快处中心往返跑腿时间。 ? 处理公开公正。通过视频与接警人员“线上见面”,证据固定和信息采集由接警人员视频远程完成,更利于当事人打消顾虑、快速撤离现场。 http://news.hnr.cn/snxw/article/1/1795622319502217217
5.校园一键式紧急报警系统与公安机关100%联网项目实施方案防暴恐演练演习联网处突应急系统集安全演练演习,安全教育,联网报警、移动报警、电话线报警、报警定位,警情群播,多警演练,一警多发,多区编组,一点报警,多处联动,一呼百应,群防群治,地图弹窗,视频联动,监控弹屏,实时对讲,远程广播为一体,具有报警及时性、方便性、隐蔽性等特点,极大的解决了现有报警的问题缺陷,满足...http://www.allyking.com/3g/show.asp?m=117&d=30
6.架构师训练营第十一周课程笔记及心得即使是经过严格的测试,软件部署到线上服务器之后还是经常会出现各种问题,甚至根本无法启动服务器。主要原因是测试环境和线上环境并不相同,特别是应用需要依赖的其他服务,如数据库,缓存、公用业务服务等,以及一些第三方服务,如电信短信网关、银行网银接口等。也许是接口变化导致的通信失败;也许是配置错误导致连接失败;也许...https://xie.infoq.cn/article/3805e7eb152adc372a15fd4ad
1.怎么线上报警然而,请务必注意,在进行线上报警时,务必要尽可能详尽且准确地提供与案件有关的各类信息,例如案发时间...https://www.66law.cn/question/answer/40470355.html
2.后端线上服务监控与报警方案如何实现软件线上环境故障报警后端线上服务监控与报警方案 个人分类:php 因原文服务区报错无法打开,此处引用本文转载自网址:http://www.tuicool.com/articles/iIV3qqq。本文只作为笔记,请大家去原文处查看。 一、背景 1、上线期间服务稳定性观察较困难 一个功能上线后,其实研发心里根本没底儿,不知道这个功能上线以后是不是真的没问题;有经验一...https://blog.csdn.net/u010412301/article/details/83089844
3.洛阳警方已开通线上微信报警服务新闻中心近日,有网友在洛阳网《百姓呼声》频道发帖称:洛阳市公安局开通有线上报警服务吗?比如可以通过发短信或者微信报警吗? 洛阳市公安局回复:我局110报警台全天24小时受理公众报警、紧急求助和投诉,为进一步拓宽群众报警渠道,我局已开通线上微信报警服务,目前暂未开通短信报警功能。微信报警具体操作流程如下: ...https://news.lyd.com.cn/system/2023/11/20/032435645.shtml
4.线上可视化,精益现场管理—基于透明化嘲的SQCDP可视化管理...梳理SQCDP可视化指标,构建模板,配置场景,进行各指标的线上透明化展示,在此基础上对指标的实时结果进行监控,暴露出的问题自动触发报警,且可结合线上问题处理功能在线进行问题的下发与跟踪处理。 图2 SQCDP管控实现逻辑 01、指标梳理与定义,构建看板的牢固根基 ...https://www.asktempo.com/news/industry-information/1463.html
5.一次线上tomcat应用请求阻塞的排查经过腾讯云开发者社区一次线上tomcat应用请求阻塞的排查经过 今天早上,收到一个报警,有个服务器的http往返时延飙升,同时曝出大量404,很是折腾了一番,特记录下思考和排查经过。 1.这是单纯的时延增大,还是有什么其他情况还未掌握? 因为不知道是只有时延变大而已,还是同时有别的情况,第一反应是先看日志有没有异常。https://cloud.tencent.com/developer/article/1065416
6.从上牌到安装各种配件,九号电动车B90评测:上车即走,下车即锁参数上都不敢过于马虎,这对于小白用户十分友好,不用担心被骗;在安全性上,九号和小牛均以智能化切入,远程查看车子实时位置、异动远程报警等功能都能做到“安心使用”,同时两者还推出了盗车险服务,例如九号买车便送一年盗抢险,在此期间车辆被盗则有相关赔付;最后在体验层面,九号和小牛均在细节方面做了优化以此来提升...https://www.yoojia.com/ask/17-11579777107581017677.html
7.网络报警平台微信110线上报案中心入口反诈防骗网遇到网络诈骗请拨打96110或在网站一键报警,微信110报警服务平台快速报警,与110接警员实现文字、语音、电话等多媒体互动,以快制快拦截。如遇诈骗,请保留转账凭证、汇款凭证、帐号信息、聊天记录、语音信息等证据,方便警方破案。http://wenanwangzhan.com/
8.上一篇:an在张力电子围栏主机轮询状态,防区号下对应显示该防区的状态,如果这个防区有多根线上有报警,会优先显示最低线号的警情到防区状态位置。按“2↑”“8↓”可以锁定显示某一线号的值和状态,方便调试和查看。 3、报警类型说明 4、主机菜单说明 三、张力围栏键盘操作说明 ...https://www.alean.cn/faq/details-164-40.html