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

说明

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

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

先看一段 mybatis 相关的代码

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

  • 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

其实源码注释说得很明白了:

  • 如果使用了 @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 才引入的类, 也就是说我们是可以拿到真实参数名称的。 那么为什么还是报错呢?

打断点调试

通过断点调试, 我们可以看到我们拿到的参数名称是 arg0arg1, 并不是我们期望的 idtitle

这两个名称在我们前面报错信息的可选值范围内,那么 param0param1 是如何生成的呢?
在这里插入图片描述

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

Java 编译

通过《深入理解 Java 虚拟机》 一说, 我们可以知道, 字段名是放在 class 文件, 而 class 文件是在编译期生成的。编译的命令是 javac 那么我们可以尝试查看 javac 命令是否为我们提供了相关参数来帮我们获取参数名称。

我们在命令行工具上执行 javac 命令, 控制台会显示它的所有 可选参数, 其中有一个参数的说明如下:

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

意思就是 编译程序 时, 如果加上这个参数, 在程序运行过程中,就可以拿到程序中方法中的 参数名称.

那么我们 IDEA 编译如何加上这个参数呢?

在这里插入图片描述
除此之外, 我们还可以通过 IDEA 提供的 jclasslib 插件帮我们翻译 class 文件:

在这里插入图片描述

从上图可以看出, 当我们加上编译参数时, 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 文件存储 方法的参数名称.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值