首先 #{} 和 ${} 都可以用来读取调用Mapper接口时传递给方法的形参值,形参可以是基本类型、引用类型(对象),不管是读取基本类型还是对象中的属性,都可以用 #{name} 或 ${name} 直接获取到。
该两者有以下区别:
一、执行过程不同
#{} : 对读取到的参数先使用?来占位,然后去预编译SQL,最后再将?替换为形参值。
${} : 直接替换读取到的形参值,没有预编译的过程。
举例1
有一个名为userinfo的用户信息表,表结构如下:
该表中有一条记录
核心代码:
UserMapper接口
UserMapper.xml实现
这里的SQL语句唯一不同的是#和$符号
UserMapperTest测试类
现在我通过 整型参数 id 查询表中的 username 和 password,看一下分别使用 #{} 和 ${} 的SQL执行结果。
#{}的SQL打印结果:
${}的SQL打印结果:
举例2
例1是读取整型参数的情况,如果我们读取的是字符串类型,SQL语句就不单是#和$占位符一字之差了。
现在我们通过字符串参数username来进行查询。
核心代码:
UserMapper接口
UserMapper.xml实现
这里我先让两个语句仅是有一个占位符的区别。
UserMapperTest测试类
#{}的SQL打印结果:
${}的SQL打印结果:
为什么使用 ${} 读取字符串参数时会报错?我们把这里的SQL语句放到命令行客户端中再尝试一次。
可以看到是一样的错误结果。归根结底是因为${}会将读取到的参数值直接进行替换,而当我们使用字符串值做查询条件时,需要用双引号或单引号将其包裹起来,不然就会出错。
这里用加了引号的语句放到命令行客户端中跑一下,
可以看到查询到了数据,说明字符串值做查询条件必须要用引号包裹,那么我们将UserMapper.xml中的SQL语句修改一下,加上引号。
再运行程序,就看到了查询结果了。
为什么 ${} 需要手动添加引号,而 #{} 确不用?这是因为 #{} 对于字符串参数会帮我们自动添加引号。
小结:
#{} 和 ${} 的使用,对于读取整型参数无差别,当读取字符串参数时,使用 ${} 需要手动添加引号,而#{}不用。
二、SQL注入问题
#{} 是先预编译SQL,然后再替换占位符,不会出现通过SQL注入改变其SQL语句结构的问题;
${} 是直接替换,会出现SQL注入的问题。
举例
我们通过查询 username 和 password 字段来模拟一下最简单的登录过程,这里先看一个正常登录的情况。
核心代码:
UserMapper接口
UserMapper.xml实现
UserMapperTest测试类
#{}的SQL打印结果:
${}的SQL打印结果:
通过正常传参输入的用户名和密码,两种方式貌似都没问题,接下来我们来看一看通过SQL注入来实现非法登录的情况。
我们将测试代码中的密码参数值改成 ’ or 1 = '1
#{}的SQL打印结果:
使用 #{} 当密码错误时,并不会查询到数据,因为先通过预编译然后替换占位符,SQL语句的结构在预编译的时候已经固定了,这里替换成的参数值并不会影响其结构,所以不会有SQL注入问题。
${}的SQL打印结果:
因为 ${} 是直接替换且没有预编译的过程,传递过来的密码参数会存在修改SQL结构的情况,有SQL注入问题。
小结:
#{}没有SQL注入问题,${}存在SQL注入问题,在开发中千万要避免使用${}。
三、可以使用${}的情况
如果当我们传递的参数是SQL关键字时,直接使用#{}会报错,因为#{}会给字符串参数加上引号。可以考虑使用${},但也可以通过其他特殊手段来实现。
举例
现在有一个文章表articleinfo,表结构如下
表中有如下几条数据
现在我想查询出这几条数据,并按阅读量rcount字段的指定排序顺序来返回。
核心代码:
ArticleMapper接口
ArticleMapper.xml实现
ArticleMapperTest测试类
#{}的SQL打印结果:
因为#{}替换成字符串参数时会自动添加引号,所以会报错误信息。
${}的SQL打印结果:
而${}可以解决这个问题
当传参是SQL关键字时,我们可以考虑使用${}来读取,但是一定要对参数值进行合法性校验,且参数值必须是可以穷举的,像这里的 asc/desc 就只有两种情况,因为${}存在SQL注入问题,所以不推荐使用,当传递的是SQL关键字,我们可以用其他方式例如表单中的true或false来决定执行asc还是desc。
扩展
如果写过原生JDBC代码的小伙伴应该知道两个接口:Statement 和 PreparedStatement,我们使用PreparedStatement来进行SQL预编译然后替换占位符,而在MyBatis框架中,${}就对应着Statement,#{}对应着PreparedStatement,框架会帮我们完成拼串的过程然后执行SQL。