查看了很多文章,大多数都是Mybatis-plus的解决方案,而且基本上都是用转义和在MapperXml中加Escape关键字的方式,然而现有项目中有大量的MapperXml文件,很多like关键字后面都得加Escape关键字,过于痛苦。如果我在sql中每碰到一个like就在后面拼接好ESCAPE关键字,是不是就不用在MapperXml文件中加大量的ESCAPE了。基于上面的考量,做出如下实现。
实现方式:拦截器
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class MybatisEscapeInterceptor implements Interceptor {
static final String REG_EXP_LIKE_CONTAIN = "^.*(like\\s*\\?|like\\s*concat\\s*[(]\\s*'%'\\s*,\\s*concat\\s*[(]\\s*\\?\\s*,\\s*'%'\\s*[)]\\s*[)]).*$";
static final String REG_EXP_PROCESSED_PARAM = "^.*(!%|!_).*$";
static final String REG_EXP_UNPROCESSED_PARAM = "^.*(%|_).*$";
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
if (args.length == 4) {
boundSql = mappedStatement.getBoundSql(parameter);
cacheKey = executor.createCacheKey(mappedStatement, parameter, rowBounds, boundSql);
} else {
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
String sql = boundSql.getSql();
String newSql = modifyLikeSql(sql, parameter, boundSql, mappedStatement);
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, newSql);
return executor.query(mappedStatement, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
public static String modifyLikeSql(String sql, Object parameterObject, BoundSql boundSql, MappedStatement mappedStatement) {
if (!matches(REG_EXP_LIKE_CONTAIN, sql) || sql.contains(" ESCAPE '!' ")) {
return sql;
}
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
int j = 0;
Matcher matcher = getMatcher("\\?", sql);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, "#{" + j + "}");
j++;
}
matcher.appendTail(sb);
sql = sb.toString();
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
if (value instanceof String) {
String val = (String) value;
if(val.equals("%%")){
continue;
}
if (!matches(REG_EXP_PROCESSED_PARAM, val) && matches(REG_EXP_UNPROCESSED_PARAM, val) && matches(getRegExpLikeContain(i), sql)) {
val = resetParam(val);
boundSql.setAdditionalParameter(propertyName, val);
value = val;
}
}
} else if (parameterObject == null) {
value = null;
} else if (mappedStatement.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = mappedStatement.getConfiguration().newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
if (value instanceof String) {
String val = (String) value;
if(val.equals("%%")){
continue;
}
if (!matches(REG_EXP_PROCESSED_PARAM, val) && matches(REG_EXP_UNPROCESSED_PARAM, val) && matches(getRegExpLikeContain(i), sql)) {
val = resetParam(val);
metaObject.setValue(propertyName, val);
value = val;
}
}
}
if (value instanceof String) {
if (matches(REG_EXP_PROCESSED_PARAM, ((String) value))) {
sql = matchesReplace(getRegExpLike(i), sql, " LIKE ? ESCAPE '!' ");
}
}
}
}
}
sql = matchesReplace("#\\{\\d+\\}", sql, " ? ");
return sql;
}
private static String resetParam(String val) {
if (!"%".equals(val) && !"%%".equals(val) && !"_".equals(val) && !"__".equals(val)) {
if (val.startsWith("%") || val.startsWith("_")) {
val = val.substring(1);
}
if (val.endsWith("%") || val.endsWith("_")) {
val = val.substring(0, val.length() - 1);
}
}
val = val.replaceAll("%", "!%");
val = val.replaceAll("_", "!_");
val = "%" + val + "%";
return val;
}
private static String getRegExpLike(int i) {
return "like\\s*#\\{" + i + "\\}|like\\s*concat\\s*[(]\\s*'%'\\s*,\\s*concat\\s*[(]\\s*#\\{" + i + "\\}\\s*,\\s*'%'\\s*[)]\\s*[)]";
}
private static String getRegExpLikeContain(int i) {
return "^.*(like\\s*#\\{" + i + "\\}|like\\s*concat\\s*[(]\\s*'%'\\s*,\\s*concat\\s*[(]\\s*#\\{" + i + "\\}\\s*,\\s*'%'\\s*[)]\\s*[)]).*$";
}
private static String matchesReplace(String regExp, String input, String replaceStr) {
Matcher matcher = getMatcher(regExp, input);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, replaceStr);
}
matcher.appendTail(sb);
return sb.toString();
}
private static boolean matches(String regExp, String input) {
return getMatcher(regExp, input).matches();
}
private static Matcher getMatcher(String regExp, String input) {
Pattern pattern = Pattern.compile(regExp, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
return pattern.matcher(input);
}
}