窥探系列之Mybatis-plus 参数名解析

ParamNameResolver

当我们使用 MyBatis 进行数据库操作时,常常需要编写 SQL 语句,并且需要将方法的参数传递给 SQL 语句中的参数占位符。MyBatis 提供了一种方便的方式来实现这一点,即使用 #{paramName} 这种形式的参数占位符,并将方法的参数存储到一个 Map 对象中,然后将该 Map 对象传递给 SQL 语句执行器。但是,Java 的字节码中并没有对方法参数名进行记录,因此 MyBatis 需要一种方法来获取方法参数的名称,以便能够正确地将参数值传递给 SQL 语句。

为了解决这个问题,MyBatis 提供了一个名为 ParamNameResolver 的类,用于解析方法中的参数名。ParamNameResolver 使用 Java 的反射机制中的 Executable 接口提供的 getParameters() 方法获取方法的所有参数对象,然后结合字节码中的局部变量表信息,解析出方法的参数名。如果方法中使用了 @Param 注解,则会优先使用注解中的参数名作为方法参数的名称。

下面是 ParamNameResolver 的核心代码片段:

public class ParamNameResolver {
    public ParamNameResolver(Configuration config, Method method) {
        // ...
        final Class<?>[] paramTypes = method.getParameterTypes();
        final Annotation[][] paramAnnotations = method.getParameterAnnotations();
        final List<String> names = new ArrayList<>();
        for (int i = 0; i < paramTypes.length; i++) {
            String name = null;
            // get the @Param annotation value
            for (Annotation annotation : paramAnnotations[i]) {
                if (annotation instanceof Param) {
                    name = ((Param) annotation).value();
                    break;
                }
            }
            if (name == null) {
                // use the parameter name as the default value
                name = getActualParamName(method, i);
            }
            names.add(name);
        }
        this.names = Collections.unmodifiableList(names);
    }

    private String getActualParamName(Method method, int paramIndex) {
        // use ASM to get the parameter name
        // ...
    }

    // ...
}

ParamNameResolver 的主要作用是为 MyBatis 提供一个统一的方式来获取方法参数的名称,从而能够正确地将参数值传递给 SQL 语句。在 MyBatis 中,如果 SQL 语句中的参数占位符使用了 #{paramName} 这种形式,那么 MyBatis 就会通过 ParamNameResolver 来获取对应的参数名,并从执行器传递的参数 Map 对象中获取对应的参数值,并将其绑定到 SQL 语句中的参数占位符上。

总之,ParamNameResolver 是 MyBatis 中的一个重要组件,它为 MyBatis 提供了一种方便的方式来获取方法参数的名称,从而使得 MyBatis 能够更加灵活、方便地实现 SQL 语句和 Java 方法之间的参数传递。

该代码是 MyBatis 中的 ParamNameResolver 类的构造函数实现。主要功能是解析方法参数的名称,并将参数名称与其索引映射到一个 SortedMap 中。

首先,构造函数会根据 MyBatis 的配置,判断是否使用实际参数名称。然后,获取方法的所有参数类型和注解,以及参数数量。接着,对于每个参数,通过遍历其注解,获取该参数的名称,如果没有找到 @Param 注解,就使用默认的名称。

如果启用了使用实际参数名称,且该参数没有指定名称,就会尝试从方法中获取该参数的实际名称。如果还是没有找到名称,就将参数的索引作为名称。

最后,构造函数将参数名称与索引映射关系保存在一个 SortedMap 中,并将其封装成一个不可修改的 SortedMap。

需要注意的是,在代码中,使用了 isSpecialParameter 方法来判断参数是否为特殊参数,这些特殊参数包括 RowBounds、ResultHandler 和 ParamMap 等类型,这些参数通常不需要指定名称。

在 for 循环中,使用 break 来提前结束循环,可以避免不必要的遍历,提高代码的效率。另外,由于 names 是一个不可修改的 SortedMap,因此可以使用 Collections.unmodifiableSortedMap 方法来创建一个不可修改的映射。

总的来说,该构造函数的作用是解析方法参数的名称,并将参数名称与其索引映射到一个 SortedMap 中。这个 SortedMap 可以方便地用于后续的参数解析操作,使得 MyBatis 可以更加灵活地处理方法参数。

  public ParamNameResolver(Configuration config, Method method) {
    this.useActualParamName = config.isUseActualParamName();
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    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 (useActualParamName) {
          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);
  }

在 MyBatis 中,getNamedParams 方法用于获取方法参数列表中使用了 @Param 注解的参数名及其对应的参数值,以及未使用 @Param 注解的参数名及其对应的参数值。

如果参数数量为 0 或 args 数组为 null,则返回 null。如果参数数量为 1 且未使用 @Param 注解,则直接使用参数的值,将其转换为一个 Map 对象并返回。否则,将参数和名称的映射关系保存到一个 Map 中,并返回该 Map。

