1. #{}使用预编译SQL,${}使用即时SQL
预编译SQL:编译一次之后会将编译后的SQL缓存起来,后面再次执行该语句时,不会再次编译,省去解析优化过程。
#{}使用预编译SQL,通过?占位的方式,提前对SQL进行编译,然后把参数填充到SQL语句中
2. #{}会根据参数类型 ,自动拼接 ' ' ,${}会直接进行字符替换,如果参数为字符串时,需要手动添加 ' ' ;
看下面一个例子
1)当参数为Integer 类型时
@Mapper
public interface UserInfoMapper2 {
@Select("select * from userinfo where id = #{id}")
List<UserInfo> getUserInfoById1(Integer id);
@Select("select * from userinfo where id = ${id}")
List<UserInfo> getUserInfoById2(Integer id);
}
@Autowired
private UserInfoMapper2 mapper2;
@Test
void getUserInfoById1() {
List<UserInfo> list = mapper2.getUserInfoById1(1);
System.out.println(list);
}
@Test
void getUserInfoById2() {
List<UserInfo> list = mapper2.getUserInfoById2(1);
System.out.println(list);
}
#{}的结果
${}的结果
2)当参数为字符串时
@Select("select * from userinfo where username = #{name}")
List<UserInfo> getUserInfoByName1(String name);
@Select("select * from userinfo where username = ${name}")
List<UserInfo> getUserInfoByName2(String name);
@Test
void getUserInfoByName1() {
List<UserInfo> list = mapper2.getUserInfoByName1("admin");
System.out.println(list);
}
@Test
void getUserInfoByName2() {
List<UserInfo> list = mapper2.getUserInfoByName2("admin");
System.out.println(list);
}
#{}的结果
${}的结果
当手动加上 ' ' 后,再次运行程序,结果正确
@Select("select * from userinfo where username = '${name}'")
List<UserInfo> getUserInfoByName2(String name);
注意:当参数为Integer等类型时,使用${}也可以手动加上 ' ' ,但可能会使得索引失效,导致效率降低
3. #{}更安全,能够防止SQL注入,${}存在SQL注入风险;
SQL注入:通过操作输入的数据来修改事先定义好的SQL语句;看下面一个例子
@Select("select * from userinfo where username = #{name}")
List<UserInfo> getUserInfoByName3(String name);
@Select("select * from userinfo where username = '${name}'")
List<UserInfo> getUserInfoByName4(String name);
@Test
void getUserInfoByName3() {
List<UserInfo> list = mapper2.getUserInfoByName3("admin' or 1='1");
System.out.println(list);
}
@Test
void getUserInfoByName4() {
List<UserInfo> list = mapper2.getUserInfoByName4("admin' or 1='1");
System.out.println(list);
}
#{}的结果
${}的结果
由此看出, 本意只想传递一个参数,但通过${}传参查询到了所有结果,所以要尽量使用#{}预查询的方式
3. ${}的使用场景:排序功能(order by),模糊查询(like)等场景SQL语句中通过desc指定为降序排列,asc指定为升序排列(默认升序),但是其中desc和asc都不需要带引号,所以通过字符串传递时,直接使用#{}会自动添加 ' ',导致报错,而使用${}是需要手动添加 ' ',这时我们不手动添加就好了,看下面一个例子
@Select("select * from userinfo order by id #{sort}")
List<UserInfo> getUserInfoSort1(String sort);
@Select("select * from userinfo order by id ${sort}")
List<UserInfo> getUserInfoSort2(String sort);
@Test
void getUserInfoSort1() {
mapper2.getUserInfoSort1("desc");
}
@Test
void getUserInfoSort2() {
mapper2.getUserInfoSort2("desc");
}
#{}的结果
${}的结果
可以看出使用${}结果正常, 而此时仍存在SQL注入风险,此时可以写后端代码判断传来的参数是否符合预期,来解决SQL注入问题;
使用#{},搭配MySQL的内置函数concat()来处理某些特定场景,看看下面这个例子
@Select("select * from userinfo where username like '%#{param}%'")
List<UserInfo> getUserInfoLike1(String param);
@Select("select * from userinfo where username like '%${param}%'")
List<UserInfo> getUserInfoLike2(String param);
@Test
void getUserInfoLike1() {
mapper2.getUserInfoLike1("h");
}
@Test
void getUserInfoLike2() {
mapper2.getUserInfoLike2("h");
}
#{}的结果
${}的结果
可以看到直接使用#{}会报错,而${}则没有问题,下面将使用concat函数实现
@Select("select * from userinfo where username like concat('%',#{key},'%')")
List<UserInfo> getUserInfoLike3(String param);
@Test
void getUserInfoLike3() {
mapper2.getUserInfoLike3("h");
}
可以看到使用#{}搭配concat函数也能正确查询了。