数据库模糊搜索时,关键字中处理特殊字符(%,_)

本文介绍在SQL中如何处理含有特殊字符如百分号(%)的模糊查询。通过使用ESCAPE关键字定义转义符,可以实现对含有通配符的字符串进行精确匹配。文中还提供了具体的示例代码。

数据库模糊搜索时,都知道应该用通配符%号来模糊匹配。如:select *from table where content like '%key%'。但当关键字key中也包含有%号时,应该怎么办?

 

  数据库中有关键字:escape就是用来转换的。使用escape关键字定义转义符时,当转义符置于通配符之前时,该通配符就解释为普通字符。用法:ESCAPE 'escape_character' 其允许在字符串中搜索通配符而不将其作为通配符使用。'escape_character'通常用'/'或'\'做为转义符,其它如‘!'等也可以做为转义符。

 

示例:

  select *from table where content like '%/%%' escape '/'

  /为转义字符,第二个%为普通字符,第一、三个%为通配符。

 

  select *from table where content like '%/%//%' escape '/'

  /为转义字符,第二个%为普通字符,第一、三个%为通配符,第二个/为转义字符,第三个/为普通字符。

----------转载http://www.cnblogs.com/joinclear/archive/2013/02/25/2932842.html

if(StringUtils.isNotEmpty(param.getCode())){
				hql.append("and node.code like ? escape ? ");
				params.put(String.valueOf(params.size()), "%" + Common.getSqlLikeEscapeStr(param.getCode()) + "%");
				params.put(String.valueOf(params.size()), Constants.CON_SQL_LIKE_ESCAPE);
			}

 

public static String getSqlLikeEscapeStr(String str){
		
		str = str.replaceAll("/", "//");
		str = str.replaceAll("%", "/%");
		str = str.replaceAll("_", "/_");
		
		return str;
	}

 

 

 

