通過攔截器Interceptor優化Mybatis的in查詢

weixin_34116110發表於2017-11-30

在工作中,我們經常會因為在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("(?<=\\()[.\n]*?(?=\\))");
        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.isBlank(m2.group())) {
                    isHaveEmptyList = Boolean.TRUE;
                    break;
                }
            }
        }
        return isHaveEmptyList;
    }
}

基本上看程式碼都能看懂,就不一一解釋了。

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

需要程式碼的同學可以在保留版權的情況下自行獲取,歡迎大家給我提寶貴的建議哈。

相關文章