qinghuan
← JDBC 查询与资源生命周期目录

JDBC QUERIES / 补充讲义 3 / 6

名称搜索中的字面量与 LIKE 通配符

参数绑定固定 SQL 结构后,仍要单独定义百分号、下划线和转义字符的搜索语义。

如果工具承诺按用户输入的文字搜索名称,输入中的 % 和 _ 就应该按字面字符处理。参数绑定不会自动取消 SQL LIKE 的通配含义。

前置:商户 REST 与查询契约。以下 Java 骨架和数据库练习未运行。

参数绑定与匹配规则

MySQL LIKE 模式中的 % 可匹配零个或多个字符,_ 匹配一个字符;匹配这些符号本身须使用转义字符,允许通过 ESCAPE 显式指定。未指定时的反斜杠规则还受 NO_BACKSLASH_ESCAPES 模式影响。Spring 的 query(String, RowMapper, Object...) 使用预处理语句绑定参数。来源:MySQL String Comparison Functions and Operators;JdbcTemplate API。两份文档分别支持数据库匹配语义和 Java 参数绑定,并非两个来源独立验证同一实现。

参数绑定不自动取消 LIKE 的通配语义。若工具承诺“名称包含用户输入的文字”,直接绑定 % + 输入 + % 会把输入中的百分号、下划线解释为模式,而非原文字面字符;这与 SQL 结构注入是不同问题。建议默认定义为字面子串查询,显式选用 ! 转义,先转义输入中的 !,再处理 % 和 _,最后添加由程序控制的两侧通配符;不要把处理后的模式拼接进 SQL。租户范围仍从已认证上下文取得,不允许模型自行指定或省略。

构造字面子串模式

以下假定 tenantId 已由服务端授权层确定,shopRowMapper 已定义;输入校验先于数据库调用。空白拒绝是本练习的产品契约,不是 MySQL 强制规定。保留合法输入中的空格,不在此函数暗中 trim。

文中示例 · java
static String literalContainsPattern(String input) {
    if (input == null || input.isBlank()) {
        throw new IllegalArgumentException("name_required");
    }
    return "%" + input.replace("!", "!!")
            .replace("%", "!%").replace("_", "!_") + "%";
}

// SQL 结构固定,模式只是参数值
var shops = jdbcTemplate.query(
    "SELECT id, name FROM shop WHERE tenant_id = ? "
    + "AND name LIKE ? ESCAPE '!' ORDER BY id LIMIT 20",
    shopRowMapper, tenantId, literalContainsPattern(name));

练习 比较命中集合

在隔离数据库准备 A_B、AXB、50% Coffee、500 Coffee、Hi!Shop 和另一租户同名商户。分别查询 _、%、!,断言只按字面符号匹配且不越租户;加入含单引号输入,确认仍走绑定参数、不改变 SQL 结构。再比较未转义模式与上述方案的命中集合,验证问题是查询语义扩大而不只是异常。通过 HTTP 与工具两个入口跑同组夹具,核对名称搜索说明与真实返回一致。

结果完整性与环境条件

本骨架不承诺性能、完整分页或大小写敏感匹配;LIMIT 20 是演示上限,不能向用户声称返回全部。字符比较仍受实际排序规则影响,应沿用已有文本比较章节验收;本轮不重复该主题。记录实际 MySQL、JDBC 驱动、Spring 版本和 SQL mode 后再执行测试。运行前按实际版本核对驱动与项目兼容性。

来源与版本

MySQL 8.4 手册与 Spring Framework 7.0.10 API。资料原核查时间为 2026 年 10 月 28 日 12:05–12:06 JST;独立发布日期未标明或未核实。上述两份来源分别支持匹配语义与 Java 参数绑定,不代表数据库实验已经执行。

固定版本实现对照