经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » MyBatis » 查看文章
mybatis/mybatis-plus模糊查询语句特殊字符转义拦截器的实现
来源:jb51  时间:2021/11/9 15:50:45  对本文有异议

mybatis/mybatis-plus模糊查询语句特殊字符转义拦截器

在开发中,我们通常会遇到这样的情况。用户在录入信息是录入了‘%',而在查询时无法精确匹配‘%'。究其原因,‘%'是MySQL的关键字,如果我们想要精确匹配‘%',那么需要对其进行转义。

1.使用mybatis提供的拦截器拦截所有的查询请求。

具体实现在代码中均有注释

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.apache.commons.lang3.StringUtils;
  3. import org.apache.ibatis.executor.Executor;
  4. import org.apache.ibatis.mapping.BoundSql;
  5. import org.apache.ibatis.mapping.MappedStatement;
  6. import org.apache.ibatis.plugin.*;
  7. import org.apache.ibatis.session.ResultHandler;
  8. import org.apache.ibatis.session.RowBounds;
  9.  
  10. import java.util.*;
  11.  
  12. /**
  13. * mybatis/mybatis-plus模糊查询语句特殊字符转义拦截器
  14. *
  15. * @author lieber
  16. */
  17. @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
  18. @Slf4j
  19. public class MybatisLikeSqlInterceptor implements Interceptor {
  20.  
  21. /**
  22. * SQL语句like
  23. */
  24. private final static String SQL_LIKE = " like ";
  25.  
  26. /**
  27. * SQL语句占位符
  28. */
  29. private final static String SQL_PLACEHOLDER = "?";
  30.  
  31. /**
  32. * SQL语句占位符分隔
  33. */
  34. private final static String SQL_PLACEHOLDER_REGEX = "\\?";
  35.  
  36. /**
  37. * 所有的转义器
  38. */
  39. private static Map<Class, AbstractLikeSqlConverter> converterMap = new HashMap<>(4);
  40.  
  41. static {
  42. converterMap.put(Map.class, new MapLikeSqlConverter());
  43. converterMap.put(Object.class, new ObjectLikeSqlConverter());
  44. }
  45.  
  46. @Override
  47. public Object intercept(Invocation invocation) throws Throwable {
  48. Object[] args = invocation.getArgs();
  49. MappedStatement statement = (MappedStatement) args[0];
  50. Object parameterObject = args[1];
  51. BoundSql boundSql = statement.getBoundSql(parameterObject);
  52. String sql = boundSql.getSql();
  53. this.transferLikeSql(sql, parameterObject, boundSql);
  54. return invocation.proceed();
  55. }
  56.  
  57. @Override
  58. public Object plugin(Object target) {
  59. return Plugin.wrap(target, this);
  60. }
  61.  
  62. @Override
  63. public void setProperties(Properties arg0) {
  64.  
  65. }
  66.  
  67. /**
  68. * 修改包含like的SQL语句
  69. *
  70. * @param sql SQL语句
  71. * @param parameterObject 参数对象
  72. * @param boundSql 绑定SQL对象
  73. */
  74. private void transferLikeSql(String sql, Object parameterObject, BoundSql boundSql) {
  75. if (!isEscape(sql)) {
  76. return;
  77. }
  78. sql = sql.replaceAll(" {2}", " ");
  79. // 获取关键字的个数(去重)
  80. Set<String> fields = this.getKeyFields(sql, boundSql);
  81. if (fields == null) {
  82. return;
  83. }
  84. // 此处可以增强,不止是支持Map对象,Map对象仅用于传入的条件为Map或者使用@Param传入的对象被Mybatis转为的Map
  85. AbstractLikeSqlConverter converter;
  86. // 对关键字进行特殊字符“清洗”,如果有特殊字符的,在特殊字符前添加转义字符(\)
  87. if (parameterObject instanceof Map) {
  88. converter = converterMap.get(Map.class);
  89. } else {
  90. converter = converterMap.get(Object.class);
  91. }
  92. converter.convert(sql, fields, parameterObject);
  93. }
  94.  
  95. /**
  96. * 是否需要转义
  97. *
  98. * @param sql SQL语句
  99. * @return true/false
  100. */
  101. private boolean isEscape(String sql) {
  102. return this.hasLike(sql) && this.hasPlaceholder(sql);
  103. }
  104.  
  105. /**
  106. * 判断SQL语句中是否含有like关键字
  107. *
  108. * @param str SQL语句
  109. * @return true/false
  110. */
  111. private boolean hasLike(String str) {
  112. if (StringUtils.isBlank(str)) {
  113. return false;
  114. }
  115. return str.toLowerCase().contains(SQL_LIKE);
  116. }
  117.  
  118. /**
  119. * 判断SQL语句中是否包含SQL占位符
  120. *
  121. * @param str SQL语句
  122. * @return true/false
  123. */
  124. private boolean hasPlaceholder(String str) {
  125. if (StringUtils.isBlank(str)) {
  126. return false;
  127. }
  128. return str.toLowerCase().contains(SQL_PLACEHOLDER);
  129. }
  130.  
  131. /**
  132. * 获取需要替换的所有字段集合
  133. *
  134. * @param sql 完整SQL语句
  135. * @param boundSql 绑定的SQL对象
  136. * @return 字段集合列表
  137. */
  138. private Set<String> getKeyFields(String sql, BoundSql boundSql) {
  139. String[] params = sql.split(SQL_PLACEHOLDER_REGEX);
  140. Set<String> fields = new HashSet<>();
  141. for (int i = 0; i < params.length; i++) {
  142. if (this.hasLike(params[i])) {
  143. String field = boundSql.getParameterMappings().get(i).getProperty();
  144. fields.add(field);
  145. }
  146. }
  147. return fields;
  148. }
  149. }

