阐明

本文涉及的相关软件版别如下:

  • mybatis 3.4.x
  • HotSpot JDK1.8
  • Windows 11
  • IDEA 2022.3

一、关于 mapper 的定义和运用

今日一个朋友丢给我如下一段代码: 然后跟我讲为什么本地是好好的, 发布到线上履行就报错。

  • BlogMapper.java
public interface BlogMapper {
    List<Blog> select(Integer id, String title);
}
  • BlogMapper.xml
<select id="select">
    select * from my_blog
    <where>
      <if test="id!=null">
        id = #{id}
      </if>
      <if test="title!=null">
        and title = #{title}
      </if>
    </where>
</select>
  • 报错信息

报错信息也很容易了解: mybatis 动态生成 sql 时,提示参数 id 找不到, 只找到了 [arg1, arg0, param1, param2] 这四个可用的参数称号

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

二、查看 mybatis 官方文档

mybatis 官网文档阐明如下:

假如你的映射办法承受多个参数,就能够运用这个注解自定义每个参数的姓名。否则在默认情况下,除 RowBounds 以外的参数会以 “param” 加参数方位被命名。例如 #{param1}, #{param2}。假如运用了 @Param("person"),参数就会被命名为 #{person}

因而咱们上面的运用方式显着是不对的, 理论上讲这段程序不管在 线上 仍是 本地编辑器 运转, 都是会提示相同的报错的。

三、mybatis 源码剖析

关于 mybatis 是怎样把 mapper.java参数名 绑定到 mapper.xml占位符 上的, 能够直接看 ParamNameResolver.java 这个类

源码中要点内容现已 标识 出来, 咱们只需要关注 要点1要点2

本地运行好好的 Java 程序, 一发布到线上就报错的灵异事件终于让我碰到了

其实源码注释说得很理解了:

  • 假如运用了 @Param("称号") 注解, 就用注解中的称号;
  • 否则, 就调用 isUseActualParamName() 办法;
  • 假如仍是拿不到, 就由 mybatis 生成。
public static final boolean parameterExists;
  static {
    boolean available = false;
    try {
      Resources.classForName("java.lang.reflect.Parameter");
      available = true;
    } catch (ClassNotFoundException e) {
      // ignore
    }
    parameterExists = available;
  }
// java.lang.reflect.Parameter
private String getActualParamName(Method method, int paramIndex) {
    if (Jdk.parameterExists) {
      return ParamNameUtil.getParamNames(method).get(paramIndex);
    }
    return null;
}

从上面的源码能够看出, 只需程序能够加载到 java.lang.reflect.Parameter 这个类, 咱们就能拿到参数称号。

/**
 * Information about method parameters.
 *
 * A {@code Parameter} provides information about method parameters,
 * including its name and modifiers.  It also provides an alternate
 * means of obtaining attributes for the parameter.
 *
 * @since 1.8
 */
public final class Parameter implements AnnotatedElement {
}

从这个类的注释能够看出, 这个类是 JDK1.8 才引进的类, 也就是说咱们是能够拿到真实参数称号的。 那么为什么仍是报错呢?

四、断点调试 mybatis 源码

经过断点调试, 咱们能够看到咱们拿到的参数称号是 arg0arg1, 并不是咱们希望的 idtitle

本地运行好好的 Java 程序, 一发布到线上就报错的灵异事件终于让我碰到了

这两个称号在咱们前面报错信息的可选值范围内,那么 param0param1 是怎样生成的呢?

本地运行好好的 Java 程序, 一发布到线上就报错的灵异事件终于让我碰到了

param0param1mybatis 为咱们生成的, 用来兜底的, 那么 arg0arg1 是怎样生成的呢 ?

五、Java 编译的相关常识

经过《深入了解 Java 虚拟机》 一说, 咱们能够知道, 字段名是放在 class 文件, 而 class 文件是在编译期生成的。编译的指令是 javac 那么咱们能够尝试查看 javac 指令是否为咱们供给了相关参数来帮咱们获取参数称号。

咱们在指令行工具上履行 javac 指令, 控制台会显现它的所有 可选参数, 其间有一个参数的阐明如下:

-parameters 生成元数据以用于办法参数的反射

意思就是 编译程序 时, 假如加上这个参数, 在程序运转过程中,就能够拿到程序中办法中的 参数称号.

那么咱们 IDEA 编译怎样加上这个参数呢?

本地运行好好的 Java 程序, 一发布到线上就报错的灵异事件终于让我碰到了
除此之外, 咱们还能够经过 IDEA 供给的 jclasslib 插件帮咱们翻译 class 文件:

本地运行好好的 Java 程序, 一发布到线上就报错的灵异事件终于让我碰到了

从上图能够看出, 当咱们加上编译参数时, class 文件中多了一个描绘符. 也就是咱们办法参数的元数据信息.

五、求证

根据上面的剖析, 我立马让我朋友查看了他电脑上 IDEA 编译相关配置, 然后他反应说道, 他之前开发过程中遇到了一些问题(详细什么问题忘记了), 然后稀里糊涂就加了上述配置, 后来也一向没删除,最后今日就碰巧遇上了文中所描绘的 “灵异事件”。

六、强化结论

当对问题以及问题产生的原因有了满足的认知后, 我就明确了检索方向, 然后顺藤摸瓜找到了官方资料来证明咱们的观点, 概况能够参阅 Access to Parameter Names at Runtime

这里我只摘录了其间一段话:

The proposed approach is to create an optional new JVM attribute in version 52.0 class files to store information about the parameters of a JVM-level method

大致意思是在 JDK8 版别中, 能够 选择性class 文件存储 办法的参数称号.