MySQL模糊查询通配符转义总结

一、问题背景

系统测试过程中,测试同学反馈搜索框输入"%"或"_"进行模糊查询后,查询结果不正确;搜索期望结果是仅包含"%"或"_"的记录。

二、原因

模糊查询是一种通过模糊条件来匹配数据库中数据的查询方法,通常会使用以下两种通配符进行数据匹配。

  • %: 表示零个或多个字符。例如,%cat% 匹配包含 "cat" 的任何字符串。
  • _: 表示一个单一的字符。例如,h_t 匹配 "hat"、"hot" 等。

如果没对通配符进行处理而直接进行查询时,它代表的特殊含义将会使得查询结果不正确。

三、解决思路

想要查询包含 "%"或"_" 字符的字段值,但又不希望它们被解释为通配符时,可以使用转义字符"\" (在MySQL中,默认使用"\"用作转义字符)来取消这些字符的特殊含义。

四、解决方法

1、\ 反斜杠转义:

这里我封装好了一个工具类,当某个查询条件需要进行模糊查询时,先对查询条件进行预处理,然后再塞到SQL中。

(1)工具类代码:

import com.google.common.escape.Escaper;
import com.google.common.escape.Escapers;

public class SqlLikeUtil {

    private static final char MYSQL_WILDCARD_PERCENTAGE = '%';
    private static final char MYSQL_WILDCARD_UNDERSCORE = '_';
    private static final char MYSQL_DEFAULT_ESCAPE = '\\';
    private static final char[] MYSQL_WILDCARDS = new char[]{'%', '_', '\\'};

    public SqlLikeUtil() {
    }

    public static String genMysqlLikedString(String str) {
        return genMysqlLikedString(str, true, true);
    }

    public static String genMysqlLikedPrefixString(String str) {
        return genMysqlLikedString(str, true, false);
    }

    public static String genMysqlLikedString(String str, boolean prefixMatch, boolean suffixMatch) {
        if (str == null) {
            return null;
        } else {
            String escaped = escapeChars(MYSQL_WILDCARDS, '\\', str);
            StringBuilder builder = new StringBuilder();
            if (suffixMatch) {
                builder.append('%');
            }

            builder.append(escaped);
            if (prefixMatch) {
                builder.append('%');
            }

            return builder.toString();
        }
    }
    private static String escapeChars(char[] chars, char escape, String str) {
        if (chars != null && chars.length != 0 && str != null) {
            Escapers.Builder builder = Escapers.builder();
            char[] var4 = chars;
            int var5 = chars.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                char c = var4[var6];
                String replacement = new String(new char[]{escape, c});
                builder.addEscape(c, replacement);
            }

            Escaper escaper = builder.build();
            String result = escaper.escape(str);
            return result;
        } else {
            return str;
        }
    }

}

代码逻辑解释:创建转义器对象,将"%", "_", "\\" 加入到转义器,使用这个转义器即可对字段值进行特殊字符的转义。

这里重点说明一下,数据库中的"\"在Java代码中要表示为 "\\";其次,为什么我们需要对 "\\" 也进行转义。

如果我们的查询条件为name = "\",但此时并未对其进行转义,那么对应的SQL应为:

select * from test where name like '%\%';

这段SQL的查询含义为查询任意字符开头,以"%"结尾的值,与我们设想的不符,正确的SQL应为:

select * from test where name like '%\\%';

这样 "\" 就可被作为普通字符进行查询了。

(2)使用示例

String name = SqlLikeUtil.genMysqlLikedString(req.getName());

注:上述SqlLikeUtil.genMysqlLikedString()方法的调用会使得name值前后被 "%" 包围,所以我们在使用like进行查询条件拼接时,只需直接取值即可;如果想对字段值前后拼接"%"进行控制,可以调用genMysqlLikedString()重载的方法,指定前缀、后缀。

2、指定转义字符进行转义:

在解决问题的过程中,我发现并不是只可以使用 "\" 进行转义,可以使用ESCAPE关键字在SQL中指定转义字符。

ESCAPE 是一个用于指定转义字符的关键字,ESCAPE 关键字允许你指定一个转义字符,用于转义模糊查询中的特殊字符。比如,在模糊查询中使用了 ESCAPE '/',这意味着斜杠 / 被指定为转义字符。在这个例子中,%/_ 表示任意字符和任意单个字符,而 ESCAPE '/' 指定了斜杠 / 为转义字符。因此,/% 和 /_ 会被解释为普通字符,而不是通配符。

(1)工具类代码:

import com.google.common.escape.Escaper;
import com.google.common.escape.Escapers;

public class SqlLikeUtil {

    private static final char MYSQL_WILDCARD_PERCENTAGE = '%';
    private static final char MYSQL_WILDCARD_UNDERSCORE = '_';
    private static final char MYSQL_DEFAULT_ESCAPE = '\\';
    private static final char[] MYSQL_WILDCARDS = new char[]{'%', '_', '/'};

    public SqlLikeUtil() {
    }

    public static String genMysqlLikedString(String str) {
        return genMysqlLikedString(str, true, true);
    }

    public static String genMysqlLikedPrefixString(String str) {
        return genMysqlLikedString(str, true, false);
    }

    public static String genMysqlLikedString(String str, boolean prefixMatch, boolean suffixMatch) {
        if (str == null) {
            return null;
        } else {
            String escaped = escapeChars(MYSQL_WILDCARDS, '/', str);
            StringBuilder builder = new StringBuilder();
            if (suffixMatch) {
                builder.append('%');
            }

            builder.append(escaped);
            if (prefixMatch) {
                builder.append('%');
            }

            return builder.toString();
        }
    }
    private static String escapeChars(char[] chars, char escape, String str) {
        if (chars != null && chars.length != 0 && str != null) {
            Escapers.Builder builder = Escapers.builder();
            char[] var4 = chars;
            int var5 = chars.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                char c = var4[var6];
                String replacement = new String(new char[]{escape, c});
                builder.addEscape(c, replacement);
            }

            Escaper escaper = builder.build();
            String result = escaper.escape(str);
            return result;
        } else {
            return str;
        }
    }

}

说明:与使用 "\" 进行转义不同的是,在使用工具类对查询条件转义后,还需要在SQL中加ESCAPE '/',例如:where name like #{name} ESCAPE '/'。

如果没有其他场景要求的话,使用"\"进行转义更方便。

最后, SqlLikeUtil工具类可能不太完善,但可以给大家提供一个思路,大家可以根据自己的实际想法与需求完善这个工具类,希望我的思路可以帮到大家~

你可能感兴趣的:(Java,mysql,java,mybatis)