# MyBatis-plus 编写 SQL 中 #与 $ 的使用区别
# 今天看到了一个 sql 如下:
dao
/** | |
* 根据 price 和多个 name 来查询信息 | |
* @param | |
* @return | |
* @author 窦凯欣 | |
* @date 2024 年 07 月 09 日 0009 17:38:31 | |
*/ | |
@Select({ | |
"SELECT", | |
"id, name", | |
"FROM tab_class", | |
"WHERE", | |
"price = #{price} and", | |
"name in", | |
"(${res})" | |
}) | |
List<ClassEntity> getClassAll(String res, Integer price); |
# 三层代码如下:
controller
@GetMapping("/classAll") | |
public List<ClassEntity> getClassAll(@RequestBody List<String> className, Integer price) | |
{ | |
String res = className.stream() | |
.map(r -> | |
{ | |
StringBuilder sb = new StringBuilder(); | |
sb.append("'" + r + "'"); | |
return sb; | |
}).collect(Collectors.joining(",")); | |
return classService.getClassAll(res, price); | |
} |
service
public List<ClassEntity> getClassAll(String res, Integer price) | |
{ | |
return classDao.getClassAll(res, price); | |
} |
请求传参:
json
[
"数学","语文"
]
param
price=10
查询结果:
[
{
"id": 1,
"name": "数学"
},
{
"id": 2,
"name": "语文"
}
]
# 问题:
为什么 price 条件接收参数使用的是 #
而 name 却是 $
呢?我可以换成 #
吗?
PS:不合适!
解释如下:
原因分析:
#
参数替换特点#
会将参数安全替换并转义为一个字符串,因此适用于单个值的传递但对于像
in
子句中的列表,需要直接将参数拼接到 SQL 语句中,而不是作为一个单独的字符串$
参数拼接特点$
直接拼接参数到 SQL 语句中,适用于动态生成的 SQL 片段,例如in
子句中的列表这样可以确保列表中的每个值都是一个独立的元素,而不是被当作单个字符串处理
例举说明:
假设 res
的值是 '数学'
, '语文'
# 使用 #
@Select({ | |
"SELECT", | |
"id, name", | |
"FROM tab_class", | |
"WHERE", | |
"price = #{price} and", | |
"name in", | |
"(#{res})" | |
}) |
如果 res
的值是 '数学'
, '语文'
,MyBatis 会将其转义为一个字符串,导致生成的 SQL 类似于
administrative_division_code in ('\'001\', \'002\', \'003\'') |
这显然是不正确的,因为它将整个列表当作一个单独的字符串
# 使用 $
@Select({ | |
"SELECT", | |
"id, name", | |
"FROM tab_class", | |
"WHERE", | |
"price = #{price} and", | |
"name in", | |
"(${res})" | |
}) |
如果 res
的值是 '数学'
, '语文'
,MyBatis 会直接将其拼接到 SQL 语句中,生成的 SQL 类似于
administrative_division_code in ('001', '002', '003') |
这是正确的,因为它将列表中的每个值都作为单独的元素处理
# 总结
#{}
用于安全替换单个值,防止 SQL 注入,但不适合替换包含多个值的列表
${}
用于直接拼接 SQL 片段,适合动态生成的 SQL 语句片段,例如 in
子句中的列表
因此,在 in
子句中传递动态列表时,需要使用 ${}
来确保生成的 SQL 是正确的。使用 #{}
会导致生成的 SQL 不符合预期,甚至可能引发错误
# 替代方案
如果您担心 SQL 注入,可以考虑使用以下方式:
- 传递 List 参数
在 MyBatis 映射文件中使用 <foreach>
标签
@Select({ | |
"<script>", | |
"SELECT", | |
"id, name", | |
"FROM tab_class", | |
"WHERE", | |
"price = #{price} and", | |
"name in", | |
"<foreach collection='res' item='item' open='(' close=')' separator=','>", | |
"#{item}", | |
"</foreach>", | |
"</script>" | |
}) | |
List<ClassEntity> getClassAll(@Param("res") List<String> res,@Param("price") Integer price); |
在 java 代码中传递 List<String>
参数
@GetMapping("/classAll") | |
public List<ClassEntity> getClassAll(@RequestBody List<String> className, Integer price) | |
{ | |
return classService.getClassAll(className, price); | |
} |
打印结果:
[
{
"id": 1,
"name": "数学"
},
{
"id": 2,
"name": "语文"
}
]