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 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 标识。

  • 情况三: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 没有发现,有效的参数是 [0, 1, param1, param2]。这是什么意思呀? 意思就是说当遇到不只一个参数时,比如两个参数,就不能用#{参数名}作为占位符,可以用MyBatis提供了两种方式之一。

    • 方式一#{0} 表示第一个参数 name,#{1} 表示第二个参数 id,#{2} 表示第三个参数...

      使用如下:

      <update id="updateUser">
      	  update tb_user set name=#{0} where id=#{1};
      </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>
    

    总结

    以上三种 MyBatis 参数的传递方式,哪种项目开发中比较常用呢?答案是第三种方式。理由是这种方式的代码可读性更好。想一想上面举例中,是#{name},#{id}作为参数占位符意思让人一目了然,还是#{0},#{1},#{param1},#{param2}呢?答案应该不言而喻。

  • 情况四: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 > #{0.age} and sex = #{1.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]
    

    测试结果报错,这说明 #{0}、#{1}这种参数占位符的方式只适用于参数是基本类型,不适用于参数是引用类型。

  • 情况五: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 的#{ }传参方式已经胸有成竹了吧。无论接口方法的参数个数如何、类型如何,你应该都知道如何映射。

MyBatis 参数传递${}方式

MyBatis 除了可以使用 #{ } 方式传递参数,还有一种传参的方式,那么就是 ${ }。你可能会想,#{ }方式传递参数就已经够用了,干嘛还要搞一个 ${ } 出来,有完没完呀。我们还是先来看一下它的用法再说。

接口方法:

 public List<UserEntity> selectUserByNameAndAge(@Param("name") String name, @Param("user") UserEntity user);

映射结果:

<select id="selectUserByAgeAndSex" resultMap="userResultMap">
     select * from tb_user where age > ${userOne.age} and sex = ${userTwo.sex};
</select>

原来这么简单,直接把原来的 #{ } 替换成 ${ }就可以了啦。那是不是所有 SQL 语句中使用 #{ } 的地方都可以被替换,这两种方式效果是相同的,是吗?对不起,回答错误。我们再看一个例子。

接口方法:

 public int updateUser(@Param("id") int id,@Param("name") String name);

映射结果:

<update id="updateUser">
        update tb_user set name=${name} where id=${id};
</update>

运行测试,结果如下:

### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column '张三三' in 'field list'
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: update tb_user set name=张三三 where id=1;
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column '张三三' in 'field list'

报错了吧,这是怎么回事。从抛出的异常错误信息中,可以重点看 Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column '张三三' in 'field list' 这句话。首先Cause 的中文意思就是错误原因;接着告诉我们抛出了一个错误异常 MySQLSyntaxErrorException,这个异常是 SQL 语法错误的意思;最后提示我们引起 SQL 语法错误的是 Unknown column '张三三',中文意思是无法识别的表字段名‘张三三''。

分析了半天,还是不知道问题到底出在哪里?有点耐心好吧,你知道吗?经常有程序员前辈会告诉你,代码是调试出来的,不是写出来的,意思就是代码都是从错误中改出来的,没有人写代码从不出错。而作为一名合格的程序员,是需要具有独立解决问题的能力。那怎样才能具有独立解决问题的能力,这就需要多学一学人家是如何分析和定位问题的,逐渐积累经验哈。

接着我们再看下面这三句,也许会给我们更多错误提示信息:

### The error may involve defaultParameterMap 错误可能涉及默认的参数映射
### The error occurred while setting parameters 当设置参数时产生错误的
### SQL: update tb_user set name=张三三 where id=1; 有语法错误的 SQL 语句

我把这三句翻译成中文,相信你应该会看得明白一些。现在你应该感受到,要想编程好英文少不了。如果英文不过关,错误原因就在眼前,你也熟视无睹,如同盲人一般。所以,抽空补一补英文,别让它拖你的后腿,成为你通往程序员大牛的绊脚石。

现在错误原因已经搞清楚了,传递参数导致的 SQL 语法错误,具体而言是传递 name 参数。那究竟错在哪里呢?update tb_user set name=张三三 where id=1; 这句 SQL 语句语法错误在于参数'张三三'是字符串但是 SQL 语句中并没有加单引号。