<think>我们正在解决MyBatis模糊查询中特殊字符%和_)导致查询失效的问题。根据引用资料,我们知道当模糊查询的条件中包含%或_,它们会被解释为通配符,而不是普通字符,从而导致查询结果不正确。例如,我们想查询包含"abc_"的字符串,但"_"会被解释为匹配任意一个字符的通配符,从而可能返回不相关的结果。解决方案的核心是对这些特殊字符进行转义,使它们被当作普通字符处理。在SQL中,我们可以使用转义字符(通常用'\')来转义特殊字符。例如,将查询条件中的'%'替换为'\%',将'_'替换为'\_'。然后,在LIKE子句中指定ESCAPE关键字来声明转义字符。但是,在MyBatis中,我们需要在传入查询条件之前对字符串进行处理,或者在SQL语句中进行处理。根据引用资料,有几种解决方案:1.在Java代码中,对查询参数进行转义处理,将%和_替换为\%和\_,然后在SQL语句中使用LIKE查询,指定转义字符。例如:Stringkeyword="abc_";keyword=keyword.replace("%","\\%").replace("_","\\_");然后在Mapper的SQL中:`likeconcat('%',#{keyword},'%')escape'\'`2.使用MyBatis的拦截器(Interceptor)对查询参数进行全局处理,自动转义所有包含在like查询中的特殊字符。引用[4]提到了一个拦截器的实现方式。3.在SQL语句中直接使用转义,例如:`SELECT*FROMtableWHEREcolumnLIKE'%abc\_%'ESCAPE'\'`然而,在实际项目中,我们可能希望有一个统一的解决方案,避免在每个查询点都手动处理。因此,使用拦截器是一个较为优雅的方式。下面我们详细说明两种主要方法:方法一:手动转义(适用于少量查询)步骤:1)在业务代码中对传入的查询关键字进行转义。例如:publicStringescapeForLike(Stringkeyword){if(keyword==null)returnnull;returnkeyword.replace("\\","\\\\").replace("%","\\%").replace("_","\\_");}注意:因为反斜杠本身也是转义字符,所以需要先替换反斜杠,再替换其他字符。2)在Mapper的XML文件中,使用ESCAPE子句:<selectid="selectByKeyword"resultType="...">SELECT*FROMtableWHEREcolumnLIKECONCAT('%',#{keyword},'%')ESCAPE'\'</select>方法二:使用拦截器自动转义(推荐,一劳永逸)我们可以创建一个MyBatis拦截器,拦截所有Executor的查询方法,然后对查询参数进行扫描,如果发现参数是字符串并且用于like查询(通常通过参数名或注解标识),则进行转义。引用[4]提供了一个拦截器的实现,我们可以参考它。步骤:1)创建一个拦截器类,实现MyBatis的Interceptor接口。2)在拦截器的intercept方法中,对参数进行处理。我们可以遍历参数对象,如果参数是字符串,则进行转义;如果参数是Map,则遍历Map中的字符串值进行转义(注意:需要识别哪些参数是用于like的,或者全局处理所有字符串参数?但全局处理可能影响非like查询,所以最好通过注解或命名约定来标记)。3)但是,引用[4]中的拦截器可能是通过识别SQL语句中是否有“like?”来判断的。这样可能会误判,但可以作为一种方式。下面是一个简单的拦截器实现示例(注意:这只是一个基础版本,实际使用可能需要根据项目调整):```javaimportorg.apache.ibatis.executor.Executor;importorg.apache.ibatis.mapping.MappedStatement;importorg.apache.ibatis.plugin.*;importorg.apache.ibatis.session.ResultHandler;importorg.apache.ibatis.session.RowBounds;importjava.util.Map;importjava.util.Properties;@Intercepts({@Signature(type=Executor.class,method="query",args={MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})})publicclassMybatisLikeSqlInterceptorimplementsInterceptor{@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{Object[]args=invocation.getArgs();//第二个参数是查询参数Objectparameter=args[1];if(parameter!=null){if(parameterinstanceofMap){//处理Map参数,通常多个参数会放在Map中Map<?,?>parameterMap=(Map<?,?>)parameter;for(Map.Entry<?,?>entry:parameterMap.entrySet()){Objectvalue=entry.getValue();if(valueinstanceofString){StringstrValue=(String)value;//判断该参数是否用于like查询?可以通过参数名判断,例如参数名包含"keyword"等,或者通过其他约定//但这里我们无法直接知道,所以我们可以选择对所有字符串参数都进行转义(激进)或者通过参数名判断(保守)//这里我们选择保守:假设参数名以"Like"结尾的表示用于like查询StringparamName=entry.getKey().toString();if(paramName.toLowerCase().endsWith("like")){entry.setValue(escapeForLike(strValue));}}}}elseif(parameterinstanceofString){//如果是单个字符串参数,我们无法判断是否是like查询,所以这里不处理//如果需要处理,可以在此处添加逻辑,但通常单个字符串参数我们无法得知其用途,所以最好通过Map方式传递参数}}returninvocation.proceed();}privateStringescapeForLike(Stringkeyword){if(keyword==null)returnnull;returnkeyword.replace("\\","\\\\").replace("%","\\%").replace("_","\\_");}@OverridepublicObjectplugin(Objecttarget){returnPlugin.wrap(target,this);}@OverridepublicvoidsetProperties(Propertiesproperties){}}```4)将拦截器注册到Spring中(如引用[4]所示):```javaimportorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassMybatisLikeSqlConfig{@BeanpublicMybatisLikeSqlInterceptormybatisSqlInterceptor(){returnnewMybatisLikeSqlInterceptor();}}```注意:这个拦截器示例中,我们假设用于like查询的参数名以"Like"结尾(不区分大小写),例如:`@Param("nameLike")`。这样,在Mapper接口中,我们需要将用于like查询的参数名按照这个约定命名。在Mapper接口中:List<User>findByNameLike(@Param("nameLike")StringnameLike);在XML中:<selectid="findByNameLike"resultType="User">SELECT*FROMuserWHEREnameLIKECONCAT('%',#{nameLike},'%')ESCAPE'\'</select>这样,拦截器会自动将传入的nameLike参数进行转义。方法三:在SQL语句中使用函数进行转义(不推荐,因为每个数据库的字符串函数不同)例如,在MySQL中,我们可以使用`REPLACE`函数嵌套替换,但是这样会使SQL语句非常复杂,而且性能可能较差。总结:推荐使用方法二(拦截器)进行全局处理,这样在代码中不需要每次都手动转义,只需要遵守参数命名约定即可。但是,请注意,使用拦截器可能会对性能有轻微影响,并且需要确保不会误转义非like查询的参数。因此,参数命名约定非常重要。另外,如果项目中只有少量地方需要模糊查询特殊字符,也可以选择手动转义。最后,在编写SQL,记得在LIKE条件中使用ESCAPE子句指定转义字符(这里是反斜杠)。根据引用资料,我们还可以看到,如果不使用ESCAPE子句,即使转义了字符,数据库也不会将其视为转义,所以ESCAPE子句是必须的。因此,在MapperXML中,我们需要确保每个like查询都写成类似:`LIKECONCAT('%',#{param},'%')ESCAPE'\'`或者`LIKE'%${param}%'ESCAPE'\'`(注意:不推荐使用${},因为有SQL注入风险)推荐使用CONCAT函数,因为#{}可以防止SQL注入。以上方案请根据项目实际情况选择。下面,我们生成相关问题:</think>### MyBatis模糊查询特殊字符%和_)过滤失效解决方案 在MyBatis中使用`LIKE`进行模糊查询,`%`和`_`会被解析为SQL通配符而非普通字符,导致查询失效[^1][^2]。以下是完整解决方案: #### 方案一:手动转义特殊字符(推荐) 在Java层对查询参数进行转义处理: ```java public String escapeSpecialChar(String keyword) { if (keyword == null) return null; return keyword.replace("\\", "\\\\") .replace("%", "\\%") .replace("_", "\\_"); } // 使用示例 String escapedKeyword = escapeSpecialChar(rawKeyword); userMapper.selectByKeyword(escapedKeyword); ``` 在Mapper XML中使用`ESCAPE`子句: ```xml <select id="selectByKeyword" resultType="User"> SELECT * FROM users WHERE username LIKE CONCAT('%', #{keyword}, '%') ESCAPE '\' </select> ``` **原理**:通过`ESCAPE '\'`声明反斜杠为转义符,将`\%`和`\_`转换为普通字符[^1][^4]。 --- #### 方案二:拦截器全局处理(MyBatis Plus) 创建转义拦截器自动处理所有`LIKE`参数: ```java @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class MybatisLikeSqlInterceptor implements Interceptor { private static final Pattern LIKE_PATTERN = Pattern.compile("(?i) like \\?"); @Override public Object intercept(Invocation invocation) throws Throwable { Object parameter = invocation.getArgs()[1]; if (parameter instanceof Map) { for (Map.Entry<?,?> entry : ((Map<?,?>) parameter).entrySet()) { if (entry.getValue() instanceof String) { String sql = invocation.getArgs()[0].toString(); if (LIKE_PATTERN.matcher(sql).find()) { entry.setValue(escapeSpecialChar((String) entry.getValue())); } } } } return invocation.proceed(); } private String escapeSpecialChar(String value) { return value.replace("%", "\\%") .replace("_", "\\_") .replace("\\", "\\\\"); } } ``` 注册到Spring容器: ```java @Configuration public class MybatisConfig { @Bean public MybatisLikeSqlInterceptor mybatisSqlInterceptor() { return new MybatisLikeSqlInterceptor(); } } ``` **优势**:无需修改业务代码,自动转义所有`LIKE`查询参数[^4]。 --- #### 方案三:数据库函数转义(MySQL示例) ```sql SELECT * FROM users WHERE username LIKE REPLACE(REPLACE(REPLACE('%${keyword}%', '\\', '\\\\'), '%', '\%'), '_', '\_') ESCAPE '\' ``` > ⚠️ 注意:此方案需谨慎使用`${}`防止SQL注入,建议优先使用方案一/二。 --- ### 关键注意点 1. **转义顺序**:先转义反斜杠`\`,再处理`%`和`_`[^4] 2. **ESCAPE必选**:不声明`ESCAPE '\'`会导致转义失效[^1] 3. **参数传递**:使用`#{}`防止SQL注入,避免直接拼接`${}` 4. **测试用例**:验证`abc_%、50\%_off`等含特殊字符的查询 > 通过上述方案可彻底解决特殊字符导致的模糊查询失效问题,方案二拦截器方式最适合大型项目[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值