bind 标签需配合 #{} 使用才能实现安全有效的 LIKE 查询:先用 bind(如 @java.lang.String@format('%s%s%s', '%', name, '%'))预处理用户输入并处理空值,再在 SQL 中用 #{likeName} 引用;严禁用 ${} 拼接,避免 SQL 注入。

bind 标签怎么写才能让 LIKE 查询既安全又生效
直接结论:bind 本身不防 SQL 注入,但它能帮你把用户输入先做预处理(比如加 %),再交给 #{} 参数化,这才是安全的关键。很多人误以为写了 bind 就万事大吉,结果还是用 ${} 拼字符串,照样被注入。
常见错误现象:org.apache.ibatis.executor.ExecutorException: Error preparing statement. Cause: java.sql.SQLSyntaxErrorException: ORA-00907: missing right parenthesis 或 MySQL 报错 You have an error in your SQL syntax,往往是因为 bind 里用了未转义的单引号、括号,或拼出了非法 SQL 片段。
-
bind的表达式是 OGNL,不是 SQL,不能直接写'%' + name + '%'这种裸字符串拼接(OGNL 不支持+字符串连接) - 正确写法是用
concat('%', name, '%')(MySQL)或'%' || name || '%'(Oracle/PostgreSQL),但更推荐统一用 Java 方法:@java.lang.String@format('%s%s%s', '%', name, '%') - 如果
name为null,concat会返回null,导致整个 WHERE 条件失效,必须提前判空或用if处理
为什么不用 ${} 直接拼 LIKE 字符串
因为 ${} 是字符串替换,用户输 admin' OR '1'='1,就会变成 WHERE username LIKE '%admin' OR '1'='1%' —— 典型注入。而 #{} 是预编译参数,数据库根本不会解析里面的逻辑。
使用场景很明确:只要涉及用户可控输入(搜索框、下拉筛选、URL 参数),LIKE 的左右通配符就必须由后端加,不能前端传 %xxx% 再 ${} 插入。
- MySQL 下
concat('%', #{name})是错的 ——#{}不能进函数参数,会被当成字符串字面量 - 必须用
bind先生成带通配符的变量,再用#{}引用,例如:<bind name="likeName" value="@java.lang.String@format('%s%s%s', '%', name, '%')" /> - Oracle 用户注意:
||是拼接符,但空值参与时结果为null,建议改用CONCAT(CONCAT('%', NVL(name, '')), '%')或在 Java 层处理空值
bind 和 if 组合处理空值与边界条件
用户可能不填搜索框,也可能只输空格。这时候 bind 生成的 likeName 如果是 '%%',查出来就是全表扫描,性能灾难;如果是 null,条件被跳过,结果为空。
所以得配合 <if test="name != null and name.trim() != ''"> 控制整个条件块是否出现,而不是靠 bind 硬扛。
-
bind只负责“加工”,不负责“开关”;if才决定这个 LIKE 条件要不要加进 SQL - 别在
bind里写复杂逻辑,比如value="name == null ? '%' : concat('%', name, '%')"—— OGNL 支持有限,容易报ognl.ExpressionSyntaxException - 更稳的做法:Java DTO 中加一个
getLikeName()方法,内部处理空值和 trim,XML 里直接<bind name="likeName" value="likeName"/>
MySQL 与 PostgreSQL 的通配符转义差异
用户搜 100%,你希望匹配真实含百分号的字段,但默认 % 是通配符,必须转义。MyBatis 本身不处理这个,得靠数据库方言和 ESCAPE 子句。
这不是 bind 能解决的问题,但容易被忽略 —— 很多人加了 bind 就以为“模糊查询安全了”,结果特殊字符一搜就崩。
- MySQL 示例:
WHERE name LIKE #{likeName} ESCAPE '\',然后在bind中把、%、_替换成\、%、_ - PostgreSQL 要用
ESCAPE+replace(),例如:value="replace(replace(replace(name, '\', '\\'), '%', '\%'), '_', '\_')" - 千万别用正则或 JavaScript 风格的
replaceAll—— OGNL 不支持,会抛ognl.MethodFailedException
真正麻烦的不是语法,而是不同数据库对空格、Unicode 零宽字符、emoji 的 LIKE 行为不一致。上线前一定用真实数据测边界 case,比如搜一个空格、一个制表符、一个中文顿号。










