我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Mybatis Mapper中多参数方法不使用@param注解报错的解决

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Mybatis Mapper中多参数方法不使用@param注解报错的解决

在使用低版本的Mybatis的时候,Mapper中的方法如果有多个参数时需要使用@param注解,才能在对应xml的sql语句中使用参数名称获取传入方法的参数值,否则就会报错。本文结合自身在真实开发环境中使用IDEA开发时遇到的问题来共同探讨一下不使用@Param注解报错背后的原因以及解决方案。

问题描述

最近使用IDEA进行开发,项目使用SpringBoot+Mybatis3.4.6,同样的代码检出到本地IDEA后运行,在一个业务查询模块报错,后台打印日志如下:

在这里插入图片描述

mybatis出现该错误的原因分析:我们正在调用一个具有多参数的mapper接口方法,对这个方法的调用其实是对mapper对应的xml中的一个sql的调用,并且我们在这个sql语句中使用#{方法参数名称}的方式构建动态SQL,但是要想在sql语句中使用参数名称获取参数值那么需要对mapper接口对应方法的每一个参数使用@Param注解,Param注解非常简单,源代码如下:


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {
  String value();
}

它只有一个value属性,这里的value就等于mapper对应的xml文件中获取参数值时要使用的key。于是我找到了对应报错的代码发现正是因为多参数方法没有使用@Param注解,在我加上该注解后便没有错误了。        

到这里事情看上去好像已经解决了,但是并没有这么简单,我查看了很多mapper发现,有很多具有多个参数的mapper方法都没有使用这个注解,按照这种修改方式,我岂不是要把几乎所有的mapper都修改一遍,并且我是刚刚检出的最新代码,代码不应该有问题才对,于是询问同事发现他们在自己的IDEA运行时并没有我这个错误,所以说并不是@Param注解的问题。

寻求解决方案

同样的代码,在不同的机器上运行出现了不同的结果,那么肯定有什么不一样的地方,首先JDK都一样,系统环境也一样,运行方式也一样,下来就是运行环境IDEA,那么IDEA是否有区别呢?

询问同事发现他们用的是比较新的版本2019.2.3,而我用的是2018.2.2版本,所以初步怀疑是IDEA的版本问题,但是好像按理来说不应该是IDEA的问题,真正运行JAVA字节码的是本地的JRE环境,貌似和IDEA关系不大,但是这是目前唯一的线索,无论如何都要试一下。

于是我下载了最新版本的IDEA,然后导入代码,运行,结果发现竟然真的没有报错!这时候问题虽然解决了,但是为什么会这样,背后的原因是什么,和IDEA版本有什么关系呢?这些问题如鲠在喉,让我茶不思,饭不想…

寻找原因

当一个问题无法知道背后的真正原因时,那么就算解决了也只是暂时的。为了寻求真正的答案,我决定使用调试代码的方式看一下mybatis执行查询过程中是如何处理mapper接口方法的参数名称的,最终找到了org.apache.ibatis.reflection.ParamNameResolver这个类,看类名就可以知道这是处理参数名称的类,主要逻辑集中在它的构造方法:

  public ParamNameResolver(Configuration config, Method method) {
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // @Param was not specified.
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }

接下来分析一下主要逻辑,首先看到的是需要获取Param注解中的Value值:

String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }

这里的name变量就是后面构造动态sql时,用于获取方法参数值的key,也就是你在xml文件中通过#{ }的方式获取动态参数时的参数key。接下来看到的代码是:

      if (name == null) {
        // @Param was not specified.
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }

这里可以看到再次判断name是否为null,如果为null则判断config.isUseActualParamName()是否为true,如果是true则通过getActualParamName(method, paramIndex)方法获取name,这些都执行完成如果name还是null,那么就是最后的逻辑: name = String.valueOf(map.size());也就是说name等于当前方法参数的位置(“0”, “1”, …),源码的注释也说明了这一点:

use the parameter index as the name (“0”, “1”, …)

那么getActualParamName(method, paramIndex)方法获取name是什么逻辑呢?接下来继续看:

首先要进入这个方法的前提是config.isUseActualParamName()为true:

public boolean isUseActualParamName() {
    return useActualParamName;
  }

config其实是mybatis的配置对象,这里面的配置项目可以影响mybatis的行为,具体配置项目可以从mybatis官方文档查询,这里我们就看一下useActualParamName参数的含义,官方文档 是这样描述的:

设置名描述有效值默认值
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true 或者 falsetrue

所以说这个属性其实就是允许我们使用mapper接口方法的参数名称当作sql语句的参数名称,而且也不需要@Param注解,这个属性默认是开启的,使用这个特性还有以下几个要求:

①采用 Java 8 编译。

②编译时加上-parameters 选项。

③mybatis在3.4.1以上

到这里基本上可以确定真正的原因了,首先我和同事的JDK都是1.8,Mybatis的版本在文章开头也说过了是3.4.6,所以只剩下-parameters选项,所以我怀疑是低版本的IDEA没有这个选项,高版本的IDEA在编译时可能默认加了这个选项。于是对比两个版本的编译设置如下:

①老版本(2018.2.2):

在这里插入图片描述

②新版本(2019.2.3):

在这里插入图片描述

果然如我们所料,新版本的IDEA编译设置里面默认添加了-parameters选项,所以在mybatis的配置项useActualParamName为true的时候,对于多参数的mapper接口方法,可以不使用@Param注解,而在低版本的IDEA时并没有添加这个选项,所以会出错。

拓展延伸

在Java8之前,JAVA代码编译为class文件后,方法参数的类型固定,但是参数名称会丢失,所以当通过反射去获取方法参数名称的时候是不能够得到原本源代码中的参数名称的,Java编译器会丢掉这部分信息。从JDK1.8开始可以通过在编译时添加-parameters这个选项来明确告诉编译器我们需要保留方法参数的原本名称。

那么为什么不默认开启这个选项呢?可能是为了避免因为保留参数名而导致class文件过大或者占用更多的内存,又或者是有些参数可能会泄露安全信息吧。

最后我们亲自来写一段代码验证一下-parameters这个选项的作用:

public class Main {
    public static void main(String[] args) {
        Method[] methods = Main.class.getMethods();
        for (Method method:methods) {
            if ("parameterMethodTest".equals(method.getName())){
                Parameter[] parameters = method.getParameters();
                for (Parameter parameter:parameters) {
                    System.out.println(parameter.getName());
                }
            }
        }
    }
    public static void parameterMethodTest(int parameterOne,String parameterTwo,Object parameterThree){
        System.out.println("Hello World!");
    }
}

在以上这段代码中,通过反射获取parameterMethodTest的三个参数名称并打印出来,首先我们在IDEA的编译设置中去掉-parameters选项,运行结果如下:

在这里插入图片描述

可以看到这个时候参数名称变成了arg0,arg1…

加上-parameters选项后,再运行结果如下:

在这里插入图片描述

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Mybatis Mapper中多参数方法不使用@param注解报错的解决

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

mybatis中@Param注解总是报取不到参数问题如何解决

这篇文章主要介绍“mybatis中@Param注解总是报取不到参数问题如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“mybatis中@Param注解总是报取不到参数问题如何解决”文章能帮助大
2023-07-02

Mybatis中Mapper使用package方式配置报错如何解决

这篇文章主要讲解了“Mybatis中Mapper使用package方式配置报错如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Mybatis中Mapper使用package方式配置报错
2023-06-20

IDEA中@Autowired自动注入MyBatis Mapper报红警告的几种解决方法

IDEA中@Autowired自动注入MyBatisMapper报红警告的解决方法有:检查依赖关系:添加MyBatis和SpringBoot依赖项。验证Mapper接口:添加@Mapper注解。扫描Mapper包:添加@MapperScan注解。开启延迟加载:设置application.properties中的mybatis.configuration.lazy-loading-enabled为true。使用全限定类名:在@Autowired中使用Mapper接口的全限定类名。忽略警告:添加@Suppre
IDEA中@Autowired自动注入MyBatis Mapper报红警告的几种解决方法
2024-04-02

使用@Autowired注解引入server服务层方法时报错的解决方法

这篇文章给大家介绍使用@Autowired注解引入server服务层方法时报错的解决方法,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。@Autowired注解引入server服务层方法时报错contentTypeSer
2023-06-25

tomcat报错:地址localhost:8080已在使用中的解决方法

今天使用tomcat发现一个问题,本文就介绍一下报错地址localhost:8080已在使用中的解决方法,具有一定的参考价值,感兴趣的可以了解一下
2023-05-19

chatGPT使用及注册过程中常见的一些错误解决方法(所以报错汇总)

这篇文章主要介绍了chatGPT注册报错及使用过程中报错汇总及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2023-02-08

Ecshop使用支付宝支付成功后提示“此支付方式不存在或者参数错”的解决方法

本文实例讲述了Ecshop使用支付宝支付成功后提www.cppcns.com示“此支付方式不存在或者参数错”的解决方法。分享给大家供大家参考,具体如下: 一、问题: ecshop 支付宝支付成功后显示“此
2022-06-12

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录