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 语句结果如下:
        MyBatis参数传递_MyBatis
        执行第二条 SQL 语句结果如下:
        MyBatis参数传递_sql_02
        我们知道 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("参数名") 为参数取别名(可读性高)
      • 建议只要能用 #{ } 的地方尽量不使用 ${ }(安全性高)