对比以下正确和错误的 SQL 语法如下:

update tb_user set name=张三三 where id=1;   #错误 SQL 语法
update tb_user set name='张三三' where id=1; #正确 SQL 语法

现在你应该明白了, #{ } 和 ${ }用法还是有区别的。

${ } 会将传递的参数直接显示在 SQL 语句中,而 #{ } 会将传递的参数自动添加单引号。

现在可以解释为何第一个例子没有报错,第二个例子就报错了。因为第一个例子的两个参数都是 int 类型,所以加不加单引号都是一样的,也就是说,以下两条 SQL 语句执行结果相同。

select * from tb_user where age > '20' and sex = '1';# 采用#{}方式
select * from tb_user where age > 20 and sex = 1;    # 采用${}方式

那 ${ } 在项目开发中到底有何用武之地呢?我们可以反过来想,什么情况下需要传递的参数不能自动添加单引号,否则会报错,而这些情况就是它的用武之地。

  • 情况一:order by 时,必须使用 ${ }

    什么意思呀,还是举个例子,如下:

    select * from tb_user where age > '20' order by 'age'
    select * from tb_user where age > '20' order by age
    

    以上两条 SQL 语句都可以在数据库中执行,但是只有一条 SQL 语句执行结果是正确的,请问是哪一条?还是动手试一试就知道了。

    执行第一条 SQL 语句结果如下:

    执行第二条 SQL 语句结果如下:

    我们知道 order by 是将查询结果进行排序,这里是按照年龄排序,默认是升序。这两条 SQL 语句执行结果只有第二条是正确的。

    现在你应该明白了,为何 order by 后面如果要传递参数,必须用不加单引号的 ${ },而不是自动加单引号的#{ }了吧。

  • 情况二:表名作为参数时,必须使用 ${ }

    什么时候会用表名做参数呀,那就是当数据库有两个一模一样的表,分别是历史表和当前表。历史表和当前表都可以查询表中的信息,但有时候需要从历史表中去查询数据,有时候需要从当前表中查询数据,而且希望使用1个方法来完成查询操作,如下:

    select * from ${tableName} 
    

    如果表名作为参数使用 #{ } 那么就会给表名自动添加单引号,这明显 SQL 语法不正确,这就是为何如果参数传递是表名时只能用 ${ }。

MyBatis 参数传递 #{} 和 ${} 区别

想必 MyBatis 的#{ } 和 ${ } 两种参数传递的方式你已经掌握了,那么我想再进一步加深你对它们的理解。你要明白 MyBatis 框架本质上是对 JDBC 的封装,所以想要深入理解 MyBatis 的原理,需要对 JDBC 有深入的认识。

  • 原理

    #{ }:为参数占位符?(即底层使用了 JDBC 的 PreparedStatement 来进行预处理)

    ${ }:为字符串替换(即底层使用了 JDBC 的 Statement 直接进行查询)

    注:如果你对 JDBC 的 PreparedStatement 和 Statement 的两种参数处理方式不了解,建议自己去补一补 JDBC 相关的内容。

  • 参数传递

    #{ }: 传递参数后 SQL 语句自动为参数加上单引号

    ${ }: 传递参数后 SQL 语句不会为参数加上单引号

  • SQL 注入

    #{ }:可以防止 sql 注入

    ${ }:不可以防止 sql 注入

    注:SQL 注入是黑客攻击服务器的一种简单手段,感兴趣的话可以去看我写的关于 SQL 注入的详细博客文章。

MyBatis 参数传递总结

参数传递方式

  • 参数传递 #{ } 方式:#{0} 、 #{param1} 、 @param(别名) / #
  • 参数传递 ${ } 方式:用法同 #{ } 相同,注意与 #{ } 的区别

项目开发建议

  • 建议接口参数一律使用 @param("参数名") 为参数取别名(可读性高)
  • 建议只要能用 #{ } 的地方尽量不使用 ${ }(安全性高)
posted @ 2020-09-29 20:52  Binge-和时间做朋友  阅读(635)  评论(0编辑  收藏  举报