使用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请求参数特殊字符(&)处理