2.定义SQL语句转义模板,分别对Map和Object对象进行处理

2.1 定义AbstractLikeSqlConverter

  1. import lombok.extern.slf4j.Slf4j;
  2. import org.apache.commons.lang3.StringUtils;
  3.  
  4. import java.beans.IntrospectionException;
  5. import java.beans.PropertyDescriptor;
  6. import java.lang.reflect.InvocationTargetException;
  7. import java.lang.reflect.Method;
  8. import java.util.Set;
  9.  
  10. /**
  11. * 包含like的SQL语句转义模板
  12. *
  13. * @author lieber
  14. */
  15. @Slf4j
  16. public abstract class AbstractLikeSqlConverter<T> {
  17.  
  18. /**
  19. * SQL语句like使用关键字%
  20. */
  21. private final static String LIKE_SQL_KEY = "%";
  22.  
  23. /**
  24. * SQL语句需要转义的关键字
  25. */
  26. private final static String[] ESCAPE_CHAR = new String[]{LIKE_SQL_KEY, "_", "\\"};
  27.  
  28. /**
  29. * mybatis-plus中like的SQL语句样式
  30. */
  31. private final static String MYBATIS_PLUS_LIKE_SQL = " like ?";
  32.  
  33. /**
  34. * mybatis-plus中参数前缀
  35. */
  36. private final static String MYBATIS_PLUS_WRAPPER_PREFIX = "ew.paramNameValuePairs.";
  37.  
  38. /**
  39. * mybatis-plus中参数键
  40. */
  41. final static String MYBATIS_PLUS_WRAPPER_KEY = "ew";
  42.  
  43. /**
  44. * mybatis-plus中参数分隔符
  45. */
  46. final static String MYBATIS_PLUS_WRAPPER_SEPARATOR = ".";
  47.  
  48. /**
  49. * mybatis-plus中参数分隔符替换器
  50. */
  51. final static String MYBATIS_PLUS_WRAPPER_SEPARATOR_REGEX = "\\.";
  52.  
  53. /**
  54. * 已经替换过的标记
  55. */
  56. final static String REPLACED_LIKE_KEYWORD_MARK = "replaced.keyword";
  57.  
  58. /**
  59. * 转义特殊字符
  60. *
  61. * @param sql SQL语句
  62. * @param fields 字段列表
  63. * @param parameter 参数对象
  64. */
  65. public void convert(String sql, Set<String> fields, T parameter) {
  66. for (String field : fields) {
  67. if (this.hasMybatisPlusLikeSql(sql)) {
  68. if (this.hasWrapper(field)) {
  69. // 第一种情况:在业务层进行条件构造产生的模糊查询关键字,使用QueryWrapper,LambdaQueryWrapper
  70. this.transferWrapper(field, parameter);
  71. } else {
  72. // 第二种情况:未使用条件构造器,但是在service层进行了查询关键字与模糊查询符`%`手动拼接
  73. this.transferSelf(field, parameter);
  74. }
  75. } else {
  76. // 第三种情况:在Mapper类的注解SQL中进行了模糊查询的拼接
  77. this.transferSplice(field, parameter);
  78. }
  79. }
  80. }
  81.  
  82. /**
  83. * 转义条件构造的特殊字符
  84. * 在业务层进行条件构造产生的模糊查询关键字,使用QueryWrapper,LambdaQueryWrapper
  85. *
  86. * @param field 字段名称
  87. * @param parameter 参数对象
  88. */
  89. public abstract void transferWrapper(String field, T parameter);
  90.  
  91. /**
  92. * 转义自定义条件拼接的特殊字符
  93. * 未使用条件构造器,但是在service层进行了查询关键字与模糊查询符`%`手动拼接
  94. *
  95. * @param field 字段名称
  96. * @param parameter 参数对象
  97. */
  98. public abstract void transferSelf(String field, T parameter);
  99.  
  100. /**
  101. * 转义自定义条件拼接的特殊字符
  102. * 在Mapper类的注解SQL中进行了模糊查询的拼接
  103. *
  104. * @param field 字段名称
  105. * @param parameter 参数对象
  106. */
  107. public abstract void transferSplice(String field, T parameter);
  108.  
  109. /**
  110. * 转义通配符
  111. *
  112. * @param before 待转义字符串
  113. * @return 转义后字符串
  114. */
  115. String escapeChar(String before) {
  116. if (StringUtils.isNotBlank(before)) {
  117. before = before.replaceAll("\\\\", "\\\\\\\\");
  118. before = before.replaceAll("_", "\\\\_");
  119. before = before.replaceAll("%", "\\\\%");
  120. }
  121. return before;
  122. }
  123.  
  124. /**
  125. * 是否包含需要转义的字符
  126. *
  127. * @param obj 待判断的对象
  128. * @return true/false
  129. */
  130. boolean hasEscapeChar(Object obj) {
  131. if (!(obj instanceof String)) {
  132. return false;
  133. }
  134. return this.hasEscapeChar((String) obj);
  135. }
  136.  
  137. /**
  138. * 处理对象like问题
  139. *
  140. * @param field 对象字段
  141. * @param parameter 对象
  142. */
  143. void resolveObj(String field, Object parameter) {
  144. if (parameter == null || StringUtils.isBlank(field)) {
  145. return;
  146. }
  147. try {
  148. PropertyDescriptor descriptor = new PropertyDescriptor(field, parameter.getClass());
  149. Method readMethod = descriptor.getReadMethod();
  150. Object param = readMethod.invoke(parameter);
  151. if (this.hasEscapeChar(param)) {
  152. Method setMethod = descriptor.getWriteMethod();
  153. setMethod.invoke(parameter, this.escapeChar(param.toString()));
  154. } else if (this.cascade(field)) {
  155. int index = field.indexOf(MYBATIS_PLUS_WRAPPER_SEPARATOR) + 1;
  156. this.resolveObj(field.substring(index), param);
  157. }
  158. } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
  159. log.error("反射 {} 的 {} get/set方法出现异常", parameter, field, e);
  160. }
  161. }
  162.  
  163. /**
  164. * 判断是否是级联属性
  165. *
  166. * @param field 字段名
  167. * @return true/false
  168. */
  169. boolean cascade(String field) {
  170. if (StringUtils.isBlank(field)) {
  171. return false;
  172. }
  173. return field.contains(MYBATIS_PLUS_WRAPPER_SEPARATOR) && !this.hasWrapper(field);
  174. }
  175.  
  176. /**
  177. * 是否包含mybatis-plus的包含like的SQL语句格式
  178. *
  179. * @param sql 完整SQL语句
  180. * @return true/false
  181. */
  182. private boolean hasMybatisPlusLikeSql(String sql) {
  183. if (StringUtils.isBlank(sql)) {
  184. return false;
  185. }
  186. return sql.toLowerCase().contains(MYBATIS_PLUS_LIKE_SQL);
  187. }
  188.  
  189. /**
  190. * 判断是否使用mybatis-plus条件构造器
  191. *
  192. * @param field 字段
  193. * @return true/false
  194. */
  195. private boolean hasWrapper(String field) {
  196. if (StringUtils.isBlank(field)) {
  197. return false;
  198. }
  199. return field.contains(MYBATIS_PLUS_WRAPPER_PREFIX);
  200. }
  201.  
  202. /**
  203. * 判断字符串是否含有需要转义的字符
  204. *
  205. * @param str 待判断的字符串
  206. * @return true/false
  207. */
  208. private boolean hasEscapeChar(String str) {
  209. if (StringUtils.isBlank(str)) {
  210. return false;
  211. }
  212. for (String s : ESCAPE_CHAR) {
  213. if (str.contains(s)) {
  214. return true;
  215. }
  216. }
  217. return false;
  218. }
  219. }

