1. MyBatis中LIKE查询的痛点与优雅解法
在数据库查询中,模糊匹配是最常用的功能之一。作为Java生态中最流行的ORM框架,MyBatis处理LIKE查询时却存在不少"坑点"。最常见的就是直接在Mapper.xml中拼接%导致的SQL注入风险,或是使用CONCAT函数造成的方言兼容性问题。
我在实际项目中见过各种"野生写法":有在Java代码里拼接好%keyword%再传入的,有用${}直接拼接SQL片段的,甚至还有专门写工具类处理通配符的。这些方案要么存在安全隐患,要么破坏了MyBatis的参数化查询特性。下面分享几种经过生产验证的优雅实现方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 四种安全的LIKE实现方案
2.1 使用SQL字符串函数拼接
最规范的写法是利用数据库的字符串函数。以MySQL为例:
xml复制<select id="searchUsers" resultType="User">
SELECT * FROM users
WHERE username LIKE CONCAT('%', #{keyword}, '%')
</select>
这种写法的优势在于:
- 完全参数化查询,防SQL注入
- 数据库自动处理字符串转义
- 符合SQL标准,可移植性强
注意:Oracle需要使用
||连接符,SQL Server则要用+。如果考虑多数据库兼容,建议配合<if>标签实现方言适配。
2.2 预拼接参数的Java方案
在Mapper接口中预定义查询模式:
java复制public interface UserMapper {
@Select("SELECT * FROM users WHERE username LIKE #{pattern}")
List<User> searchByPattern(@Param("pattern") String pattern);
}
// 调用方
String keyword = "test";
List<User> users = userMapper.searchByPattern("%" + keyword + "%");
这种方案的优点在于:
- SQL保持简洁
- 业务层控制匹配模式(前缀/后缀/全匹配)
- 适合动态匹配场景
2.3 MyBatis拦截器统一处理
对于需要全局统一处理的场景,可以自定义拦截器:
java复制@Intercepts({
@Signature(type= StatementHandler.class,
method="parameterize",
args=Statement.class)
})
public class LikeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
// 识别包含LIKE的参数并自动添加%
}
}
在mybatis-config.xml中注册:
xml复制<plugins>
<plugin interceptor="com.example.LikeInterceptor"/>
</plugins>
2.4 MyBatis-Plus的Wrapper方案
如果使用MyBatis-Plus,其QueryWrapper提供了更优雅的链式调用:
java复制queryWrapper.like("username", keyword)
.likeRight("email", "prefix");
内置支持的匹配模式包括:
like():全匹配%keyword%likeLeft():后缀匹配%keywordlikeRight():前缀匹配keyword%
3. 高级技巧与性能优化
3.1 索引命中优化
LIKE查询最头疼的就是索引失效问题。针对不同场景的优化策略:
- 前缀匹配:
LIKE 'keyword%'可以使用索引 - 后缀匹配:考虑使用反转字符串+函数索引
- 全匹配:建议结合全文索引方案
MySQL 8.0+可以创建函数索引:
sql复制CREATE INDEX idx_reverse_name ON users(REVERSE(username));
查询时:
xml复制<select id="searchUsers">
SELECT * FROM users
WHERE REVERSE(username) LIKE REVERSE(CONCAT('%', #{keyword}))
</select>
3.2 大数据量分页优化
当处理百万级数据时,常规分页会出现性能问题。推荐采用"游标分页"方案:
xml复制<select id="searchUsers" resultType="User">
SELECT * FROM users
WHERE username LIKE CONCAT('%', #{keyword}, '%')
AND id > #{lastId}
ORDER BY id ASC
LIMIT #{size}
</select>
3.3 多字段联合搜索
对于需要同时匹配多个字段的场景:
xml复制<select id="searchUsers" resultType="User">
SELECT * FROM users
WHERE
<bind name="pattern" value="'%' + keyword + '%'"/>
(username LIKE #{pattern} OR email LIKE #{pattern})
AND status = 1
</select>
使用<bind>标签避免重复拼接,同时保持SQL清晰。
4. 生产环境避坑指南
4.1 字符转义问题
当搜索内容包含_或%时需要进行转义:
java复制String escaped = keyword.replace("_", "\\_")
.replace("%", "\\%");
或者在SQL中使用ESCAPE子句:
xml复制WHERE username LIKE '%' || #{keyword} || '%' ESCAPE '/'
4.2 NULL值处理
当keyword为null时的处理策略:
xml复制<select id="searchUsers">
SELECT * FROM users
<where>
<if test="keyword != null and keyword != ''">
AND username LIKE CONCAT('%', #{keyword}, '%')
</if>
</where>
</select>
4.3 性能监控建议
在应用监控中需要特别关注:
- 慢查询日志中的LIKE语句
- 没有使用索引的LIKE查询
- 返回结果集过大的模糊查询
推荐配置监控规则:
sql复制-- 监控非前缀匹配的LIKE
SELECT * FROM slow_log
WHERE query_text LIKE '%LIKE%''%''%';
5. 扩展方案:结合全文索引
对于专业搜索场景,建议采用专业方案:
- MySQL全文索引:
sql复制ALTER TABLE users ADD FULLTEXT INDEX ft_idx(username);
SELECT * FROM users
WHERE MATCH(username) AGAINST('+keyword' IN BOOLEAN MODE);
- Elasticsearch集成:
- 通过logstash同步数据
- 使用Spring Data Elasticsearch实现搜索
- 优势:支持分词、相关性评分、拼音搜索等
- Alibaba Druid SQL防火墙:
- 配置禁止使用
LIKE %%全匹配 - 强制使用参数化查询
在实际项目中,我通常会根据数据量级选择方案:
- 10万级以下:优化后的LIKE查询
- 10万-100万级:MySQL全文索引
- 100万级以上:Elasticsearch专业搜索
最后提醒一点:所有方案都要考虑业务场景。如果是管理后台的筛选功能,LIKE完全够用;如果是面向用户的搜索框,建议直接上专业搜索引擎。
