# 和 $ 的区别
在MyBatis中,#
和 $
是用来处理参数的两种不同方式,它们之间有一些重要的区别:
#
符号:
#
是用来进行参数占位符的,它会进行 SQL 注入防护。使用 #
时,MyBatis 会将参数值进行预处理,以防止 SQL 注入的问题。
当你在 SQL 语句中使用 #{parameterName}
时,MyBatis 会将 parameterName
所代表的参数值转义,安全地插入 SQL 语句中。
例如:
java
SELECT * FROM users WHERE username = #{username}
如果 username
是一个用户输入的值,MyBatis 会处理这个值并确保不会引起 SQL 注入。
$
符号:
$
用于直接插入参数值,不进行任何处理。这意味着使用 $
的时候,如果参数包含恶意 SQL 代码,可能会导致 SQL 注入问题。
当你在 SQL 语句中使用 ${parameterName}
时,MyBatis 会直接将 parameterName
的值替换到 SQL 语句中。
例如:
java
SELECT * FROM ${tableName}
如果 tableName
是用户输入的值,直接插入可能会导致安全风险。
总结:
- **使用
#
**:安全,参数经过转义,防止 SQL 注入。适用于 SQL 中的条件值或数据列。 - **使用
$
**:不安全,参数不经过处理,直接替换。一般用于列名、表名等需要动态命名的场景,但需谨慎使用。
在实际使用中,建议优先使用 #
,只有在确实需要动态 SQL 结构时才考虑使用 $
,并确保传入的内容是安全可信的。
#{} 和${} 区别
-
#{}:预编译处理, ${}:字符直接替换
-
#{} 可以防⽌SQL注⼊, ${}存在SQL注⼊的⻛险, 查询语句中, 可以使⽤ #{} ,推荐使⽤ #{}
-
但是⼀些场景, #{} 不能完成, ⽐如 排序功能, 表名, 字段名作为参数时, 这些情况需要使⽤${}
-
模糊查询虽然${}可以完成, 但因为存在SQL注⼊的问题,所以通常使⽤mysql内置函数concat来完成
sql注入
下面是一个示例,展示了如何通过使用 $
符号导致 SQL 注入的情况
假设我们有一个 MyBatis 的 Mapper XML 文件,其中定义了一个可插入表名的 SQL 查询:
java
<mapper namespace="com.example.UserMapper">
<select id="getUsersByTable" resultType="com.example.User">
SELECT * FROM ${tableName} WHERE username = #{username}
</select>
</mapper>
在这个查询中,${tableName}
采用了 $
符号,表示我们希望动态插入一个表名,而 #{username}
采用了 #
符号,确保了对用户输入的 username
进行了安全的处理。
SQL 注入示例:
假设调用这个方法的 Java 代码如下:
java
String userInputTableName = "users; DROP TABLE users; --"; // 用户输入的表名
String username = "exampleUser";
List<User> users = userMapper.getUsersByTable(userInputTableName, username);
在上面的代码中,userInputTableName
是来源于不受信任的用户输入。如果用户输入了恶意的 SQL 片段 users; DROP TABLE users; --
,那么最终生成的 SQL 会是:
java
SELECT * FROM users; DROP TABLE users; -- WHERE username = 'exampleUser'
这个查询会导致数据库首先执行 SELECT * FROM users;
,然后在同一个执行上下文中执行 DROP TABLE users;
,从而将 users
表删除。
解决方案:
为了防止这种 SQL 注入,应该避免在 SQL 语句中使用 ${}
直接插入来自用户的输入。应该采用以下做法:
- 使用预定义表名:使用固定值或通过查证安全的值来避免用户直接输入可能的表名。
- 使用
#
符号:对于需要而不应该通过用户直接控制的内容,避免动态 SQL。
例如,我们可以用一个枚举(或其他安全措施)来限制表名:
java
// 使用合法的表名,防止SQL注入
List<User> users = userMapper.getUsersByTable("users", username);
总之,任何时候都应谨慎对待 SQL 的构建,特别是涉及到用户输入时,确保不使用 $
来动态插入外部输入内容,以防止 SQL 注入漏洞。
# 与 $ 分别适用的场景
在 MyBatis 中,#
和 $
各自适用于不同的场景。下面结合代码示例来描述它们的适用场景
使用 #
的场景
#
符号用于安全地绑定参数,适合用于绑定查询条件、列值等情境,确保参数得到转义以避免 SQL 注入。
示例:
java
<mapper namespace="com.example.UserMapper">
<select id="getUserByUsername" resultType="com.example.User">
SELECT * FROM users WHERE username = #{username}
</select>
</mapper>
使用方式:
java
String username = "exampleUser";
User user = userMapper.getUserByUsername(username);
在这个示例中,#{username}
会被 MyBatis 安全处理,防止 SQL 注入。这里 #
的使用是非常合适的,因为 username
由用户输入,可能会包含潜在的恶意内容。
使用 $
的场景
$
符号用于动态构建 SQL 中的结构,比如表名或列名等,适合参数值是已知的、受控的,不需要转义的场景。使用市在容易导致 SQL 注入时,要十分谨慎,仅在确实安全的情况下才使用。
示例:
java
<mapper namespace="com.example.UserMapper">
<select id="getUsersByTableName" resultType="com.example.User">
SELECT * FROM ${tableName} WHERE username = #{username}
</select>
</mapper>
使用方式:
java
String tableName = "users"; // 确保这是一个受控值
String username = "exampleUser";
List<User> users = userMapper.getUsersByTableName(tableName, username);
在这个示例中,${tableName}
用于动态选择表名。在实际使用中,tableName
应该是一个固定的值,确保不会来自于不受信任的用户输入,以避免 SQL 注入。
总结
-
使用
#
的场景:- 查询条件、列值等动态内容
- 任何来自用户输入的值
- 需要安全处理以防止 SQL 注入的场景
-
使用
$
的场景:- 动态构建 SQL 的结构部分,如表名和列名
- 受控且安全的值,不应直接来自用户输入
在编写 MyBatis SQL 时,务必小心选择使用 #
或 $
以保护应用程序的安全性。建议优先使用 #
,只有在必要且安全的情况下才使用 $
。