系统测试过程中,测试同学反馈搜索框输入"%"或"_"进行模糊查询后,查询结果不正确;搜索期望结果是仅包含"%"或"_"的记录。
模糊查询是一种通过模糊条件来匹配数据库中数据的查询方法,通常会使用以下两种通配符进行数据匹配。
如果没对通配符进行处理而直接进行查询时,它代表的特殊含义将会使得查询结果不正确。
想要查询包含 "%"或"_" 字符的字段值,但又不希望它们被解释为通配符时,可以使用转义字符"\" (在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工具类可能不太完善,但可以给大家提供一个思路,大家可以根据自己的实际想法与需求完善这个工具类,希望我的思路可以帮到大家~