该代码首先创建一个 ParamMap 对象,然后遍历names和args,取出参数和名称的映射关系,将其添加到 Map 中。同时,还会使用一个计数器变量 i,用于生成使用默认命名规则的参数名称,这些参数名称以 “param” 开头,并以数字结尾,例如 “param1”、“param2” 等,然后添加到Map中。

在添加参数时,代码会先添加names中的参数名,然后再添加使用默认命名规则的参数,并避免覆盖已经具有名称的参数。

  public Object getNamedParams(Object[] args) {
    final int paramCount = names.size();
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      Object value = args[names.firstKey()];
      return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);
        // add generic param names (param1, param2, ...)
        final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
        // ensure not to overwrite parameter named with @Param
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
      return param;
    }
  }

wrapToMapIfCollection用于将方法的参数列表中的集合或数组类型的参数包装成 Map 对象。

如果参数 object 是集合类型,则会创建一个 ParamMap 对象,并将集合对象存储在该 Map 对象中。同时,会添加一个名为 collection 的键,对应的值为集合对象。同时,如果actualParamName 不为空,则往ParamMap 对象中添加一个名为 actualParamName 的键,对应的值也为数组对象。

如果集合对象还是 List 类型,则会添加一个名为 list 的键,对应的值也为集合对象。

如果参数 object 是数组类型,则与集合类型类似,也会创建一个 ParamMap 对象,并会添加一个名为 array 的键,对应的值为数组对象。同时,如果actualParamName 不为空,则往ParamMap 对象中添加一个名为 actualParamName 的键,对应的值也为数组对象。

如果参数 object 不是集合或数组类型,则会直接返回 object,而不做任何处理。因为只有一个参数的话,将其包装成 Map 对象并没有太多实际意义,而直接返回对象更加简单和高效。

需要注意的是,ParamMap 是 MyBatis 中的一个特殊的 Map 实现,它可以用于存储多个同名的键值对。这样,我们就可以在 SQL 语句中使用 #{paramName} 占位符来引用参数,而不必担心参数名的重复问题。同时,ParamMap 还可以像普通的 Map 一样使用,支持添加、删除、遍历等操作。

  public static Object wrapToMapIfCollection(Object object, String actualParamName) {
    if (object instanceof Collection) {
      ParamMap<Object> map = new ParamMap<>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
      return map;
    } else if (object != null && object.getClass().isArray()) {
      ParamMap<Object> map = new ParamMap<>();
      map.put("array", object);
      Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
      return map;
    }
    return object;
  }

从源码分析上可以得知,我们在SQL中可以通过以下参数名引用到参数变量:

  1. @Param指定的参数名;
  2. 如果我们不使用@Param注解,则会默认使用参数的本名作为参数名;
  3. 无论是否使用@Param,param1、param2等根据参数索引和前缀param拼接生成的名称可以用于参数引用;
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Mybatis-plusMyBatis增强工具包,用于简化CRUD操作。该工具包为MyBatis提供了一些高效,有用,即用的功能,使用它可以有效地节省您的开发时间。 Mybatis-plus特征: 与MyBatis完全兼容 启动时自动配置 开箱即用的用于操作数据库的界面 强大而灵活的条件包装器 生成主键的多种策略 Lambda样式的API 全能和高度可定制的代码生成器 自动分页操作 SQL注入防御 支持活动记录 支持可插拔的自定义界面 内置许多有用的扩展 Mybatis-plus功能: 1、单表CURD(简单 + 批量)操作,自动完成(支持 like 比较等查询)。 2、分页插件,Count查询自动或自定义SQL查询。 3、Spring根据不同环境加载不同配置支持(支持typeAliasesPackage通配符扫描)。 【自动生成Entity  Mapper  Service文件】 Mybatis-plus更新日志: v3.4.3 增加瀚高数据库支持 增加注解 Order By 支持默认排序 Wrapper exists notExists orderBy groupBy 支持参数绑定 Wrapper 支持 setParamAlias 其它优化 优化 KeyGenerator 支持多实现多数据源注入 增强 ServiceImpl 泛型推断,解决多继承与代理问题 新增 PageDto 用于微服务对象传输序列化 新增 Page 提供静态 of 构造方式 增加代理 MethodHandleProxies 对 lambda 调试支持 调整 ActiveRecord 日志对象初始化 调整 ActiveRecord 模式 Model 类开发 pkVal 方法外部可用 删除标记过时代码 优化枚举值获取方式 分页 count 安全处理 Sequence 方法支持重写支持 升级 Mybatis 3.5.7 修复自动配置 lazy-initialization 无属性提示 修复 mysql on duplicate key update 字段判断为表问题 修复 lambda 条件 npe 异常 重构 lambda 信息提取方法 获取 lambda 信息不在序列化 合并 gitee pulls/ 141 fixed github issues/3208 3016 fixed github issues/3482 数据权限处理器支持 union all 调整事务未启用打印提示信息 单元测试优化相关依赖升级

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值