MyBatis参数传递总结
MyBatis参数传递#{}
方式
情况一:Mapper 映射器接口方法参数只有一个且为基本类型
接口方法:
public List<UserEntity> selectUserByAge(int age);
映射结果:
<select id="selectUserByAge" resultMap="userResultMap">select * from tb_user where age > #{age};
</select>
其中 #{参数名} 表示参数占位符,等价于SQL语句的 ?号,这里的 #{age} 对应的就是接口方法 selectUserByAge 的参数。由于只有一个参数,而且是基本类型,所以写成 #{userAge} 或者 #{ageUser} 都无所谓,反正作用是一样的。
情况二:Mapper映射器接口方法参数有两个基本类型
接口方法:
public int updateUser(int id, String name);
映射结果:
<update id="updateUser">update tb_user set name=#{name} where id=#{id};
</update>
接口方法 updateUser 有两个参数且都是基本类型,按理说直接使用 #{参数名} 就可以了,不过一运行居然报错,如下:
### SQL: update tb_user set name=? where id=?;
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [0, 1, param1, param2]
从错误信息描述看,说是参数 name 没有发现,有效的参数是 [arg0, arg1, param1, param2]。这是什么意思呀? 意思就是说当遇到不只一个参数时,比如两个参数,就不能用 #{参数名} 作为占位符,可以用 MyBatis 提供了两种方式之一。
-
方式一:#{arg0} 表示第一个参数 name,#{arg1} 表示第二个参数 id,#{arg2} 表示第三个参数...
使用如下:
<update id="updateUser">update tb_user set name=#{arg0} where id=#{arg1}; </update>
-
方式二:#{param1} 表示第一个参数 name,#{param2} 表示第二个参数 id,#{param3} 表示第三个参数...
使用如下:
<update id="updateUser">update tb_user set name=#{param1} where id=#{param2};
</update>
其实,如果你非要用 #{参数名} 作为占位符,还可以用 MyBatis 提供的第三种方式,如下:
-
方式三:给接口方法的参数取别名,只要参数别名和 #{参数名} 相同就可以了。
使用如下:
// @Param("id")表示给参数 int id 取别名为id,@Param("name") 表示给参数 name 取别名为name
public int updateUser(@Param("id") int id,@Param("name") String name);
<update id="updateUser">update tb_user set name=#{name} where id=#{id};
</update>
sql 中使用 arg0,arg1,arg..
或 param1、param2、param...
这种方式来引用多参数,对参数的顺序依赖性特别强,如果有人把参数的顺序调整了或者调整了参数的个数,后果就是灾难性的,所以这两种方式不建议大家使用。
推荐大家使用第三种方式,不仅可读性高,而且参数顺序可以任意调整。
情况三:Mapper 映射器接口方法参数为 Map 类型
接口方法:
public List<UserEntity> selectByMap(Map<String,Object> map);
映射结果:
<select id="selectByMap" resultType="entity.UserEntity"><![CDATA[SELECT * FROM tb_user WHERE age=#{age} OR sex = #{sex}]]>
</select>
情况四:Mapper 映射器接口方法参数只有一个且为引用类型
接口方法:
public int insertUser(UserEntity user);
映射结果:
<insert id="insertUser">insert into tb_user (id,userName, password, name, age, sex, birthday, created, updated) values(null,#{userName},#{password},#{name},#{age},#{sex},#{birthday},now(),now());
</insert>
接口方法 insertUser 的参数是引用类型,其实传递给 SQL 语句的参数是引用类型的属性值,SQL 语句本身是不支持引用类型的。那引用类型有很多属性(或成员变量),是如何与 SQL 语句的参数一一对应的呢?
答案是使用 #{引用类型的属性名} ,这里需要注意的是属性名不能写错了,否则就无法与 SQL 语句的参数对应,无法正确传递参数哈。
public class UserEntity {private int id;private String userName;private String password;private String name;private int age;private int sex;private Date birthday;private String created;private String updated;
}
由于是自增主键,所以不需要传递引用类型的 id 参数,使用 null 代替,数据库会自动生成主键 id 标识。
传递 java 对象的方式相对于 map 的方式更清晰一些,可以明确知道具体有哪些参数,而传递 map,我们是不知道这个 map 中具体需要哪些参数的,map 对参数也没有约束,参数可以随意传,建议多个参数的情况下选择通过 java 对象进行传参。
情况五:Mapper 映射器接口方法参数有两个引用类型
接口方法:
public List<UserEntity> selectUserByAgeAndSex(@Param("userOne") UserEntity userOne,@Param("userTwo") UserEntity userTwo);
映射结果1:
<select id="selectUserByAgeAndSex" resultMap="userResultMap">select * from tb_user where age > #{userOne.age} and sex = #{userTwo.sex};
</select>
映射结果2:
<select id="selectUserByAgeAndSex" resultMap="userResultMap">select * from tb_user where age > #{param1.age} and sex = #{param2.sex};
</select>
以上两种映射方式都可以,但是如果没有为两个参数取 @Param("userOne") 和 @Param("userTwo") 别名的话,那么就只有映射结果2可以了,映射结果1将会报错。
爱思考的你可能会问,MyBatis 不是还有一种传参的方式吗?如下映射方式是否可以?
<select id="selectUserByAgeAndSex" resultMap="userResultMap">select * from tb_user where age > #{arg0.age} and sex = #{arg1.sex};
</select>
回答这个问题很简单,试一试不就知道了嘛。测试结果如下:
### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter '0' not found. Available parameters are [userOne, userTwo, param1, param2]
### Cause: org.apache.ibatis.binding.BindingException: Parameter '0' not found. Available parameters are [userOne, userTwo, param1, param2]
测试结果报错,这说明#{arg0}、#{arg1}
这种参数占位符的方式只适用于参数是基本类型,不适用于参数是引用类型。
情况六:Mapper 映射器接口方法参数有多个(包括基本类型和引用类型)
接口方法:
public List<UserEntity> selectUserByNameAndAge(@Param("name") String name, @Param("user") UserEntity user);
映射结果1:
<select id="selectUserByNameAndAge" resultMap="userResultMap">select * from tb_user where name = #{name} and age > #{user.age};
</select>
映射结果2:
<select id="selectUserByNameAndAge" resultMap="userResultMap">select * from tb_user where name = #{param1} and age > #{param2.age};
</select>
以上两种映射方式都可以。
看到这里,相信你应该对 MyBatis 的#{ }传参方式已经胸有成竹了吧。无论接口方法的参数个数如何、类型如何,你应该都知道如何映射。
情况七:Mapp 映射器接口方法参数为 Collection 类型
当传递的参数类型是Collection
的时候,会被放在 map 中,key为collection
,value 为参数的值
接口方法:
public List<UserEntity> selectByCollection(Collection<Integer> ages);
映射结果:
<select id="selectByCollection" resultType="entity.UserEntity"><![CDATA[SELECT * FROM tb_user WHERE age in (#{list[0]},#{list[1]})]]>
</select>
Mybatis 中集合参数处理了源码解析
集合参数,mybatis会进行一些特殊处理,代码在下面的方法中:
org.apache.ibatis.session.defaults.DefaultSqlSession#wrapCollection
源码解释:
判断参数是否是java.util.Collection
类型,如果是,会放在map中,key为collection
。如果参数是java.util.List
类型的,会在map中继续放一个list
作为key来引用这个对象。如果参数是数组类型的,会通过array
来引用这个对象。
情况八:Mapper 映射器接口方法参数为 ResultHandler 类型
查询的数量比较大的时候,返回一个 List 集合占用的内存还是比较多的,比如我们想导出很多数据,实际上如果我们通过jdbc的方式,遍历ResultSet
的next
方法,一条条处理,而不用将其存到 List 集合中再取处理。
mybatis中也支持我们这么做,可以使用ResultHandler
对象,犹如其名,这个接口是用来处理结果的,先看一下其定义:
public interface ResultHandler<T> {void handleResult(ResultContext<? extends T> resultContext);
}
里面有1个方法,方法的参数是ResultContext
类型的,这个也是一个接口,看一下源码:
public interface ResultContext<T> {T getResultObject();int getResultCount();boolean isStopped();void stop();
}
4 个 方法:
- getResultObject:获取当前行的结果
- getResultCount:获取当前结果到第几行了
- isStopped:判断是否需要停止遍历结果集
- stop:停止遍历结果集
ResultContext
接口有一个实现类org.apache.ibatis.executor.result.DefaultResultContext
,mybatis中默认会使用这个类。
遍历tb_user
表的所有记录,第 2 条遍历结束之后,停止遍历
接口方法:
void getList(ResultHandler<UserEntity> resultHandler);
映射结果:
<select id="getList" resultType="entity.UserEntity"><![CDATA[SELECT * FROM tb_user]]>
</select>
测试代码:
@Test
public void getListTest() {userMapper.getList(context->{//将context参数转换为DefaultResultContext对象DefaultResultContext<UserEntity> defaultResultContext= (DefaultResultContext<UserEntity>) context;//打印遍历结果System.out.println(defaultResultContext.getResultObject());//遍历到第二条之后停止if (defaultResultContext.getResultCount() == 2) {//调用stop方法停止遍历,stop方法会更新内部的一个标志,置为停止遍历defaultResultContext.stop();}});
}
MyBatis 参数传递 #{} 和 ${} 区别
原理
-
{ }:为参数占位符?(即底层使用了 JDBC 的 PreparedStatement 来进行预处理)
- ${ }:为字符串替换(即底层使用了 JDBC 的 Statement 直接进行查询)
参数传递
-
{ }: 传递参数后 SQL 语句自动为参数加上单引号
- ${ }: 传递参数后 SQL 语句不会为参数加上单引号
SQL 注入
-
{ }:可以防止 sql 注入
- ${ }:不可以防止 sql 注入
注:SQL 注入是黑客攻击服务器的一种简单手段,感兴趣的话可以去看我写的关于 SQL 注入的详细博客文章。
MyBatis 参数传递总结
参数传递方式
- 参数传递 #{ } 方式:#{arg0} 、 #{param1} 、 @param(别名) / #
- 参数传递 ${ } 方式:用法同 #{ } 相同,注意与 #{ } 的区别
项目开发建议
- 建议接口参数一律使用 @param("参数名") 为参数取别名(可读性高)
- 建议只要能用 #{ } 的地方尽量不使用 ${ }(安全性高)