头图

在MyBatis中,#{}${} 是两种用于获取参数值的主要方式,它们的工作原理和使用场景各有不同。在使用MyBatis进行SQL操作时,理解并正确选择这两种方式是保障应用程序安全性和灵活性的关键。接下来,我们将详细分析这两者的区别、使用场景以及注意事项。

1. #{} 占位符的工作原理与使用场景

1.1 #{} 的工作原理

#{} 是MyBatis中的一种预编译参数绑定方式。在SQL语句执行之前,MyBatis 会将 #{} 中的内容解析为一个占位符 ?,然后通过JDBC的 PreparedStatement 来安全地绑定参数。例如,当你编写如下SQL语句:

SELECT * FROM users WHERE id = #{id}

在实际执行时,MyBatis 会将#{id} 替换为 ?,并将传入的参数绑定到 PreparedStatement 对象的 ? 占位符上。这种方式确保了用户输入不会直接拼接到SQL语句中,极大地提高了SQL执行的安全性,尤其是防止了SQL注入攻击

1.2 #{} 的优势

  • 防止SQL注入:由于参数是通过 PreparedStatement 安全绑定的,而不是直接拼接在SQL字符串中,所以能够防止用户恶意输入导致的SQL注入问题。
  • 支持复杂类型#{} 不仅支持简单的数据类型(如intString),还支持复杂的数据类型(如 JavaBeanMap 等)。
  • 性能优化:通过预编译机制,数据库可以缓存SQL语句的执行计划,提高SQL的执行效率。

1.3 使用 #{} 的场景

  • 用户输入的动态参数:任何用户输入的参数,如查询条件、分页参数等,都应通过 #{} 来绑定,以确保安全性。
  • JavaBean、Map 类型的参数:在传递复杂类型的参数时,MyBatis支持通过 #{} 来获取其中的具体值。例如:

    SELECT * FROM users WHERE name = #{user.name} AND age = #{user.age}

1.4 #{} 的使用示例

public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User getUserById(@Param("id") int id);
}

在这个示例中,#{id} 将通过 PreparedStatement 绑定 id 参数,确保SQL执行安全。

2. ${} 的直接拼接方式与使用场景

2.1 ${} 的工作原理

${}** 是MyBatis中的直接文本替换方式。在SQL执行前,**MyBatis 会将 ${} 中的内容直接拼接到SQL字符串中。因此,它不会使用 PreparedStatement 的参数绑定机制,而是将参数直接插入到SQL中,类似于字符串拼接。例如:

SELECT * FROM ${tableName}

在实际执行时,如果 tableName 的值是 "users",SQL语句将直接拼接为:

SELECT * FROM users

这种方式适合用于动态SQL的生成,例如动态表名或列名。然而,由于参数是直接拼接的,${} 存在SQL注入的风险,尤其是在直接接受用户输入的情况下。

2.2 ${} 的适用场景

  • 动态SQL片段:当需要动态生成SQL的一部分(如表名或列名)时,可以使用 ${},因为表名和列名通常无法作为SQL参数绑定。
  • 不涉及用户输入的情况:应避免在用户输入的地方使用 ${},以防止SQL注入攻击。

2.3 使用 ${} 时的注意事项

  • 安全性问题:由于${} 会将参数直接拼接到SQL中,因此必须谨慎使用,尤其是在参数来源于用户输入时,极容易造成SQL注入攻击。
  • 仅用于动态结构:如果参数是动态生成的SQL结构(如表名、列名),并且参数来源是安全的,使用 ${} 是合理的。

2.4 ${} 的使用示例

public interface UserMapper {
    @Select("SELECT * FROM ${tableName}")
    List<User> getUsersFromTable(@Param("tableName") String tableName);
}

在这个例子中,${tableName} 用于动态选择查询的表名,但必须确保 tableName 的来源是可信的,以防止恶意SQL注入。

3. #{} 与 ${} 的对比总结

特性#{}${}
参数处理方式使用 PreparedStatement 参数绑定直接拼接到SQL字符串中
SQL注入风险,因为参数安全绑定,容易造成SQL注入
适用场景安全的动态参数,例如用户输入、查询条件动态生成SQL片段,例如表名、列名
支持的参数类型简单类型、复杂类型(如JavaBean、Map)任何类型,但需保证参数安全

4. 多参数传递的方式

当需要在SQL语句中传递多个参数时,MyBatis会根据传入参数的类型封装参数。以下是几种常见的参数传递方式:

4.1 单个参数

对于单个参数,可以直接使用 #{}${} 来获取参数值:

SELECT * FROM users WHERE id = #{id}

若该参数是一个JavaBean或Map,可以通过 #{} 获取其内部属性值:

SELECT * FROM users WHERE name = #{user.name} AND age = #{user.age}

4.2 多个参数

当方法中存在多个参数时,MyBatis会自动将这些参数封装为一个 Map,其键为 param1param2 等。可以通过 #{param1}#{param2} 来访问这些参数。如果想使用自定义的键名,可以通过 @Param 注解进行指定:

@Select("SELECT * FROM users WHERE id = #{id} AND name = #{name}")
User getUserByIdAndName(@Param("id") int id, @Param("name") String name);

4.3 List 或 数组参数

如果传递的参数是List或数组,MyBatis会将它们封装为 listarray,可以通过 #{list[0]}#{array[0]} 来获取其中的元素:

SELECT * FROM users WHERE id IN (#{array[0]}, #{array[1]})

4.4 Map参数

对于Map类型的参数,可以直接通过 #{key} 来获取对应的值:

SELECT * FROM users WHERE id = #{id} AND name = #{name}

5. 避免SQL注入的建议

为了保障SQL安全性,尽量避免使用 ${} 来获取用户输入的参数。相反,所有用户输入都应通过 #{} 来绑定,以防止SQL注入。对于需要动态生成的SQL结构(如表名、列名),应确保输入的安全性。

5.1 SQL注入攻击的示例

如果不小心使用了 ${} 拼接用户输入,可能会造成如下SQL注入攻击:

SELECT * FROM users WHERE name = '${name}'

如果用户输入了 name = "admin' OR '1'='1",那么最终生成的SQL将是:

SELECT * FROM users WHERE name = 'admin' OR '1'='1'

这将导致查询返回所有用户,而非特定用户。

5.2 正确使用 #{}} 的示例

相同的查询,使用 #{} 来安全绑定参数:

SELECT * FROM users WHERE name = #{name}

即使用户输入了恶意字符串,PreparedStatement 也会将其作为参数处理,不会导致SQL注入问题。

6. 总结与建议

在使用MyBatis时,#{} 和 ${}** 提供了灵活的参数获取方式。**#{} 更加安全,适用于用户输入的参数绑定**,可以有效防止SQL注入。**${} 更适合动态生成SQL结构的场景,但必须确保参数来源的安全性。根据不同场景合理使用这两者,可以使SQL更加安全、高效和灵活。

通过深入理解 #{} 和 ${} 的区别,以及结合多参数传递的实际场景,你将能够编写出更加健壮、可维护的代码,避免常见的安全问题。


蓝易云
28 声望3 粉丝