MyBatis多参数传递之@Param究竟加还是不加?

背景:

MyBatis传递多个参数,常用的三种实现方式 1.@Param注解传参 2.Map传参法 3.Java Bean传参法

那么@Param 使用场景都有哪些呢?为啥平时写代码有的时候不加会报错,有的时候不写也没问题

一 、MyBatis多参数传递 四种情况需要加@Param

1.方法有多个参数,需要 @Param 注解

2.当需要给参数取一个别名的时候,需要 @Param 注解

1
List<Device> getDeviceListTest(@Param("deviceId")String deviceId,@Param("deviceName")String deviceName);

3.XML 中的 SQL 使用了 $ ,那么参数中也需要 @Param 注解,$ 会有注入漏洞的问题,但是有的时候你必须要 $ 符号,例如要传入列名者表名的时候,这个时候必须要

1
2
3
4
//mapper 接口
List<Device> getDeviceListTest(@Param("columnName") String columnName);
<!--mybatis的xml-->
select * from t_device order by ${columnName} desc

4.动态 SQL ,如果在动态 SQL 中使用了参数作为变量,那么也需要 @Param 注解,即使你只有一个参数。

1
2
3
4
5
6
7
8
9
10
11
//mapper 接口
List<Device> getDeviceListTest(@Param("deviceId") String deviceId);
<!--mybatis的xml-->
<select id="getDeviceListTest" parameterType="String" resultType="Device">
        select * from t_device
        <where>
           <if test="deviceId != null and deviceId != ''">
               device_id=#{deviceId}
           </if>
        </where>
    </select>

二 、为啥平时写代码有些时候不加可以正常运行 ,谁在搞鬼?

测试 多参数时,不写 @Param

1
List<Device> getDeviceListTest(String deviceId,String deviceName);

得到结果如下图所示:

历史原因:
在Java8之前,可以说你无法做到(你是不可能读取这个 id) 的,因为Java在编译的时候会将 String deviceId编译为 String arg0,然而Java8中新增了这样的一个特性,你可以在编译的时候设定保留参数名称.详见源码分析

错误总结:

注: 使用jdk1.7得到的是: [1, 0, param1, param2]
使用1.8得到的则是: [arg1, arg0, param1, param2]

例如 xml 可以这样写,但这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。

为了证明上述解释是对的:以下sql语句可以完美运行

1
select * from t_device where device_id = #{arg0} and device_name != #{arg1}

idea有时可以不加@Param,那么它 对我的代码做了什么?

但是 你如使用的是idea ,即时不写@Param 也能成功,原因是

IDEA编译时采取了强制保持方法参数变量名,但需要满足如下

1. 必须是jdk8或以上

2. 编译器参数-parameters

三、错误源码分析

debug断点进入service实现类

1
deviceMapper.getDeviceListTest(device.getDeviceId(),device.getDeviceName());

进入后,见下图,怎么样熟悉的感觉吧 JDK动态代理,我们写的mapper 接口,MyBatis 通过动态代理,自动添加了实现类,主要看invoke() 方法,参数变成了数组args

动态代理会在原有方法上实现增强,而增强的逻辑就写在InvocationHandler类的invoke方法上,

所以接下来看
cachedMapperMethod,如果不存在就实例化一个 MapperMethod,然后 put。
当然,第一次调用肯定是不存在的。

进来后,看 method 进行了实例化

下面对 paramAnnotations 的遍历,如果没有设置 @Param,那么 name 也不会有值,那么将会通过 getActualParamName 来获取参数值。获取实体或者map 里的参数名,或者直接得到参数名arg0 补充:getActualParamName 使用了 JDK 1.8 新增特性,反射获取参数名