mybatis 中 mapper 文件里 #{} 与 ${} 的核心区别
mybatis 的 mapper 文件中,#{}和${}是两种参数占位符,核心差异在于参数解析方式、sql 注入风险、适用场景,以下从底层原理、使用场景、示例等维度详细解析:
一、核心区别对比表
| 特性 | #{} | ${} |
|---|---|---|
| 解析方式 | 预编译处理:mybatis 将#{}替换为?,通过 preparedstatement 设置参数(setxxx()) | 字符串替换:直接将参数拼接到 sql 语句中,无预编译 |
| sql 注入风险 | 安全(参数会被自动转义) | 危险(直接拼接,易触发 sql 注入) |
| 参数类型处理 | 自动识别参数类型,自动加引号(如字符串参数拼接后为'value') | 不处理类型,直接拼接(字符串需手动加引号) |
| 适用场景 | 常规参数传递(列值、条件值) | 动态表名、列名、排序字段等 sql 语法部分 |
| 性能 | 预编译可复用执行计划,性能高 | 每次拼接生成新 sql,性能低 |
二、底层原理与示例
1.#{}:预编译占位符(推荐)
原理
mybatis 解析#{}时,会将其替换为 jdbc 的?占位符,生成preparedstatement,并通过setint()/setstring()等方法设置参数,参数会被自动转义(如特殊字符'会被处理为\'),从根本上避免 sql 注入。
示例
mapper 文件:
<select id="getuserbyid" resulttype="user">
select id, name, age from user where id = #{id}
</select>实际执行的 sql(参数 id=1):
-- mybatis生成preparedstatement,参数通过setint(1, 1)设置 select id, name, age from user where id = ?
字符串参数示例(name="张三"):
<select id="getuserbyname" resulttype="user">
select id, name, age from user where name = #{name}
</select>实际执行:参数会被自动加引号,等价于where name = '张三',若参数含特殊字符(如张三' or 1=1),会被转义为'张三\' or 1=1',避免注入。
2.${}:字符串拼接(慎用)
原理
mybatis 直接将${}替换为参数的原始字符串,无任何转义处理,相当于 “硬拼接” sql,因此存在严重的 sql 注入风险,但可用于动态指定 sql 语法的部分(如表名、列名)。
示例
动态表名(按年月分表):
<select id="getuserbymonth" resulttype="user">
select id, name from user_${month} where id = #{id}
</select>参数 month="202512",id=1 时,实际执行 sql:
select id, name from user_202512 where id = ?
风险示例(sql 注入):若使用${}接收用户输入的排序字段:
<select id="getuserlist" resulttype="user">
select id, name from user order by ${sortfield}
</select>当用户传入sortfield="id; drop table user;"时,拼接后的 sql 为:
select id, name from user order by id; drop table user;
直接导致表被删除,风险极高。
三、关键使用原则
1. 优先使用#{}
所有常规参数(如查询条件、插入 / 更新的列值)必须用#{},杜绝 sql 注入风险,同时享受预编译的性能优势。
2. 仅在必要时使用${}
仅当需要动态指定 sql 语法元素时使用${},且必须做严格的参数校验:
// 对${}参数做白名单校验(示例:仅允许指定的排序字段)
public list<user> getuserlist(string sortfield) {
// 白名单
list<string> allowedfields = arrays.aslist("id", "name", "age");
if (!allowedfields.contains(sortfield)) {
sortfield = "id"; // 默认值,防止注入
}
return usermapper.getuserlist(sortfield);
}3. 特殊场景的兼容处理
模糊查询:#{}可配合concat使用(推荐),而非${}:
<!-- 正确:避免注入 -->
select * from user where name like concat('%', #{name}, '%')
<!-- 错误:易注入 -->
select * from user where name like '%${name}%'批量插入:mybatis 的foreach标签中,集合元素用#{}:
<insert id="batchinsert">
insert into user (name, age) values
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.age})
</foreach>
</insert>四、总结
| 场景 | 推荐用法 | 注意事项 |
|---|---|---|
| 传递列值 / 条件值 | #{} | 自动转义,无注入风险 |
| 动态表名 / 列名 / 排序 | ${} + 白名单 | 必须校验参数,限制取值范围 |
| 模糊查询 | #{} + concat | 避免直接拼接%${}% |
核心记住:#{}是 “安全的预编译”,${}是 “危险的字符串拼接”,开发中除非明确需要动态拼接 sql 语法,否则一律使用#{}。
到此这篇关于mybatis的mapper文件中#和$的区别的文章就介绍到这了,更多相关mybatis #和$区别内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论