关于mybatis 的parameterType问题分析

1、背景及问题

公司某个应用,进行数据源切换,公告数据接入只读库,导致真线出现部分sql执行出现错误;报错信息如下:
图1、错误信息
图1、错误信息
问题代码定位:图2  RaRuleDao代码
图3、RaRuleMapper.xml

问题:

1、嗯,原因基本上很明确了是因为dao层的入参和mapper里的入参类型不一致,但是代码是之前的本次没有任何修改,为什么之前能够正常运行?

二、源码分析

根据异常堆栈错误信息对源码分析,源码分析比较多,可以直接跳过直接看第三部分;

2.1 mybatis运行原理

首先讲下mybatis初始化和实际执行sql的大概流程,后续会涉及到这两个流程,结合了网上一篇画的比较好的流程图,如下:

图4、初始化加载阶段
图5、 实际执行sql的步骤
在具体执行阶段,涉及到的一些组件,我们需要做简单的了解:

SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能。
Executor:MyBatis执行器,这是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护。
BoundSql:表示动态生成的SQL语句以及相应的参数信息。
StatementHandler:封装了JDBC Statement操作,负责对JDBC statement的操作, 如设置参数、将Statement结果集转换成List集合等等。
ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数。 TypeHandler:负责Java数据类型和JDBC数据类型之间的映射和转换。

2.2 下面介绍下涉及的部分关键源码

下面将以上文涉及到的sql语句进行分析 即RaRuleDao::getByDistrictCode
先从初始化加载开始;
step1、 读取所有的mapper文件入口 org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory 521行
 图6 SqlSessionFactoryBean类
step2、org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode 解析paramterType,这里对应mapper.xml文件中的id:getByDistrictCode, 中的parameter为long,这里解析出来的parameterTypeClass为Java.lang.Long类型
图7  XMLStatementBuilder类
step3、 org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode, 这里会将sql语句进行解析,例如会将里面的入参替换为?形式
这是关键步骤,这里构造sqlsource的时候使用到了parameterType,继续看下面
 图8  XMLScriptBuilder类
step4、 org.apache.ibatis.mapping.ParameterMapping.Builder#resolveTypeHandler 这里将parameterType转化为了一个TypeHandler (类型处理器)类型处理器的作用是为后续将sql语句中的?替换为实际入参,即图5流程图中的parameterHandler参数化sql语句;
本次分析的核心关键步骤:mybatis在初始化加载的时候,将mapper.xml中的parameter中的类型(TypeHandler)保存到sqlsource中这里为java.lang.Long类型,对应的typeHandler为LongTypeHandler
图9  ParameterMapping类 step5、 最后所有数据存在最后的MappedStatement 即builderAssistant.addMappedStatement org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
图10  XMLStatementBuilder类
step6、 将当前解析的sql信息,按照保存到org.apache.ibatis.session.Configuration#mappedStatements 其中key为mapper.xml中的 namespace+id
图11  Configuration
------------------------- 以上是mybatis的初始化加载的关键步骤,下面介绍mybatis在运行期执行具体sql语句的关键步骤------------------------------
step7、 mybatis执行具体sql的源码解析关键部分: org.apache.ibatis.session.defaults.DefaultSqlSession#selectList (java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)
图12 DefaultSqlSession 类 step8、 获取boundSql,对应流程可以参考
图13 获取boundSql 即初始化加载解析出来的sql信息
图13 获取boundSql 即初始化加载解析出来的sql信息
图13 获取boundSql 即初始化加载解析出来的sql信息
step9、 org.apache.ibatis.executor.SimpleExecutor#doQuery
 图14  SimpleExecutor类
step10、 org.apache.ibatis.executor.SimpleExecutor#doQuery进行查询时,会根据当前的SQL类型,生成对应的语句处理器StatementHandler。
这里用的预编译SQL,因此生成的statementHandler就是PreparedStatementHandler;
图15、  SimpleExecutor
这里先暂停,在初始化中提到的TypeHandler接口,看下TypeHandler接口的构造以及实现类,很重要。。。
 图16 TypeHandler接口
TypeHandler接口,进行sql参数化的时候用到setParameter,第三个入参parameter就是实际从dao传入的参数,不同的入参需要不同的解析,例如入参为long、string、bigInteger分别对应
LongTypeHandler、StringTypeHandler和BigIntegerTypeHandler,如下图
图17  TypeHandler实现类
图17  TypeHandler实现类
接上继续源码分析
step11、 使用org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize 参数化,调用org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters 默认参数处理器 设置参数
图18  PreparedStatementHandler类
 图19   DefaultParameterHandler类
这里获取到的typeHandler,就是前面在初始化加载的时候,获取到的LongTypeHandler,参考图17 parameter入参类型为Long,但是实际入参“339900”是String,因此导致出现图一的报错;
问题原因:在于typeHandler处理器的入参类型和实际入参不匹配导致
 图20  实际入参

三、分析与总结

3.1 分析
本次改动,报错的sql没有任何改动,按照上述源码分析,那么之前的线上应该也会报错,查看近期的日志,并没有任何异常,那为什么出现报错呢?将分支切换到本次修改前的一个分支梳理下本次的改动点,由于做数据源切换,原来的sql查询使用的是端点封装的,并且原来的dao层和center公用同一个模块,结构层级不清晰,本次新增了dal模块;
本次修改了mybatis相关的版本号,version从3.2.3升级到3.4.4;

<dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>${mybatis.version}</version>
 </dependency>

3.2 版本对比
step1、3.2.3是在实际执行sql,重新根据SQL方法入参类型,然后计算合适的TypeHandler处理器
在这里插入图片描述在这里插入图片描述
step2、3.4.4是在实际执行sql,使用的是框架初始化阶段得到的parameterType
在这里插入图片描述
在这里插入图片描述
step3、 github看官网的说明
在这里插入图片描述
github说明

MyBatis 3.2.4及以上版本中parameterType会在框架初始化阶段阶段就被使用到,然后得到对应的typeHandler, 实际执行sql的时候使用该typeHandler进行参数化处理,如果不一致会报错;
MyBatis 3.2.3及以下版本会忽略parameterType,在真正进行SQL转换时,重新根据SQL方法入参类型,然后获取对应的TypeHandler处理器,所以本文中的代码在3.2.3版本时,它在运行时是正常的;

3.3 解决方案:
版本升级,不进行降级处理
1、修改mapper里面的parameterType类型,保持和dao层的一致
2、将mapper里面的parameterType类型删除,mybatis在解析的时候, 会自动使用dao的实际入参类型进行解析获取typeHandler(建议选择第一种)
注:如果mapper中没有parameter ,初始化的时候得到的是UnknownTypeHandler,在执行sql进行参数化的时候,会根据实际的入参得到对应TypeHandler
在这里插入图片描述

致谢

在排查问题过程中,查看了很多资料,包括写这篇文章也参考了很多文章,非常感谢各位大佬,写这篇文章的目的,是为了给自己留下足迹,也为了以后遇到同样问题的想要深入了解原因的童鞋提供点思路;

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值