mybatis 参数绑定 useActualParamName

360 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 19 天,点击查看活动详情

夜来风雨声,花落知多少。

1 前言

Mybatis 作为在项目开发中经常使用的 ORM 框架,在开发过程中经常会遇到参数绑定的问题,比如绑定异常 BindingException,比如下图所示:

Cause: org.apache.ibatis.binding.BindingException:
Parameter 'nickname' not found. Available parameters are [arg1, arg0, param1, param2]

在 mybatis 中,Mapper 中的定义执行方法和 Xml 中的执行 sql 关联关系,是通过 namespace + id 来做绑定。

以上的报错信息具体案例如下图所示:

// 接口层内容
List<User> queryUserList(String username, String nickname);
// xml 内容
select * from `tb_user`
where username = #{username} and nickname = #{nickname}

可能大家都知道这个是因为在接口层传递查询参数时,没有给参数加 @Param, 因此造成了 sql 执行时找不到参数造成的。但是这个过程中为什么会出现这样参数绑定的问题呢?在本文中将以 Mybatis-plus 3.5.1 和 jdk 1.8 环境说明一下为什么会出现这种情况以及如何在不加注解的情况下正常运行改查询语句。

2 Mybatis 执行分析

在 mybatis 的执行过程中,其执参数解析顺序如下:

  • 1 执行 mapper 层的方法 userMapper.queryUserList
  • 2 通过 MapperMethod 来处理参数的解析和属性的参数的填充,并执行 MapperMethod 中的方法来执行。
  • 3 SqlSessionTemplate.SqlSessionInterceptor.invoke
  • 4 DefaultSqlSession.selectList,所有的查询都是走这个方法,不论是单个还是多个
  • 5 执行 CachingExecutor.query 查询方法,CachingExecutor 是一个装饰器,其包装了 SimpleExecutor,BatchExecutor, ReuseExecutor
2.1 ParamNameResolver

在 mybatis 中有一个 ParamNameResolver,负责处理参数名称和值的解析,先来看一下构造方法: 根据构造方法可以判断 mybatis 获取参数解析的过程。

2.2 MapperProxy & MapperMethod

在 MapperMethod 的构造方法中会创建 MethodSignature,后者会创建 ParamNameResolver

Mybatis 通过 JDK 的动态代理方式,在启动加载配置文件时,根据配置 mapper 的 xml 去生成 Dao 层的接口实现。

当执行 sql 时,会调用代理类 MapperProxy 中的 invoke 方法, 紧接着会调用到 MapperMethod 类中的 execute 方法,由于查询返回结果是多条,因此会执行 MapperMethod.executeForMany 方法。

2.3 DynamicSqlSource & SqlNode

executor.query 方法后,会首先执行 MappedStatement.getBoundSql 方法,最终会执行 DynamicSqlSource.sqtBoundSql 方法。

rootSqlNode 中 执行 apply 方法,在 IfSqlNode 可以看到参数的判断和参数名称。sqlNode 是一个接口,实现类有多种,这里是条件判断,所以是 IfSqlNode 。具体的实现类还有 SetSqlNode 、TrimSqlNode、WhereSqlNode 等。

在进一步看 ExpressionEvaluator 中的 evaluateBoolean 方法可以看到判断的表达式为 nickname != null ,但是 parameterObject 中却没有对应的参数名称。

接下来会调用 OgnlCache.getValue 方法,使用 Ognl 解析条件判断 nickname != null, 因此在进一步执行就会报参数绑定失败的异常。

当我们采用 @Param 注解修饰参数名称时,就会看到 parameterObject 已经有了 username 和 nickname 参数以及值。

2.4 SqlSession

sqlsession 中的查询语句都是执行的 selectList 方法:

最终会执行到 BaseExecutor 的 query 方法,可以看到已经找到了 xml 所在的路径,以及文件完全限定名+方法名称,parameter 中的参数一共有四个, arg0 、arg1 和 param1 、param2 , 由此我们知道在 xml 中的参数名称可以是 argn 和 paramn ,只不过 arg 是从下标 0 开始,而 param 是从下标 1 开始的。

再继续看 Boudsql 方法,从截图中可以看到 rootSqlNode 中的 contents 对象中已经把 sql 语句分解成多个判断条件。

4 使用 useActualParamName

既然已经知道了问题的原因,那么有没有办法解决呢,Mybatis 3.4 以后添加了 useActualParamName,含义是使用实际参数名,这个需要 jdk1.8 支持,需要在代码编译时,保留原始的参数名称,而不是 var1,var2 …。Mybatis3.5.5 useActualParamName 默认是开启的,因此只需要在编译项目时添加一下配置即可:

使用真实参数 项目编译是添加 -parameters 参数,这样在项目编译后,方法的参数名称依旧是编码时的参数名,而不是编译后的 arg1,arg2 的参数。

5 总结

本文以 mybatis 使用过程中常见的参数绑定 BindException 为例解析了错误发生的原因,并在这个过程中分析了 sql 参数解析的逻辑,最后结合 jdk 1.8 和 useActualParamName 解决了参数绑定问题。