1. 为什么需要处理模糊查询中的通配符在MyBatis-Plus框架中进行模糊查询时最常用的方式就是使用LIKE语句配合%和_这两个通配符。%表示匹配任意多个字符_表示匹配单个字符。看似简单的语法背后却隐藏着严重的安全隐患和性能风险。我曾在项目中遇到过这样一个案例前端传入的用户名参数直接拼接到了LIKE语句中当用户输入%时系统执行了SELECT * FROM user WHERE username LIKE %这样的查询。这个简单的百分号导致数据库进行了全表扫描不仅返回了全部用户数据还造成了严重的性能问题。更糟糕的是如果这个接口没有做好权限控制就可能造成数据泄露。通配符带来的风险主要体现在三个方面全表扫描风险未转义的通配符会导致数据库执行全表查询SQL注入风险恶意用户可能构造特殊输入进行注入攻击性能问题不必要的全表扫描会消耗大量数据库资源2. 通配符转义的实现原理2.1 SQL通配符的转义机制SQL标准中其实已经提供了通配符转义的解决方案 - 使用ESCAPE关键字。比如我们可以这样写SELECT * FROM user WHERE username LIKE %\%% ESCAPE \这条SQL中的第二个%被转义为普通字符不再具有通配符功能。MyBatis-Plus的拦截器正是基于这个原理实现的。2.2 拦截器的工作时机MyBatis-Plus的InnerInterceptor可以在SQL执行前对语句进行拦截和修改。我们的通配符转义拦截器需要在以下时机工作SQL解析完成后参数绑定前实际执行前拦截器会检查SQL中是否包含LIKE语句如果存在则对相关参数值进行转义处理将%替换为%_替换为_。3. 自定义拦截器的完整实现3.1 拦截器类结构设计我们先定义一个继承InnerInterceptor的拦截器类public class EscapeLikeSqlInterceptor implements InnerInterceptor { private static final String LIKE_SQL like ; private static final String SQL_SPECIAL_CHARACTER _%; private static final String IGNORE_FLAG EscapeLikeSqlIgnore; Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 拦截逻辑实现 } }3.2 核心转义逻辑实现转义的核心方法是处理参数中的特殊字符private String escapeSpecialChars(String input) { if (StringUtils.isEmpty(input)) { return input; } StringBuilder sb new StringBuilder(); for (int i 0; i input.length(); i) { char c input.charAt(i); if (SQL_SPECIAL_CHARACTER.indexOf(c) ! -1) { sb.append(\\); } sb.append(c); } return sb.toString(); }3.3 处理不同类型的参数拦截器需要处理多种参数类型Map类型参数最常见的情况需要处理Wrapper条件实体对象参数需要反射获取字段值基本类型参数直接处理字符串对于Map参数中的Wrapper条件我们需要特殊处理private void processWrapperParams(MapString, Object paramMap) { paramMap.forEach((key, value) - { if (value instanceof String) { String strValue (String)value; if (strValue.contains(%) || strValue.contains(_)) { paramMap.put(key, escapeSpecialChars(strValue)); } } }); }4. 拦截器的配置与使用4.1 Spring Boot配置方式在Spring Boot中配置拦截器非常简单Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 添加通配符转义拦截器 interceptor.addInnerInterceptor(new EscapeLikeSqlInterceptor()); // 其他拦截器 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } }4.2 特殊情况处理有时候我们确实需要通配符的功能这时可以通过添加特殊标记来跳过转义MapString, Object params new HashMap(); params.put(username, %admin%); params.put(EscapeLikeSqlIgnore, true);4.3 性能优化建议拦截器会对所有SQL进行检查为了减少性能影响我们可以添加缓存机制避免重复解析相同SQL使用快速失败策略先检查是否包含LIKE关键字对简单查询进行短路处理5. 实际应用中的注意事项5.1 与分页插件的配合使用在使用分页插件时要注意拦截器的添加顺序// 正确的顺序应该是先添加其他拦截器最后添加分页拦截器 interceptor.addInnerInterceptor(new EscapeLikeSqlInterceptor()); interceptor.addInnerInterceptor(new PaginationInnerInterceptor());5.2 多数据源环境下的配置在多数据源环境下需要为每个SqlSessionFactory单独配置拦截器Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean factory new MybatisSqlSessionFactoryBean(); factory.setDataSource(dataSource); MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new EscapeLikeSqlInterceptor()); factory.setPlugins(interceptor); return factory.getObject(); }5.3 测试用例设计好的测试用例应该覆盖以下场景普通字符串参数包含通配符的参数嵌套对象参数需要跳过转义的特殊情况各种Wrapper查询条件6. 方案对比与优化方向6.1 与其他方案的比较除了拦截器方案还有几种常见的处理方式方案优点缺点拦截器全局生效无需修改业务代码需要处理复杂参数场景AOP可以精确控制拦截范围配置复杂性能开销大工具类简单直接需要手动调用容易遗漏6.2 可能的优化方向智能识别通过AI识别真正需要转义的场景动态配置支持通过配置中心动态调整拦截规则性能监控添加监控指标统计拦截器耗时注解支持使用注解精确控制哪些方法需要拦截7. 常见问题排查在实际使用中可能会遇到以下问题转义不生效检查拦截器是否正确配置确认参数类型是否被支持查看是否有跳过标记性能下降检查是否有多余的拦截器确认SQL解析是否高效考虑添加缓存机制特殊字符处理异常测试各种边界情况考虑使用更完善的转义算法检查数据库兼容性8. 最佳实践建议经过多个项目的实践验证我总结了以下经验防御性编程始终假设用户输入是不可信的全面测试覆盖所有可能的参数类型和场景监控报警对全表扫描操作设置监控文档规范在开发规范中明确要求使用转义渐进式优化根据实际性能数据持续优化在最近的一个电商项目中我们部署这套方案后成功阻止了多次潜在的全表扫描操作系统查询性能提升了30%以上。特别是在促销活动期间有效避免了数据库过载的情况。