通过拦截器Interceptor优化Mybatis的in查询

来源:互联网 发布:apache poi word 编辑:程序博客网 时间:2024/06/16 01:54

本文首发于简书:http://www.jianshu.com/p/e20323d960f4

在工作中,我们经常会因为在mybatis中的不严谨写法,导致查询语句会产生in()的情况。这种情况不符合SQL的语法,导致程序报错。

网上有一些解决方案,大部分的解决方案都是对list判null和判空来处理。

<if test="list != null and list.size>0">    do something</if>

但是这种解决方法会产生一个逻辑问题,本来in一个空列表,查询结果应该是没有数据才对,现在却变成了这个in条件失效,这就导致了结果有可能并不是我们想要的。

还有一种解决方案是对list做双重判断。第一重判断和上面的解决方案一致,增加的第二重判断是为了保证如果list为空列表则只能查到空列表 

<if test="list != null and list.size>0">    do something</if>
<if test="list!=null and list.size==0">    and 1=0</if>

这种方案能解决in()的问题报错的问题,也不会产生逻辑错误的情况。但是这个写法有点繁琐,每次遇到这种情况都需要特殊判断。

于是我就准备通过拦截器Interceptor来解决这个问题。

在理想状态下,我们在xml中应该只应该判断list非null就可以了。

以下是我写的EmptyCollectionIntercept

@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}),        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class,Object.class})})public class EmptyCollectionIntercept implements Interceptor {    @Override    public Object intercept(Invocation invocation) throws Throwable {        //通过invocation.getArgs()可以得到当前执行方法的参数        //第一个args[0]是MappedStatement对象,第二个args[1]是参数对象parameterObject。        final Object[] args = invocation.getArgs();        MappedStatement mappedStatement = (MappedStatement) args[0];        Object parameter = args[1];        if (parameter == null) {            Class parameterType = mappedStatement.getParameterMap().getType();            // 实际执行时的参数值为空,但mapper语句上存在输入参数的异常状况,返回默认值            if (parameterType != null) {                return getDefaultReturnValue(invocation);            }            return invocation.proceed();        }        BoundSql boundSql = mappedStatement.getBoundSql(parameter);        if (isHaveEmptyList(boundSql.getSql())) {            return getDefaultReturnValue(invocation);        }        return invocation.proceed();    }    @Override    public Object plugin(Object target) {        //只拦截Executor对象,减少目标被代理的次数        if (target instanceof Executor) {            return Plugin.wrap(target, this);        } else {            return target;        }    }    @Override    public void setProperties(Properties properties) {    }    /**     * 返回默认的值,list类型的返回空list,数值类型的返回0     *     * @param invocation     * @return     */    private Object getDefaultReturnValue(Invocation invocation) {        Class returnType = invocation.getMethod().getReturnType();        if (returnType.equals(List.class)) {            return Lists.newArrayList();        } else if (returnType.equals(Integer.TYPE) || returnType.equals(Long.TYPE)                || returnType.equals(Integer.class) || returnType.equals(Long.class)) {            return 0;        }        return null;    }    /**     * 去除字符中的干扰项,如果存在in(" in")这种情况会将" in"替换成""     *     * @param sql     * @return     */    private static String removeInterference(String sql) {        Pattern pattern = Pattern.compile("\".*?\"");        Matcher matcher = pattern.matcher(sql);        while (matcher.find()) {            String replaceWorld = matcher.group();            if (StringUtils.containsIgnoreCase(replaceWorld, " in")) {                sql = sql.replace(replaceWorld, "\"\"");            }        }        return sql;    }    /**     * 判断sql是否存在in空列表,找到in关键字,判断in后面是否没有括号或者括号中没有元素     *     * @param sql     * @return     */    private static Boolean isHaveEmptyList(String sql) {        sql = removeInterference(sql);        Boolean isHaveEmptyList = Boolean.FALSE;        List<Integer> indexList = Lists.newArrayList();        Pattern pattern = Pattern.compile("\\s(?i)In");        Matcher matcher = pattern.matcher(sql);        while (matcher.find()) {            indexList.add(matcher.start());        }        if (CollectionUtils.isEmpty(indexList)) {            return isHaveEmptyList;        }        Pattern p2 = Pattern.compile("(?<=\\()(.+?)(?=\\))");        for (Integer index : indexList) {            if(StringUtils.isEmpty(subSql)){                isHaveEmptyList = Boolean.TRUE;                break;            }            String subSql = sql.substring(index + 3);            Boolean flag = subSql.startsWith("(")                    || subSql.startsWith(" ")                    || subSql.startsWith("\n")                    || subSql.startsWith("\r");            if (!flag) {                continue;            }            subSql = subSql.trim();            if (!subSql.startsWith("(")) {                isHaveEmptyList = Boolean.TRUE;                break;            }            Matcher m2 = p2.matcher(subSql);            if (m2.find()) {                if (StringUtils.isEmpty(m2.group())) {                    isHaveEmptyList = Boolean.TRUE;                    break;                }            }        }        return isHaveEmptyList;    }}

基本上看代码都能看懂,就不一一解释了。

如何配置mybatis的拦截器大家可以自行研究哦。

需要代码的同学可以在保留版权的情况下自行获取,欢迎大家给我提宝贵的建议哈。

原创粉丝点击