我们讨论的占位符通常出现在MyBatis这样的持久层框架中。在MyBatis中,有两种常见的占位符:#{}${}。它们有本质的区别,特别是在安全方面。

1. #{}(井号占位符)

  • 原理:使用#{}时,MyBatis会创建预编译语句(PreparedStatement),并将参数安全地设置到SQL中。它会将参数值作为字符串处理,并自动进行转义,从而防止SQL注入。

  • 示例

    xml

    <select id="selectUser" resultType="User">
      SELECT * FROM users WHERE username = #{username} AND password = #{password}
    </select>

    生成的SQL类似于:

    sql

    SELECT * FROM users WHERE username = ? AND password = ?

    然后通过PreparedStatement的set方法安全地设置参数。

  • 安全性:由于使用了预编译,用户输入的值不会被解析为SQL代码,因此非常安全,可以防止SQL注入。

2. ${}(美元占位符)

  • 原理:使用${}时,MyBatis会直接将参数值替换到SQL语句中,是简单的字符串拼接。不会进行转义,也不会使用预编译。

  • 示例

    xml

    <select id="selectUser" resultType="User">
      SELECT * FROM users WHERE username = '${username}' AND password = '${password}'
    </select>

    如果username输入admin,password输入' OR '1'='1,那么生成的SQL将是:

    sql

    SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1'

    这就会导致SQL注入。

  • 用途${}通常用于动态指定表名、列名等无法使用预编译的地方,比如:

    xml

    <select id="selectUser" resultType="User">
      SELECT * FROM ${tableName} WHERE id = #{id}
    </select>

    但即使这样,也需要非常小心,因为如果表名是用户输入的,那么同样存在风险(虽然不是SQL注入,但可能导致其他问题,如查询非法的表)。

总结

  • 安全性#{}是安全的,可以防止SQL注入;${}是不安全的,容易导致SQL注入。

  • 使用场景

    • 绝大多数情况下,尤其是对于用户输入的值,必须使用#{}

    • 只有在需要动态指定表名、列名等SQL关键字时,才考虑使用${},并且必须确保这些值不是来自用户输入,或者经过严格的校验。

所以,在编写MyBatis的SQL映射时,一定要谨慎使用${},对于用户输入的参数,必须使用#{}