mybatis 防注入依赖 #{} 配合 preparedstatement 机制,参数在语义分析后绑定,仅作数据不参与语法构建;所有值类参数必须用 #{},like 查询需 java 层加通配符或数据库函数处理;${} 仅限白名单校验下的动态 sql 场景。

MyBatis 本身不自动防注入,安全与否完全取决于你是否正确使用 #{} 占位符。它的防护能力来自底层 JDBC 的 PreparedStatement 预编译机制——参数值在 SQL 语义解析完成后才被绑定,永远作为纯数据,不会参与语法结构构建。
为什么 #{} 能防注入?关键在执行阶段分离
SQL 执行分三步:语义分析 → 制定执行计划 → 获取结果。#{} 把参数传入安排在“语义分析完成之后”,数据库已锁定语句结构(比如 WHERE name = ?),后续填入的任何内容(哪怕是 ' OR '1'='1)都只是被当作字符串字面量处理,加引号后变成:WHERE name = ''' OR ''1''=''1',无法改变原意。
所有值类参数必须用 #{}
以下场景一律禁用 ${} ,只用 #{} :
-
WHERE 条件:如
username = #{username}、status IN (#{status1}, #{status2}) -
INSERT VALUES:如
VALUES (#{name}, #{age}, #{email}) -
UPDATE SET:如
SET phone = #{phone}, updated_at = NOW() WHERE id = #{id} -
DELETE 条件:如
WHERE id = #{id} AND tenant_id = #{tenantId}
LIKE 模糊查询不能图省事写 ${}
错误写法:WHERE username LIKE '%${keyword}%' —— 直接把用户输入拼进 SQL,等于敞开大门。
正确做法有两种:
-
Java 层加通配符:Service 中将原始 keyword 处理为
"%" + keyword + "%",XML 中仍用#{keyword} -
数据库函数兜底(注意方言兼容):
MySQL:WHERE username LIKE CONCAT('%', #{keyword}, '%')
Oracle:WHERE username LIKE '%' || #{keyword} || '%'
PostgreSQL:WHERE username LIKE '%' || #{keyword} || '%'或WHERE username LIKE concat('%', #{keyword}, '%')
必须用 ${} 的场景要加白名单校验
仅限无法参数化的动态 SQL 元素,且必须严格限制输入范围:
-
ORDER BY 字段:只允许
id、created_time、status等预设列名,禁止传id; DROP TABLE user; -
表名或库名:如多租户分表
SELECT * FROM user_${tenantCode},tenantCode必须从配置或枚举中取,不可来自前端请求 - GROUP BY 表达式:若需动态字段,先查白名单映射表,再拼接
实际代码中建议封装校验工具类,比如:Validate.columnName(sortField) 返回合法字段或抛异常,绝不让原始参数直通 XML。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











