Mybaits应该很多的Java开发者都用到了,但是有一些功能想必不少的开发者不能灵活使用。或者使用的时候不理解,使用的时候总犹豫感觉用的迷迷糊糊的。今天就结合源码给大家解决疑惑。
mapper接口传参的方式有很多方式,下面会一一列举,最后看一下Mybatis的源码来总结以下mapper接口传参。
1:传递一个参数【非集合】,mapper中xml使用一个参数的规则:#{任意合法名称},如:#{name}、#{val}等等写法都可以获取到personName参数的值。如下代码:
List<User> selectUser(String personName) ;
// xml文件中的sql语句。
// 1:可以如下传参,
<select id="selectUser" resultType="io.renren.domain.User">
select * from user where person_Name=#{personName}
</select>
// 也可以如下传参,也就是可以传任意的合法字符。
<select id="selectUser" resultType="io.renren.domain.User">
select * from user where person_Name=#{val}
</select>
测试代码如下:
@org.junit.Test
public void test(){
List<User> list = userDao.selectUser("TestUser");
System.out.println(list);
}
// 输出结果如下查询结果完全正确。
==> Preparing: select * from user where person_Name=?
==> Parameters: TestUser(String)
<== Columns: PERSON_ID, PERSON_NAME
<== Row: 20291, TestUser
<== Row: 20292, TestUser
<== Total: 2
[User(personId=20291, personName=TestUser), User(personId=20292, personName=TestUser)]
2:传递一个map参数,如果我们需要传递的参数比较多,参数个数是动态的,那么我们可以将这些参数放在一个map中,key为参数名称,value为参数的值。代码如下:
List<User> selectUserByMap(Map<String,Object> map);
// xml中的sql语句。
// 对应的xml中可以通过#{map中的key}可以获取key在map中对应的value的值作为参数,
<select id="selectUserByMap" resultType="io.renren.domain.User">
select * from user where person_Name=#{personName} and PERSON_ID=#{personId}
</select>
测试代码如下:
@org.junit.Test
public void test(){
Map<String,Object> map = new HashMap<>();
map.put("personId",20291);
map.put("personName","TestUser");
List<User> list = userDao.selectUserByMap(map);
System.out.println(list);
}
// 测试结果如下:
==> Preparing: select * from user where person_Name=? and PERSON_ID=?
==> Parameters: TestUser(String), 20291(Integer)
<== Columns: PERSON_ID, PERSON_NAME
<== Row: 20291, TestUser
<== Total: 1
[User(personId=20291, personName=TestUser)]
3:传递一个java对象参数,当参数个数比较多,但是具体有多少个参数我们是确定的时候,我们可以将这些参数放在一个java对象中。如我们想通过personId和personName查询,可以定义一个dto对象。代码如下:
List<User> selectUserByBean(UserQueryDTO userQuery);
// xml中的查询语句:
<select id="selectUserByBean" resultType="io.renren.domain.User">
select * from user where person_Name=#{personName} and PERSON_ID=#{personId}
</select>
测试代码如下:
@org.junit.Test
public void test(){
UserQueryDTO dto = new UserQueryDTO() ;
dto.setPersonId(20291);
dto.setPersonName("TestUser");
List<User> list = userDao.selectUserByBean(dto);
System.out.println(list);
}
// 测试结果如下:
==> Preparing: select * from user where person_Name=? and PERSON_ID=?
==> Parameters: TestUser(String), 20291(Integer)
<== Columns: PERSON_ID, PERSON_NAME
<== Row: 20291, TestUser
<== Total: 1
[User(personId=20291, personName=TestUser)]
传递java对象的方式相对于map的方式更清晰一些,可以明确知道具体有哪些参数,而传递map,我们是不知道这个map中具体需要哪些参数的,map对参数也没有约束,参数可以随意传,建议多个参数的情况下选择通过java对象进行传参。
4:传递多个参数,上面我们介绍的都是传递一个参数,那么是否可以传递多个参数呢?我们来试试吧。如下代码:
List<User> selectUserBy2Param(Integer id,String name);
// xml文件中的查询语句。
<select id="selectUserBy2Param" resultType="io.renren.domain.User">
select * from user where person_Name=#{personName} and PERSON_ID=#{personId}
</select>
然后我们测试以下,测试代码如下:
@org.junit.Test
public void test(){
List<User> list = userDao.selectUserBy2Param(20291,"TestUser") ;
System.out.println(list);
}
然后回报如下的错误:
上面报错,给大家解释一下,sql中我们通过#{personName}去获取personName参数的值,但是mybatis无法通过#{personName}获取到personName的值,所以报错了,从上错误中我们看到有这样的一段:
Available parameters are [name, id, param1, param2]
上面表示可用的参数名称列表,先不说这4个参数具体怎么来的,我们将sql改成下面这样试试,如下代码:
<select id="selectUserBy2Param" resultType="io.renren.domain.User">
select * from user where person_Name=#{param1} and PERSON_ID=#{param2}
</select>
测试结果如下:
// 虽然不报错了,但是没查询出来正确的结果,大家看一下下面打印sql就知道
// 为什么没有查询出正确的数据了。
==> Preparing: select * from user where person_Name=? and PERSON_ID=?
==> Parameters: 20291(Integer), TestUser(String)
<== Total: 0
[]
我们来分析一下mybatis对于这种多个参数是如何处理的?我想后面也是大家会更感兴趣的地方。mybatis处理多个参数的时候,会将多个参数封装到一个map中,map的key为参数的名称,value就是参数的值。所以上面传递的参数相当于传递了下面这样的一个map
Map<String,Object> map = new HashMap<>();
map.put("param1",id参数的值);
map.put("param2",name参数的值);
// PS 当然map中还存了一组值,后面源码中有写出来。
sql中使用`param1、param2、paramN`这种方式来引用多参数,对参数的顺序依赖性特别强,如果有人把参数的顺序调整了或者调整了参数的个数,后果就是灾难性的,所以这种方式不建议大家使用。【我们上面的查询结果就印证了,虽然不报错但是结果不是我们想看到的】。
多参数中建议用@param指定参数名称。使用param1的方式对参数名称和顺序有很强的依赖性,容易导致一些严重的错误。mybatis也为我们考虑到了这种情况,可以让我们自己去指定参数的名称,通过@param(“参数名称”)来给参数指定名称。【这个注解很多人应该都会使用我就不讲了】。mybatis参数处理相关源码,上面参数的解析过程代码在org.apache.ibatis.reflection.ParamNameResolver类中,主要看下面的2个方法:
public ParamNameResolver(Configuration config, Method method)
public Object getNamedParams(Object[] args)
如下源码:
public ParamNameResolver(Configuration config, Method method) {
//
this.useActualParamName = config.isUseActualParamName();
// 获取参数类性,我们传的是 Integer,String 2种类型
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取传递的参数加的注解,目前我们没加注解。
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
// 排了顺序的map
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
// 从 param注解中获取注解的名称
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
// 如果加了param注解,解析走到这里
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);
}
// 没加@Param注解,程序直接走到这里。
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
// 没加param注解里面存放的值【0->id,1->name】
// 加了param注解里面存放的值【0-personId,1->personName】
names = Collections.unmodifiableSortedMap(map);
}
public Object getNamedParams(Object[] args) {
// 获取参数的数量
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
// 参数没加注解,并且只有一个参数的时候直接返回
} else if (!hasParamAnnotation && paramCount == 1) {
// 如果只有一个参数。参数不是集合类型返回一个【testUser】
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, ...)
// 这里就是我们提到的 param1,params
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++;
}
// 没加param注解存放的值【param1->20291,param2->testUser,id->20291,name->testUser】
// 加了param注解存放的值【param1->20291,param2->testUser,personId->20291,personName->testUser】
return param;
}
}
所以看了上面的源码我们知道map中始终存在的key是param1,param2...等等等。这也就是我们一直使用param1作为参数不报错的原因。
当我们的参数传递一个集合的时候是什么样的情况呢?让我们继续看源码如下代码:
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
if (object instanceof Collection) {
ParamMap<Object> map = new ParamMap<>();
// 如果参数是一个集合,则会存放一个key 为 collection,
// value为参数的集合的map。
map.put("collection", object);
// 如果参数是一个list,则map中会再存一个key为list
// value为参数的集合的map。
if (object instanceof List) {
map.put("list", object);
}
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
// 如果是数组:
// 则map中会再存一个key为array,value为参数的集合的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;
}
所以从上面的源码可知,如果你传递的参数是一个集合。map中存放的key为collection。当这个集合为List的时候会继续存一个key为list的map。如果传递的参数为数组会继续存一个key为array的map。看过参数为集合的时候这下你使用集合传参的时候应该不会犹豫了吧。
总结:当mapper接口只有一个参数的时候,xml文件中你可以在#{}中使用任何合法的字符传参,当mapper中使用map作为参数的时候,xml文件中你可以在#{}中使用map的key传参,当mapper中使用java对象作为参数的时候,xml文件中你可以在#{}中使用java对象的属性传参。当mapper接口中你使用集合传参的时候,xml文件中你可以使用collection,或者list,array来获取参数的值【集合一般需要配合foreach标签使用】。
当mapper接口中有多个参数的时候推荐你使用@Param注解使用。