2.2 定义MapLikeSqlConverter处理Map类型参数的情况

  1. import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
  2.  
  3. import java.util.Map;
  4. import java.util.Objects;
  5.  
  6. /**
  7. * 参数对象为Map的转换器
  8. *
  9. * @author lieber
  10. * @create_date 2020/1/21 12:28
  11. */
  12. public class MapLikeSqlConverter extends AbstractLikeSqlConverter<Map> {
  13.  
  14. @Override
  15. public void transferWrapper(String field, Map parameter) {
  16. AbstractWrapper wrapper = (AbstractWrapper) parameter.get(MYBATIS_PLUS_WRAPPER_KEY);
  17. parameter = wrapper.getParamNameValuePairs();
  18. String[] keys = field.split(MYBATIS_PLUS_WRAPPER_SEPARATOR_REGEX);
  19. // ew.paramNameValuePairs.param1,截取字符串之后,获取第三个,即为参数名
  20. String paramName = keys[2];
  21. String mapKey = String.format("%s.%s", REPLACED_LIKE_KEYWORD_MARK, paramName);
  22. if (parameter.containsKey(mapKey) && Objects.equals(parameter.get(mapKey), true)) {
  23. return;
  24. }
  25. if (this.cascade(field)) {
  26. this.resolveCascadeObj(field, parameter);
  27. } else {
  28. Object param = parameter.get(paramName);
  29. if (this.hasEscapeChar(param)) {
  30. String paramStr = param.toString();
  31. parameter.put(keys[2], String.format("%%%s%%", this.escapeChar(paramStr.substring(1, paramStr.length() - 1))));
  32. }
  33. }
  34. parameter.put(mapKey, true);
  35. }
  36.  
  37. @Override
  38. public void transferSelf(String field, Map parameter) {
  39. if (this.cascade(field)) {
  40. this.resolveCascadeObj(field, parameter);
  41. return;
  42. }
  43. Object param = parameter.get(field);
  44. if (this.hasEscapeChar(param)) {
  45. String paramStr = param.toString();
  46. parameter.put(field, String.format("%%%s%%", this.escapeChar(paramStr.substring(1, paramStr.length() - 1))));
  47. }
  48. }
  49.  
  50. @Override
  51. public void transferSplice(String field, Map parameter) {
  52. if (this.cascade(field)) {
  53. this.resolveCascadeObj(field, parameter);
  54. return;
  55. }
  56. Object param = parameter.get(field);
  57. if (this.hasEscapeChar(param)) {
  58. parameter.put(field, this.escapeChar(param.toString()));
  59. }
  60. }
  61.  
  62. /**
  63. * 处理级联属性
  64. *
  65. * @param field 级联字段名
  66. * @param parameter 参数Map对象
  67. */
  68. private void resolveCascadeObj(String field, Map parameter) {
  69. int index = field.indexOf(MYBATIS_PLUS_WRAPPER_SEPARATOR);
  70. Object param = parameter.get(field.substring(0, index));
  71. if (param == null) {
  72. return;
  73. }
  74. this.resolveObj(field.substring(index + 1), param);
  75. }
  76. }

