MyBatis(6)#{}和${}的区别

代码纪元 数据库 2024-11-04

MyBatis(6)#{}和${}的区别

在MyBatis中,#{}${}是用于在SQL语句中嵌入参数的两种不同方式。它们的核心区别在于预处理和潜在的SQL注入风险。

#{}(预处理)

#{}用于预处理参数(prepared statement),也就是说,参数占位符会被替换为?,然后参数值会在执行时绑定到SQL语句中。这样做的好处是可以防止SQL注入,因为MyBatis会对参数进行适当的转义处理。

以下是使用#{}的代码示例:java

代码解读
复制代码
@Select("SELECT * FROM user WHERE id = #{id}") User getUserById(@Param("id") Integer id);

MyBatis会将上面的SQL语句转换为一个预处理语句(prepared statement),大致相当于:sql

代码解读
复制代码
SELECT * FROM user WHERE id = ?

然后,MyBatis会将id参数的值安全地绑定到问号(?)位置。

${}(直接替换)

${}进行的是直接字符串替换。你提供的字符串会在MyBatis创建SQL语句之前就被替换到SQL中。这种方式允许你动态地插入表名、列名或者是动态的SQL片段。但由于这种方式可能会导致SQL注入风险,它的使用需要非常小心。

以下是使用${}的代码示例:java

代码解读
复制代码
@Select("SELECT * FROM ${tableName} WHERE id = ${id}") User getUserById(@Param("tableName") String tableName, @Param("id") Integer id);

如果tableName是"user",id是1,那么最终的SQL将会是:sql

代码解读
复制代码
SELECT * FROM user WHERE id = 1

源码分析

当MyBatis解析#{}${}时,它使用了不同的解析器。对于#{},MyBatis使用ParameterMapping来处理每一个参数,将其转换为一个预处理的参数。

对于${},MyBatis将参数的实际值直接拼接到SQL字符串中,这就意味着如果参数包含特殊字符,它们将直接嵌入到SQL中,可能引起安全问题。

在MyBatis的源码中,SqlSourceBuilder 类处理带有#{}的表达式:java

代码解读
复制代码
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); TokenParser parser = new TokenParser("#{", "}", handler); String sql = parser.parse(originalSql); StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql, handler.getParameterMappings()); return sqlSource; }

这里,ParameterMappingTokenHandler 负责将#{}标记的参数转换为预处理参数,并创建相应的ParameterMapping

而对于${}的处理,简单的字符串替换是通过TextSqlNode处理的:java

代码解读
复制代码
public boolean apply(DynamicContext context) { Matcher matcher = pattern.matcher(text); StringBuilder builder = new StringBuilder(); while (matcher.find()) { String replacement = getProperty(matcher.group(1), context.getBindings()); if (replacement != null) { matcher.appendReplacement(builder, replacement); } else { // handle null value ... } } matcher.appendTail(builder); context.appendSql(builder.toString()); return true; }

在这里,getProperty方法直接从上下文中取出变量值并替换掉${}标记的部分。

细节和最佳实践

  • 应尽可能使用#{}来防止SQL注入攻击。
  • 只有在需要动态替换表名、列名或者SQL片段时才考虑使用${}
  • 如果必须使用${},确保参数值来自于信任的源,或者对参数值进行严格的验证和清理,以避免SQL注入风险。
  • 在可能的情况下,考虑使用MyBatis的内置功能,如<if>标签和<choose>标签等,来动态构建SQL语句,而不是依赖${}

总之,在编写安全的MyBatis应用时,理解#{}${}的区别是至关重要的,以确保你的应用不容易受到SQL注入攻击。

转载来源:https://juejin.cn/post/7384256110281293876

Apipost 私有化火热进行中

评论