mybatis/mybatis-plus模糊查詢語(yǔ)句特殊字符轉(zhuǎn)義攔截器的實(shí)現(xiàn)
mybatis/mybatis-plus模糊查詢語(yǔ)句特殊字符轉(zhuǎn)義攔截器
在開發(fā)中,我們通常會(huì)遇到這樣的情況。用戶在錄入信息是錄入了‘%',而在查詢時(shí)無(wú)法精確匹配‘%'。究其原因,‘%'是MySQL的關(guān)鍵字,如果我們想要精確匹配‘%',那么需要對(duì)其進(jìn)行轉(zhuǎn)義。
1.使用mybatis提供的攔截器攔截所有的查詢請(qǐng)求。
具體實(shí)現(xiàn)在代碼中均有注釋
import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.util.*; /** * mybatis/mybatis-plus模糊查詢語(yǔ)句特殊字符轉(zhuǎn)義攔截器 * * @author lieber */ @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) @Slf4j public class MybatisLikeSqlInterceptor implements Interceptor { /** * SQL語(yǔ)句like */ private final static String SQL_LIKE = " like "; /** * SQL語(yǔ)句占位符 */ private final static String SQL_PLACEHOLDER = "?"; /** * SQL語(yǔ)句占位符分隔 */ private final static String SQL_PLACEHOLDER_REGEX = "\\?"; /** * 所有的轉(zhuǎn)義器 */ private static Map<Class, AbstractLikeSqlConverter> converterMap = new HashMap<>(4); static { converterMap.put(Map.class, new MapLikeSqlConverter()); converterMap.put(Object.class, new ObjectLikeSqlConverter()); } @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); MappedStatement statement = (MappedStatement) args[0]; Object parameterObject = args[1]; BoundSql boundSql = statement.getBoundSql(parameterObject); String sql = boundSql.getSql(); this.transferLikeSql(sql, parameterObject, boundSql); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties arg0) { } /** * 修改包含like的SQL語(yǔ)句 * * @param sql SQL語(yǔ)句 * @param parameterObject 參數(shù)對(duì)象 * @param boundSql 綁定SQL對(duì)象 */ private void transferLikeSql(String sql, Object parameterObject, BoundSql boundSql) { if (!isEscape(sql)) { return; } sql = sql.replaceAll(" {2}", " "); // 獲取關(guān)鍵字的個(gè)數(shù)(去重) Set<String> fields = this.getKeyFields(sql, boundSql); if (fields == null) { return; } // 此處可以增強(qiáng),不止是支持Map對(duì)象,Map對(duì)象僅用于傳入的條件為Map或者使用@Param傳入的對(duì)象被Mybatis轉(zhuǎn)為的Map AbstractLikeSqlConverter converter; // 對(duì)關(guān)鍵字進(jìn)行特殊字符“清洗”,如果有特殊字符的,在特殊字符前添加轉(zhuǎn)義字符(\) if (parameterObject instanceof Map) { converter = converterMap.get(Map.class); } else { converter = converterMap.get(Object.class); } converter.convert(sql, fields, parameterObject); } /** * 是否需要轉(zhuǎn)義 * * @param sql SQL語(yǔ)句 * @return true/false */ private boolean isEscape(String sql) { return this.hasLike(sql) && this.hasPlaceholder(sql); } /** * 判斷SQL語(yǔ)句中是否含有l(wèi)ike關(guān)鍵字 * * @param str SQL語(yǔ)句 * @return true/false */ private boolean hasLike(String str) { if (StringUtils.isBlank(str)) { return false; } return str.toLowerCase().contains(SQL_LIKE); } /** * 判斷SQL語(yǔ)句中是否包含SQL占位符 * * @param str SQL語(yǔ)句 * @return true/false */ private boolean hasPlaceholder(String str) { if (StringUtils.isBlank(str)) { return false; } return str.toLowerCase().contains(SQL_PLACEHOLDER); } /** * 獲取需要替換的所有字段集合 * * @param sql 完整SQL語(yǔ)句 * @param boundSql 綁定的SQL對(duì)象 * @return 字段集合列表 */ private Set<String> getKeyFields(String sql, BoundSql boundSql) { String[] params = sql.split(SQL_PLACEHOLDER_REGEX); Set<String> fields = new HashSet<>(); for (int i = 0; i < params.length; i++) { if (this.hasLike(params[i])) { String field = boundSql.getParameterMappings().get(i).getProperty(); fields.add(field); } } return fields; } }
2.定義SQL語(yǔ)句轉(zhuǎn)義模板,分別對(duì)Map和Object對(duì)象進(jìn)行處理
2.1 定義AbstractLikeSqlConverter
import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Set; /** * 包含like的SQL語(yǔ)句轉(zhuǎn)義模板 * * @author lieber */ @Slf4j public abstract class AbstractLikeSqlConverter<T> { /** * SQL語(yǔ)句like使用關(guān)鍵字% */ private final static String LIKE_SQL_KEY = "%"; /** * SQL語(yǔ)句需要轉(zhuǎn)義的關(guān)鍵字 */ private final static String[] ESCAPE_CHAR = new String[]{LIKE_SQL_KEY, "_", "\\"}; /** * mybatis-plus中l(wèi)ike的SQL語(yǔ)句樣式 */ private final static String MYBATIS_PLUS_LIKE_SQL = " like ?"; /** * mybatis-plus中參數(shù)前綴 */ private final static String MYBATIS_PLUS_WRAPPER_PREFIX = "ew.paramNameValuePairs."; /** * mybatis-plus中參數(shù)鍵 */ final static String MYBATIS_PLUS_WRAPPER_KEY = "ew"; /** * mybatis-plus中參數(shù)分隔符 */ final static String MYBATIS_PLUS_WRAPPER_SEPARATOR = "."; /** * mybatis-plus中參數(shù)分隔符替換器 */ final static String MYBATIS_PLUS_WRAPPER_SEPARATOR_REGEX = "\\."; /** * 已經(jīng)替換過(guò)的標(biāo)記 */ final static String REPLACED_LIKE_KEYWORD_MARK = "replaced.keyword"; /** * 轉(zhuǎn)義特殊字符 * * @param sql SQL語(yǔ)句 * @param fields 字段列表 * @param parameter 參數(shù)對(duì)象 */ public void convert(String sql, Set<String> fields, T parameter) { for (String field : fields) { if (this.hasMybatisPlusLikeSql(sql)) { if (this.hasWrapper(field)) { // 第一種情況:在業(yè)務(wù)層進(jìn)行條件構(gòu)造產(chǎn)生的模糊查詢關(guān)鍵字,使用QueryWrapper,LambdaQueryWrapper this.transferWrapper(field, parameter); } else { // 第二種情況:未使用條件構(gòu)造器,但是在service層進(jìn)行了查詢關(guān)鍵字與模糊查詢符`%`手動(dòng)拼接 this.transferSelf(field, parameter); } } else { // 第三種情況:在Mapper類的注解SQL中進(jìn)行了模糊查詢的拼接 this.transferSplice(field, parameter); } } } /** * 轉(zhuǎn)義條件構(gòu)造的特殊字符 * 在業(yè)務(wù)層進(jìn)行條件構(gòu)造產(chǎn)生的模糊查詢關(guān)鍵字,使用QueryWrapper,LambdaQueryWrapper * * @param field 字段名稱 * @param parameter 參數(shù)對(duì)象 */ public abstract void transferWrapper(String field, T parameter); /** * 轉(zhuǎn)義自定義條件拼接的特殊字符 * 未使用條件構(gòu)造器,但是在service層進(jìn)行了查詢關(guān)鍵字與模糊查詢符`%`手動(dòng)拼接 * * @param field 字段名稱 * @param parameter 參數(shù)對(duì)象 */ public abstract void transferSelf(String field, T parameter); /** * 轉(zhuǎn)義自定義條件拼接的特殊字符 * 在Mapper類的注解SQL中進(jìn)行了模糊查詢的拼接 * * @param field 字段名稱 * @param parameter 參數(shù)對(duì)象 */ public abstract void transferSplice(String field, T parameter); /** * 轉(zhuǎn)義通配符 * * @param before 待轉(zhuǎn)義字符串 * @return 轉(zhuǎn)義后字符串 */ String escapeChar(String before) { if (StringUtils.isNotBlank(before)) { before = before.replaceAll("\\\\", "\\\\\\\\"); before = before.replaceAll("_", "\\\\_"); before = before.replaceAll("%", "\\\\%"); } return before; } /** * 是否包含需要轉(zhuǎn)義的字符 * * @param obj 待判斷的對(duì)象 * @return true/false */ boolean hasEscapeChar(Object obj) { if (!(obj instanceof String)) { return false; } return this.hasEscapeChar((String) obj); } /** * 處理對(duì)象like問(wèn)題 * * @param field 對(duì)象字段 * @param parameter 對(duì)象 */ void resolveObj(String field, Object parameter) { if (parameter == null || StringUtils.isBlank(field)) { return; } try { PropertyDescriptor descriptor = new PropertyDescriptor(field, parameter.getClass()); Method readMethod = descriptor.getReadMethod(); Object param = readMethod.invoke(parameter); if (this.hasEscapeChar(param)) { Method setMethod = descriptor.getWriteMethod(); setMethod.invoke(parameter, this.escapeChar(param.toString())); } else if (this.cascade(field)) { int index = field.indexOf(MYBATIS_PLUS_WRAPPER_SEPARATOR) + 1; this.resolveObj(field.substring(index), param); } } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) { log.error("反射 {} 的 {} get/set方法出現(xiàn)異常", parameter, field, e); } } /** * 判斷是否是級(jí)聯(lián)屬性 * * @param field 字段名 * @return true/false */ boolean cascade(String field) { if (StringUtils.isBlank(field)) { return false; } return field.contains(MYBATIS_PLUS_WRAPPER_SEPARATOR) && !this.hasWrapper(field); } /** * 是否包含mybatis-plus的包含like的SQL語(yǔ)句格式 * * @param sql 完整SQL語(yǔ)句 * @return true/false */ private boolean hasMybatisPlusLikeSql(String sql) { if (StringUtils.isBlank(sql)) { return false; } return sql.toLowerCase().contains(MYBATIS_PLUS_LIKE_SQL); } /** * 判斷是否使用mybatis-plus條件構(gòu)造器 * * @param field 字段 * @return true/false */ private boolean hasWrapper(String field) { if (StringUtils.isBlank(field)) { return false; } return field.contains(MYBATIS_PLUS_WRAPPER_PREFIX); } /** * 判斷字符串是否含有需要轉(zhuǎn)義的字符 * * @param str 待判斷的字符串 * @return true/false */ private boolean hasEscapeChar(String str) { if (StringUtils.isBlank(str)) { return false; } for (String s : ESCAPE_CHAR) { if (str.contains(s)) { return true; } } return false; } }
2.2 定義MapLikeSqlConverter處理Map類型參數(shù)的情況
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper; import java.util.Map; import java.util.Objects; /** * 參數(shù)對(duì)象為Map的轉(zhuǎn)換器 * * @author lieber * @create_date 2020/1/21 12:28 */ public class MapLikeSqlConverter extends AbstractLikeSqlConverter<Map> { @Override public void transferWrapper(String field, Map parameter) { AbstractWrapper wrapper = (AbstractWrapper) parameter.get(MYBATIS_PLUS_WRAPPER_KEY); parameter = wrapper.getParamNameValuePairs(); String[] keys = field.split(MYBATIS_PLUS_WRAPPER_SEPARATOR_REGEX); // ew.paramNameValuePairs.param1,截取字符串之后,獲取第三個(gè),即為參數(shù)名 String paramName = keys[2]; String mapKey = String.format("%s.%s", REPLACED_LIKE_KEYWORD_MARK, paramName); if (parameter.containsKey(mapKey) && Objects.equals(parameter.get(mapKey), true)) { return; } if (this.cascade(field)) { this.resolveCascadeObj(field, parameter); } else { Object param = parameter.get(paramName); if (this.hasEscapeChar(param)) { String paramStr = param.toString(); parameter.put(keys[2], String.format("%%%s%%", this.escapeChar(paramStr.substring(1, paramStr.length() - 1)))); } } parameter.put(mapKey, true); } @Override public void transferSelf(String field, Map parameter) { if (this.cascade(field)) { this.resolveCascadeObj(field, parameter); return; } Object param = parameter.get(field); if (this.hasEscapeChar(param)) { String paramStr = param.toString(); parameter.put(field, String.format("%%%s%%", this.escapeChar(paramStr.substring(1, paramStr.length() - 1)))); } } @Override public void transferSplice(String field, Map parameter) { if (this.cascade(field)) { this.resolveCascadeObj(field, parameter); return; } Object param = parameter.get(field); if (this.hasEscapeChar(param)) { parameter.put(field, this.escapeChar(param.toString())); } } /** * 處理級(jí)聯(lián)屬性 * * @param field 級(jí)聯(lián)字段名 * @param parameter 參數(shù)Map對(duì)象 */ private void resolveCascadeObj(String field, Map parameter) { int index = field.indexOf(MYBATIS_PLUS_WRAPPER_SEPARATOR); Object param = parameter.get(field.substring(0, index)); if (param == null) { return; } this.resolveObj(field.substring(index + 1), param); } }
2.3 定義ObjectLikeSqlConverter處理Object類型參數(shù)的情況
import lombok.extern.slf4j.Slf4j; /** * 通用參數(shù)的轉(zhuǎn)換器 * * @author lieber */ @Slf4j public class ObjectLikeSqlConverter extends AbstractLikeSqlConverter<Object> { @Override public void transferWrapper(String field, Object parameter) { // 尚未發(fā)現(xiàn)這種情況 } @Override public void transferSelf(String field, Object parameter) { // 尚未發(fā)現(xiàn)這種情況 } @Override public void transferSplice(String field, Object parameter) { this.resolveObj(field, parameter); } }
3. 注冊(cè)到Spring中
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * mybatis/mybatis-plus模糊查詢語(yǔ)句特殊字符轉(zhuǎn)義配置 * * @author lieber */ @Configuration public class MybatisLikeSqlConfig { @Bean public MybatisLikeSqlInterceptor mybatisSqlInterceptor() { return new MybatisLikeSqlInterceptor(); } }
到此,即可進(jìn)行關(guān)鍵字的精確匹配。
到此這篇關(guān)于mybatis/mybatis-plus模糊查詢語(yǔ)句特殊字符轉(zhuǎn)義攔截器的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)mybatis-plus模糊查詢特殊字符轉(zhuǎn)義攔截器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中資源加載的方法及Spring的ResourceLoader應(yīng)用小結(jié)
在Java開發(fā)中,資源加載是一個(gè)基礎(chǔ)而重要的操作,這篇文章主要介紹了深入理解Java中資源加載的方法及Spring的ResourceLoader應(yīng)用,本文通過(guò)實(shí)例代碼演示了通過(guò)ClassLoader和Class獲取資源的內(nèi)容,以及使用Spring的ResourceLoader加載多個(gè)資源的過(guò)程,需要的朋友可以參考下2024-01-01Java異步編程之Callbacks與Futures模型詳解
這篇文章主要為大家詳細(xì)介紹了Java異步編程中Callbacks與Futures模型的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03如何實(shí)用Java實(shí)現(xiàn)合并、拆分PDF文檔
這篇文章主要介紹了如何實(shí)用Java實(shí)現(xiàn)合并、拆分PDF文檔,處理PDF文檔時(shí),這樣的好處是對(duì)文檔的存儲(chǔ)、管理很方便。下面將通過(guò)Java程序代碼介紹具體的PDF合并、拆分的方法,需要的朋友可以參考下2019-07-07SpringSecurity?默認(rèn)登錄認(rèn)證的實(shí)現(xiàn)原理解析
這篇文章主要介紹了SpringSecurity?默認(rèn)登錄認(rèn)證的實(shí)現(xiàn)原理解析,本文通過(guò)圖文示例相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-12-12解決feignClient調(diào)用時(shí)獲取返回對(duì)象類型匹配的問(wèn)題
這篇文章主要介紹了解決feignClient調(diào)用時(shí)獲取返回對(duì)象類型匹配的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06使用IDEA將Java/Kotliin工程導(dǎo)出Jar包的正確姿勢(shì)
這篇文章主要介紹了使用IDEA將Java/Kotliin工程導(dǎo)出Jar包的正確姿勢(shì),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03