简介:本文详细对比iBatis框架中#与$两种参数占位符的核心差异,从SQL注入防护、预编译处理、数据类型适配等维度展开分析,结合实际代码示例说明应用场景,帮助开发者避免常见错误并提升系统安全性。
iBatis框架(现MyBatis前身)中的#与$符号均用于动态SQL参数绑定,但二者在底层实现机制和适用场景上存在本质区别。#符号采用预编译方式处理参数,而$符号直接进行字符串替换,这种差异直接影响了SQL语句的安全性、性能表现和可维护性。
| 特性 | #符号 | $符号 |
|---|---|---|
| 处理阶段 | 预编译阶段绑定参数 | SQL拼接阶段替换占位符 |
| 数据类型 | 自动类型转换 | 纯字符串替换 |
| 安全性 | 防SQL注入 | 存在注入风险 |
| 性能 | 支持预编译缓存 | 每次执行重新解析SQL |
当使用#符号时,iBatis会生成带参数占位符的预编译SQL(如SELECT * FROM user WHERE id = ?),数据库驱动在执行前将参数值安全绑定。这种机制确保:
示例代码:
<select id="getUserById" resultType="User">SELECT * FROM user WHERE id = #{userId}</select>
iBatis会根据参数对象的Java类型自动进行JDBC类型映射:
类型映射示例:
public class User {private Integer id; // 自动映射为INTEGERprivate String username; // 自动映射为VARCHARprivate Date createTime; // 自动映射为TIMESTAMP}
WHERE username = #{name} AND status = #{status}
<if>标签)
ORDER BY ${sortField} <if test="order != null"> ${order}</if>
WHERE id IN<foreach item="item" index="index" collection="ids"open="(" separator="," close=")">#{item}</foreach>
$符号采用简单的文本替换,生成的SQL直接包含参数值:
<select id="getTableData" resultType="Map">SELECT * FROM ${tableName} WHERE id = ${id}</select>
执行时可能生成:SELECT * FROM user_data WHERE id = 123
直接拼接字符串的特性导致以下问题:
// 危险示例:用户输入包含"OR 1=1"String tableName = "user OR 1=1";
SELECT ${columns} FROM ${tableName}
WHERE DATE(${dateColumn}) = CURRENT_DATE
ORDER BY ${sortExpr} DESC
<select id="searchUsers" resultType="User">SELECT * FROM ${safeTableName}WHERE username LIKE CONCAT('%', #{keyword}, '%')ORDER BY ${sortField}</select>
关键点:
<select id="dynamicQuery" resultType="Map">SELECT<choose><when test="fields != null">${fields}</when><otherwise>*</otherwise></choose>FROM ${tableName}<where><if test="id != null">AND id = #{id}</if><if test="name != null">AND name LIKE #{name}</if></where></select>
<insert id="batchInsert">INSERT INTO user (name, age) VALUES<foreach collection="users" item="user" separator=",">(#{user.name}, #{user.age})</foreach></insert>
问题代码:
<select id="unsafeQuery" resultType="User">SELECT * FROM user WHERE id = ${userId}</select>
风险:若userId为”1 OR 1=1”,将导致全表扫描
修正方案:
<select id="safeQuery" resultType="User">SELECT * FROM user WHERE id = #{userId}</select>
当必须使用$符号时,建议:
public boolean isValidTableName(String tableName) {return tableName.matches("^[a-zA-Z_][a-zA-Z0-9_]*$");}
随着MyBatis 3.x的普及,参数处理机制得到增强:
User getUser(@Param("id") Integer id);
<bind>标签实现安全拼接
<bind name="pattern" value="'%' + keyword + '%'" />WHERE name LIKE #{pattern}
通过合理区分#与$的使用场景,开发者可以在保证系统安全性的同时,实现灵活的动态SQL构建。建议在新项目中优先采用MyBatis 3.x的增强特性,进一步降低安全风险。