2.3 定义ObjectLikeSqlConverter处理Object类型参数的情况

  1. import lombok.extern.slf4j.Slf4j;
  2.  
  3. /**
  4. * 通用参数的转换器
  5. *
  6. * @author lieber
  7. */
  8. @Slf4j
  9. public class ObjectLikeSqlConverter extends AbstractLikeSqlConverter<Object> {
  10.  
  11. @Override
  12. public void transferWrapper(String field, Object parameter) {
  13. // 尚未发现这种情况
  14. }
  15.  
  16. @Override
  17. public void transferSelf(String field, Object parameter) {
  18. // 尚未发现这种情况
  19. }
  20.  
  21. @Override
  22. public void transferSplice(String field, Object parameter) {
  23. this.resolveObj(field, parameter);
  24. }
  25. }

3. 注册到Spring中

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3.  
  4. /**
  5. * mybatis/mybatis-plus模糊查询语句特殊字符转义配置
  6. *
  7. * @author lieber
  8. */
  9. @Configuration
  10. public class MybatisLikeSqlConfig {
  11.  
  12. @Bean
  13. public MybatisLikeSqlInterceptor mybatisSqlInterceptor() {
  14. return new MybatisLikeSqlInterceptor();
  15. }
  16. }

到此,即可进行关键字的精确匹配。

到此这篇关于mybatis/mybatis-plus模糊查询语句特殊字符转义拦截器的实现的文章就介绍到这了,更多相关mybatis-plus模糊查询特殊字符转义拦截器内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号