深入解析MyBatis中#{}和${}的区别与应用场景

在MyBatis框架的使用过程中,SQL映射文件的编写是核心工作之一。而#{}和${}这两种参数占位符语法,虽然看起来相似,却有着本质的区别。正确理解和使用它们,不仅关系到应用程序的安全性,还会影响系统性能。本文将全面剖析这两种语法的区别、实现原理、使用场景以及最佳实践。

一、基本概念与语法

1.1 #{}语法

#{}是MyBatis中的预编译占位符,也称为参数标记。它的基本形式如下:

<select id="findUserById" resultType="User">
    SELECT * FROM users WHERE id = #{userId}
</select>

1.2 ${}语法

${}是MyBatis中的字符串替换占位符,也称为非转义字符串替换。它的基本形式如下:

<select id="findUsersByTable" resultType="User">
    SELECT * FROM ${tableName} WHERE status = 1
</select>

二、底层实现原理

2.1 #{}的工作原理

当MyBatis遇到#{}时,会进行以下处理:

  1. 解析阶段:MyBatis解析SQL映射文件时,识别出#{}标记

  2. 参数处理:运行时将参数值通过PreparedStatement的set方法设置

  3. SQL生成:最终生成带有"?"的预编译SQL语句

例如上面的例子会生成:

SELECT * FROM users WHERE id = ?

然后通过PreparedStatement的setInt/setString等方法设置参数值。

2.2 ${}的工作原理

对于${}的处理则完全不同:

  1. 直接替换:MyBatis在SQL解析阶段就直接将${}替换为实际的参数值

  2. 字符串拼接:替换后的SQL语句是通过字符串拼接而成的

  3. 直接执行:最终生成的是完整的SQL语句,而非预编译语句

例如,如果tableName="users",生成的SQL就是:

SELECT * FROM users WHERE status = 1

三、核心区别对比

3.1 安全性对比

特性#{}${}
SQL注入防护安全,能防止SQL注入不安全,存在SQL注入风险
实现方式参数化查询字符串拼接

#{}示例:

String sql = "SELECT * FROM users WHERE name = #{name}";
// 即使用户输入 name = "' OR '1'='1"
// 实际执行:SELECT * FROM users WHERE name = ? 
// 参数值会被正确处理,不会导致注入

${}示例:

String sql = "SELECT * FROM users WHERE name = '${name}'";
// 如果用户输入 name = "' OR '1'='1"
// 实际执行:SELECT * FROM users WHERE name = '' OR '1'='1'
// 这将返回所有用户数据,造成SQL注入

3.2 性能对比

特性#{}${}
数据库优化支持预编译,可缓存执行计划每次都是新SQL,无法缓存
网络传输只需传输参数需要传输完整SQL
编译次数一次编译多次执行每次都需要重新编译

3.3 使用场景对比

场景#{}${}说明
普通参数值推荐使用#{}
表名动态表名必须使用${}
列名动态列名必须使用${}
ORDER BY子句动态排序需谨慎使用
GROUP BY子句动态分组需谨慎使用
LIKE模糊查询需特殊处理通配符

四、深入应用场景

4.1 必须使用#{}的场景

  1. 所有用户输入的参数值

    WHERE username = #{username} AND password = #{password}
  2. 数值型参数

    WHERE age > #{minAge} AND age < #{maxAge}
  3. 日期型参数

    WHERE create_time > #{startDate}

4.2 可能需要使用${}的场景

  1. 动态表名 

    SELECT * FROM ${tableName}

    适用于分表场景,如表名按年份分表:user_2022, user_2023等

  2. 动态列名 

    SELECT ${columns} FROM users

    适用于动态选择返回字段的场景

  3. ORDER BY排序 

    ORDER BY ${sortColumn} ${sortOrder}

    但更安全的做法是:

    <choose>
        <when test="sortColumn == 'name'">ORDER BY name</when>
        <when test="sortColumn == 'age'">ORDER BY age</when>
        <otherwise>ORDER BY id</otherwise>
    </choose>

4.3 特殊场景处理

LIKE模糊查询的正确写法:

错误方式:

WHERE name LIKE '%${name}%'

正确方式:

// Java代码中处理参数
String nameParam = "%" + name + "%";
WHERE name LIKE #{nameParam}

或使用SQL函数:

WHERE name LIKE CONCAT('%', #{name}, '%')

五、最佳实践建议

5.1 安全性实践

  1. 默认使用#{}:除非确有必要,否则总是使用#{}

  2. 严格过滤${}参数:使用${}时,必须对参数值进行白名单验证

    // 验证表名是否合法
    if (!isValidTableName(tableName)) {
        throw new IllegalArgumentException("Invalid table name");
    }
  3. 避免用户输入直接用于${}:特别是排序、分组等场景

5.2 性能优化实践

  1. 优先使用#{}:利用预编译语句的缓存优势

  2. 减少${}使用频率:对于频繁调用的SQL,避免使用${}导致无法缓存执行计划

  3. 批量处理动态SQL:对于必须使用${}的场景,考虑批量处理减少SQL解析次数

5.3 代码可维护性实践

  1. 明确注释:在使用${}的地方添加注释说明原因

    <!-- 必须使用${}因为表名是动态的 -->
    SELECT * FROM ${tableName}
  2. 集中管理:将动态部分集中管理,便于维护和安全检查

  3. 单元测试:为使用${}的SQL编写额外的安全测试用例

六、常见问题解答

Q1:为什么ORDER BY不能使用#{}?

A:因为#{}会给参数值添加引号,例如:

ORDER BY 'name' 'DESC'  -- 错误的SQL语法

而正确的应该是:

ORDER BY name DESC

Q2:什么情况下必须使用${}?

A:当SQL语句的非参数部分需要动态变化时,如:

  • 动态表名

  • 动态列名

  • SQL关键字(如ASC/DESC)

Q3:如何安全地使用${}?

A:可以采取以下措施:

  1. 使用白名单验证参数值

  2. 避免直接使用用户输入

  3. 对参数值进行转义处理

  4. 最小化使用范围

总结

#{}和${}在MyBatis中扮演着不同的角色:

  • #{} 是安全的参数占位符,适用于几乎所有参数值的场景,能防止SQL注入,性能更好。

  • ${} 是字符串替换,适用于SQL语句本身需要动态变化的场景,但存在安全风险,应当谨慎使用。

在实际开发中,我们应该遵循以下原则:

  1. 默认使用#{}

  2. 谨慎评估${}的使用必要性

  3. 对必须使用${}的场景实施严格的安全控制

  4. 编写清晰的文档和注释说明使用原因

正确理解和使用这两种占位符,将使你的MyBatis应用更加安全、高效和可维护。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值