知其然要知其所以然,探索每一个知识点背后的意义,你知道的越多,你不知道的越多,一起学习,一起进步,如果文章感觉对您有用的话,关注、收藏、点赞,有困惑的地方请评论,我们一起交流!
深入分析MyBatis中#{}
和${}
的区别
1. 核心区别概述
特性 | #{} |
${} |
---|---|---|
处理方式 | 预编译参数(占位符) | 直接文本替换(字符串拼接) |
安全性 | 防止SQL注入 | 存在SQL注入风险 |
适用场景 | 动态参数值(如WHERE条件) | 动态SQL片段(如表名、列名) |
数据类型处理 | 自动类型转换(如String→VARCHAR) | 原样替换(需手动处理类型) |
2. 底层原理剖析
(1)#{}
的工作流程
-
解析阶段 :
MyBatis将#{}
转换为JDBC的PreparedStatement
占位符(?
)。 -
执行阶段 :
通过ps.setXxx()
方法安全地设置参数值,例如:java// 生成的JDBC代码 PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?"); ps.setInt(1, 5); // 自动类型安全处理
(2)${}
的工作流程
-
解析阶段 :
MyBatis直接替换${}
为字符串内容(无占位符)。 -
执行阶段 :
生成静态SQL语句,例如:java// 假设${tableName}的值为"user" Statement stmt = conn.createStatement("SELECT * FROM user"); // 直接拼接
3. 安全性与SQL注入
-
#{}
示例(安全):xml<select id="getUser" resultType="User"> SELECT * FROM user WHERE name = #{name} </select>
-
输入
name = "admin' OR '1'='1"
时,会被转义为:sqlSELECT * FROM user WHERE name = 'admin'' OR ''1''=''1'
-
-
${}
示例(危险):xml<select id="getUser" resultType="User"> SELECT * FROM user WHERE name = '${name}' </select>
-
输入
name = "admin' OR '1'='1"
时,直接拼接为:sqlSELECT * FROM user WHERE name = 'admin' OR '1'='1' -- 返回所有数据!
-
4. 性能对比
维度 | #{} |
${} |
---|---|---|
SQL解析 | 需预编译(首次稍慢) | 直接替换(无预编译开销) |
数据库缓存 | 可复用执行计划(高效) | 每次生成新SQL(无法复用) |
网络传输 | 仅传输参数(体积小) | 传输完整SQL(体积大) |
5. 适用场景与反例
✅ #{}
的正确使用
-
动态条件值 :
xml<select id="findUsers" resultType="User"> SELECT * FROM user WHERE status = #{status} AND create_time > #{createTime} </select>
✅ ${}
的合理使用
-
动态表名/列名 (需手动过滤危险字符):
xml<select id="queryFromTable" resultType="map"> SELECT * FROM ${tableName} WHERE ${columnName} = #{value} <!-- 混合使用 --> </select>
❌ ${}
的错误使用
-
直接拼接用户输入 :
xml<!-- 危险!可能引发SQL注入 --> <select id="login" resultType="User"> SELECT * FROM user WHERE username = '${username}' AND password = '${password}' </select>
6. 特殊场景处理
(1)LIKE
查询
-
安全写法 (使用
#{}
+CONCAT
):xml<select id="searchUsers" resultType="User"> SELECT * FROM user WHERE name LIKE CONCAT('%', #{keyword}, '%') </select>
(2)IN
语句
-
动态参数列表 (结合MyBatis的
foreach
):xml<select id="getUsersByIds" resultType="User"> SELECT * FROM user WHERE id IN <foreach item="id" collection="ids" open="(" separator="," close=")"> #{id} </foreach> </select>
7. 最佳实践总结
-
默认使用
#{}
:
所有动态参数值必须用#{}
,避免SQL注入。 -
谨慎使用
${}
:
仅用于动态SQL片段(如表名、ORDER BY子句),且需手动过滤危险字符:java// 在Java代码中校验表名合法性 if (!isValidTableName(tableName)) { throw new IllegalArgumentException("Invalid table name"); }
-
混合使用策略 :
在需要动态SQL结构时组合两者:xmlORDER BY ${sortColumn} #{sortDirection}