mybatis/mybatis

网友投稿 286 2022-09-16


mybatis/mybatis

目录1.使用mybatis提供的拦截器拦截所有的查询请求。2.定义SQL语句转义模板,分别对Map和Object对象进行处理

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 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[1http://];

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 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 getKeyFields(String sql, BoundSql boundSql) {

String[] params = sql.split(SQL_PLACEHOLDER_REGEX);

Set 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 {

/**

* 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 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 {

@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 {

@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 {

http:// @Bean

public MybatisLikeSqlInterceptor mybatisSqlInterceptor() {

return new MybatisLikeSqlInterceptor();

}

}

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


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:HuaWei ❀ IPv6重复地址检测(华为mate50)
下一篇:HuaWei ❀ 路由器发现功能(华为p50)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~