mybatis/mybatis-plus模糊查询语句特殊字符转义拦截器
在开发中,我们通常会遇到这样的情况。用户在录入信息是录入了‘%',而在查询时无法精确匹配‘%'。究其原因,‘%'是MySQL的关键字,如果我们想要精确匹配‘%',那么需要对其进行转义。
1.使用mybatis提供的拦截器拦截所有的查询请求。
具体实现在代码中均有注释
- 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模糊查询语句特殊字符转义拦截器
- *
- * @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语句like
- */
- private final static String SQL_LIKE = " like ";
-
- /**
- * SQL语句占位符
- */
- private final static String SQL_PLACEHOLDER = "?";
-
- /**
- * SQL语句占位符分隔
- */
- private final static String SQL_PLACEHOLDER_REGEX = "\\?";
-
- /**
- * 所有的转义器
- */
- 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语句
- *
- * @param sql SQL语句
- * @param parameterObject 参数对象
- * @param boundSql 绑定SQL对象
- */
- private void transferLikeSql(String sql, Object parameterObject, BoundSql boundSql) {
- if (!isEscape(sql)) {
- return;
- }
- sql = sql.replaceAll(" {2}", " ");
- // 获取关键字的个数(去重)
- Set<String> fields = this.getKeyFields(sql, boundSql);
- if (fields == null) {
- return;
- }
- // 此处可以增强,不止是支持Map对象,Map对象仅用于传入的条件为Map或者使用@Param传入的对象被Mybatis转为的Map
- AbstractLikeSqlConverter converter;
- // 对关键字进行特殊字符“清洗”,如果有特殊字符的,在特殊字符前添加转义字符(\)
- if (parameterObject instanceof Map) {
- converter = converterMap.get(Map.class);
- } else {
- converter = converterMap.get(Object.class);
- }
- converter.convert(sql, fields, parameterObject);
- }
-
- /**
- * 是否需要转义
- *
- * @param sql SQL语句
- * @return true/false
- */
- private boolean isEscape(String sql) {
- return this.hasLike(sql) && this.hasPlaceholder(sql);
- }
-
- /**
- * 判断SQL语句中是否含有like关键字
- *
- * @param str SQL语句
- * @return true/false
- */
- private boolean hasLike(String str) {
- if (StringUtils.isBlank(str)) {
- return false;
- }
- return str.toLowerCase().contains(SQL_LIKE);
- }
-
- /**
- * 判断SQL语句中是否包含SQL占位符
- *
- * @param str SQL语句
- * @return true/false
- */
- private boolean hasPlaceholder(String str) {
- if (StringUtils.isBlank(str)) {
- return false;
- }
- return str.toLowerCase().contains(SQL_PLACEHOLDER);
- }
-
- /**
- * 获取需要替换的所有字段集合
- *
- * @param sql 完整SQL语句
- * @param boundSql 绑定的SQL对象
- * @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语句转义模板,分别对Map和Object对象进行处理
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语句转义模板
- *
- * @author lieber
- */
- @Slf4j
- public abstract class AbstractLikeSqlConverter<T> {
-
- /**
- * SQL语句like使用关键字%
- */
- private final static String LIKE_SQL_KEY = "%";
-
- /**
- * SQL语句需要转义的关键字
- */
- private final static String[] ESCAPE_CHAR = new String[]{LIKE_SQL_KEY, "_", "\\"};
-
- /**
- * mybatis-plus中like的SQL语句样式
- */
- private final static String MYBATIS_PLUS_LIKE_SQL = " like ?";
-
- /**
- * mybatis-plus中参数前缀
- */
- private final static String MYBATIS_PLUS_WRAPPER_PREFIX = "ew.paramNameValuePairs.";
-
- /**
- * mybatis-plus中参数键
- */
- final static String MYBATIS_PLUS_WRAPPER_KEY = "ew";
-
- /**
- * mybatis-plus中参数分隔符
- */
- final static String MYBATIS_PLUS_WRAPPER_SEPARATOR = ".";
-
- /**
- * mybatis-plus中参数分隔符替换器
- */
- final static String MYBATIS_PLUS_WRAPPER_SEPARATOR_REGEX = "\\.";
-
- /**
- * 已经替换过的标记
- */
- final static String REPLACED_LIKE_KEYWORD_MARK = "replaced.keyword";
-
- /**
- * 转义特殊字符
- *
- * @param sql SQL语句
- * @param fields 字段列表
- * @param parameter 参数对象
- */
- public void convert(String sql, Set<String> fields, T parameter) {
- for (String field : fields) {
- if (this.hasMybatisPlusLikeSql(sql)) {
- if (this.hasWrapper(field)) {
- // 第一种情况:在业务层进行条件构造产生的模糊查询关键字,使用QueryWrapper,LambdaQueryWrapper
- this.transferWrapper(field, parameter);
- } else {
- // 第二种情况:未使用条件构造器,但是在service层进行了查询关键字与模糊查询符`%`手动拼接
- this.transferSelf(field, parameter);
- }
- } else {
- // 第三种情况:在Mapper类的注解SQL中进行了模糊查询的拼接
- this.transferSplice(field, parameter);
- }
- }
- }
-
- /**
- * 转义条件构造的特殊字符
- * 在业务层进行条件构造产生的模糊查询关键字,使用QueryWrapper,LambdaQueryWrapper
- *
- * @param field 字段名称
- * @param parameter 参数对象
- */
- public abstract void transferWrapper(String field, T parameter);
-
- /**
- * 转义自定义条件拼接的特殊字符
- * 未使用条件构造器,但是在service层进行了查询关键字与模糊查询符`%`手动拼接
- *
- * @param field 字段名称
- * @param parameter 参数对象
- */
- public abstract void transferSelf(String field, T parameter);
-
- /**
- * 转义自定义条件拼接的特殊字符
- * 在Mapper类的注解SQL中进行了模糊查询的拼接
- *
- * @param field 字段名称
- * @param parameter 参数对象
- */
- public abstract void transferSplice(String field, T parameter);
-
- /**
- * 转义通配符
- *
- * @param before 待转义字符串
- * @return 转义后字符串
- */
- String escapeChar(String before) {
- if (StringUtils.isNotBlank(before)) {
- before = before.replaceAll("\\\\", "\\\\\\\\");
- before = before.replaceAll("_", "\\\\_");
- before = before.replaceAll("%", "\\\\%");
- }
- return before;
- }
-
- /**
- * 是否包含需要转义的字符
- *
- * @param obj 待判断的对象
- * @return true/false
- */
- boolean hasEscapeChar(Object obj) {
- if (!(obj instanceof String)) {
- return false;
- }
- return this.hasEscapeChar((String) obj);
- }
-
- /**
- * 处理对象like问题
- *
- * @param field 对象字段
- * @param parameter 对象
- */
- 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方法出现异常", parameter, field, e);
- }
- }
-
- /**
- * 判断是否是级联属性
- *
- * @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语句格式
- *
- * @param sql 完整SQL语句
- * @return true/false
- */
- private boolean hasMybatisPlusLikeSql(String sql) {
- if (StringUtils.isBlank(sql)) {
- return false;
- }
- return sql.toLowerCase().contains(MYBATIS_PLUS_LIKE_SQL);
- }
-
- /**
- * 判断是否使用mybatis-plus条件构造器
- *
- * @param field 字段
- * @return true/false
- */
- private boolean hasWrapper(String field) {
- if (StringUtils.isBlank(field)) {
- return false;
- }
- return field.contains(MYBATIS_PLUS_WRAPPER_PREFIX);
- }
-
- /**
- * 判断字符串是否含有需要转义的字符
- *
- * @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类型参数的情况
- import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
-
- import java.util.Map;
- import java.util.Objects;
-
- /**
- * 参数对象为Map的转换器
- *
- * @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,截取字符串之后,获取第三个,即为参数名
- 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()));
- }
- }
-
- /**
- * 处理级联属性
- *
- * @param field 级联字段名
- * @param parameter 参数Map对象
- */
- 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类型参数的情况
- import lombok.extern.slf4j.Slf4j;
-
- /**
- * 通用参数的转换器
- *
- * @author lieber
- */
- @Slf4j
- public class ObjectLikeSqlConverter extends AbstractLikeSqlConverter<Object> {
-
- @Override
- public void transferWrapper(String field, Object parameter) {
- // 尚未发现这种情况
- }
-
- @Override
- public void transferSelf(String field, Object parameter) {
- // 尚未发现这种情况
- }
-
- @Override
- public void transferSplice(String field, Object parameter) {
- this.resolveObj(field, parameter);
- }
- }
3. 注册到Spring中
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
-
- /**
- * mybatis/mybatis-plus模糊查询语句特殊字符转义配置
- *
- * @author lieber
- */
- @Configuration
- public class MybatisLikeSqlConfig {
-
- @Bean
- public MybatisLikeSqlInterceptor mybatisSqlInterceptor() {
- return new MybatisLikeSqlInterceptor();
- }
- }
到此,即可进行关键字的精确匹配。
到此这篇关于mybatis/mybatis-plus模糊查询语句特殊字符转义拦截器的实现的文章就介绍到这了,更多相关mybatis-plus模糊查询特殊字符转义拦截器内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!