使用like模糊匹配传入%入参不生效。
文章目录
- 原因
- 解决方法
- 插曲(get请求支持特殊字符)
原因
使用mybatis-plus支持模糊查询,例如:
LambdaQueryWrapper<XXXDO> query = new LambdaQueryWrapper<>();
query.like(StringUtils.isNotEmpty(ro.getName()), XXXDO::getName, ro.getName());
以上写法表示需要支持按照name字段的模糊查询,正常情况下查询都是符合要求的。但是如果传入的name=%,这个时候查询条件就不生效了,预期结果应该是模糊匹配名字中有%的结果集,但是查出来的却是全部。
我们也知道如果用like进行全模糊匹配,例如模糊匹配某个字符前后都要用%,这个时候如果匹配的字符本身也是%,那sql语句就会变成这样:
SELECT
*
FROM
table_a
WHERE
`name` LIKE '%%%'
结果就是查出来了所有的结果集,查看打印出来的sql语句也验证了确实没有生效查询条件。
解决方法
我们采用INSTR语句来进行模糊匹配调整。mysql的内置函数instr(filed,str),作用是返回str子字符串在filed字符串的第一次出现的位置,如果没有找到,则返回0。
这个时候我们只要判断INSTR判断的结果大于0即表示包含该字符,就可以绕过like语句的%被忽略的问题。
改造结果如下:
query.apply(StringUtils.isNotEmpty(ro.getName()), String.format("INSTR(`%s`, '%s') > 0", "name", ro.getName()))
其中name为需要模糊匹配的表字段。如果我们要实现诸如XXXDO::getName的写法,我们进行优化提炼封装一个单独处理INSTR的工具类:
public class SqlUtils {
public static <T> String likeByInstr(SFunction<T, ?> filed, String val) {
return String.format("INSTR(`%s`, '%s') > 0", ColumnUtils.getName(filed, true), val);
}
}
public class ColumnUtils {
public ColumnUtils() {
}
public static <T> String getName(SFunction<T, ?> fn, boolean toUnderScoreCase) {
Method writeReplaceMethod;
try {
writeReplaceMethod = fn.getClass().getDeclaredMethod("writeReplace");
} catch (NoSuchMethodException var7) {
throw new RuntimeException(var7);
}
boolean isAccessible = writeReplaceMethod.isAccessible();
writeReplaceMethod.setAccessible(true);
SerializedLambda serializedLambda;
try {
serializedLambda = (SerializedLambda)writeReplaceMethod.invoke(fn);
} catch (InvocationTargetException | IllegalAccessException var6) {
throw new RuntimeException(var6);
}
writeReplaceMethod.setAccessible(isAccessible);
String fieldName = serializedLambda.getImplMethodName();
if (fieldName.startsWith("get")) {
fieldName = fieldName.substring(3);
} else if (fieldName.startsWith("is")) {
fieldName = fieldName.substring(2);
}
fieldName = fieldName.replaceFirst(fieldName.charAt(0) + "", (fieldName.charAt(0) + "").toLowerCase());
return toUnderScoreCase ? StringUtils.toUnderScoreCase(fieldName) : fieldName;
}
}
我们使用封装的SqlUtils中的likeByInstr()方法即可实现传入SFunction,即最终效果如下:
query.apply(StringUtils.isNotEmpty(ro.getName()), SqlUtils.likeByInstr(XXXDO::getName, ro.getName()))
插曲(get请求支持特殊字符)
我们列表查询的Controller一般都会使用get请求,这个时候如果用户在输入框里输入了%或者其他的特殊字符,预期结果是查询到包含%的匹配结果,但是结果却是该查询条件并未生效。
通过检查可以发现,由于get请求的路径参数会直接拼接在地址栏上,这个时候特殊字符是没办法被正确识别到的,故特殊字符并没有被作为入参传递到后端参数接收。
这个时候有几种解决方案,将get请求调整为post请求并使用body传参,或者前端将特殊字符进行转义后再传递,例如使用encodeURIComponent()方法进行手动转义。
// 原始,参数未生效
var url = "http://localhost:8080/login?name=%";
// 使用转义,参数生效
var url = "http://localhost:8080/login?name=" + encodeURIComponent("%");
特殊字符对应关系如下:
字符 | 特殊字符的含义 | URL编码 |
+ | URL 中+号表示空格 | %2B |
空格 | URL中的空格可以用+号或者编码 | %20 |
/ | 分隔目录和子目录 | %2F |
? | 分隔实际的 URL 和参数 | %3F |
% | 指定特殊字符 | %25 |
# | 表示书签 | %23 |
& | URL 中指定的参数间的分隔符 | %26 |
= | URL 中指定参数的值 | %3D |
参考资料:
- get请求参数特殊字符(&)处理