rose jade處理DELETE語句時,偶爾報錯

背景

項目中使用了paoding-rose作為開發(fā)框架,該框架作為國產(chǎn)的一個十分優(yōu)秀的框架,在Jade方面處理的也非常好,但是在實際的使用過程中,發(fā)現(xiàn)了一個很有意思的問題,在使用Delete SQL語句批量刪除數(shù)據(jù)時,當傳參進去只有一個參數(shù)時,會因為返回類型的不知道導致拋異常。

這里還是先把解決方案說了好了,升級到最新2.0以上版本即可我們使用的是比較老的1.2.2版本

然后下面為了備忘,特別記錄一下這個蠻有意思的問題。
Git上可以參考這個Issue


問題描述

問題主要體現(xiàn)為,偶然性會報ClassCastException錯誤,具體表現(xiàn)形式如下:

java.lang.ClassCastException: [I cannot be cast to java.lang.Integer
    at com.sun.proxy.$Proxy51.deleteRecommendListByIDs(Unknown Source)
    at com.xx.service.xxxService.generateDefaultRecommendList(xxxService.java:214)
    at com.xx.service.xxxService.main(xxxService.java:951)

其錯誤來源于通過@SQL的注解實現(xiàn)了SQL語句的執(zhí)行地方,具體的SQL如下:

@SQL(" DELETE FROM videolist_home_recommend "
    +" WHERE ID IN ( :delRecords ) ")
public Integer deleteRecommendListByIDs(@SQLParam("delRecords") List<Integer> delRecords);

以上就是這個錯誤產(chǎn)生的基本現(xiàn)象,但是有意思的該問題并非一定會產(chǎn)生,根據(jù)詳細的測試和分析發(fā)現(xiàn),只有在傳參List中元素個數(shù)大于1個以上時,會導致該錯誤的發(fā)生。


原因分析

  • 首先分析了這個現(xiàn)象以后覺得可能是傳參進入的問題,導致了List轉(zhuǎn)化存在的問題,但是后來請教了同事,其實rose這里和數(shù)據(jù)庫中一般SQL語句的返回是不一致的,數(shù)據(jù)庫中返回的是影響的行數(shù),但是Rose中是一個是否更新成功的int[],其中1代表處理成功,0代表處理失?。ň唧w為什么這么處理后面會分析到)。還有這里有個需要反省的就是,對getName()這個方法不熟悉,其實JVM已經(jīng)告訴了我們問題點,出問題的類型是"[I",只不過沒反應過來,括號哭~

String java.lang.Class.getName()
Returns the name of the entity (class, interface, array class, primitive type, or void) represented by this Class object, as a String.
……
If this class object represents a class of arrays, then the internal form of the name consists of the name of the element type preceded by one or more '[' characters representing the depth of the array nesting. The encoding of element type names is as follows:
……
Element Type
boolean ??Z
byte ???B
……
int????I
long???J
short ???S

  • 所以之后根據(jù)這個返回值將SQL方法的返回值替換成為int[]類型,然而有趣的是這次反而在參數(shù)list只有一個值時報錯了,于是進一步debug到源碼中去看了一下(由于github上找不到1.2.2版本的內(nèi)容,又找了好久source文件)

  • 這里就發(fā)現(xiàn)rose jade中關(guān)于這個處理很有意思:

  1. 在rose中,SQL語句只有兩種類型:READ和WRITE,其中show、select等查詢類的屬于READ類型,而update、delete等則屬于WRITE類型。
  2. 在WRITE類型中,會將ID IN (:list) 這種語句自動翻譯成Batch語句來處理。但是在轉(zhuǎn)換成Batch來處理時,在傳入的list中只有一個值時,又會根據(jù)參數(shù)個數(shù),將Batch轉(zhuǎn)化成了SingleBatch,因此造成了這種不統(tǒng)一的情況(兩者的返回值不一樣,一個是Integer,一個是int[])
   @Override
    public Object execute(SQLType sqlType, StatementRuntime... runtimes) {
        switch (runtimes.length) {
            case 0:
                return 0;
            case 1:
                return executeSingle(runtimes[0], returnType);
            default:
                return executeBatch(runtimes);
        }
    }
  1. 因此對照著源碼又看了一下新版本,發(fā)現(xiàn)已經(jīng)對executeBatch做了修正,增加了對一般數(shù)據(jù)庫SQL返回影響行數(shù)的支持(有趣的是,也并沒有放棄之前對int[]的支持,雖然可以理解為向下兼容,但是個人覺得其實也是想保持這種有趣的對數(shù)據(jù)處理的理解吧,挺有意思的)
  • 1.2.2的版本
    private Object executeBatch(StatementRuntime... runtimes) {
        int[] updatedArray = new int[runtimes.length];
        Map<String, List<StatementRuntime>> batchs = new HashMap<String, List<StatementRuntime>>();
        for (int i = 0; i < runtimes.length; i++) {
            StatementRuntime runtime = runtimes[i];
            List<StatementRuntime> batch = batchs.get(runtime.getSQL());
            if (batch == null) {
                batch = new LinkedList<StatementRuntime>();
                batchs.put(runtime.getSQL(), batch);
            }
            runtime.setProperty("_index_at_batch_", i); // 該runtime在batch中的位置
            batch.add(runtime);
        }
        // TODO: 多個真正的batch可以考慮并行執(zhí)行~待定
        for (Map.Entry<String, List<StatementRuntime>> batch : batchs.entrySet()) {
            String sql = batch.getKey();
            List<StatementRuntime> batchRuntimes = batch.getValue();
            StatementRuntime runtime = batchRuntimes.get(0);
            DataAccess dataAccess = dataAccessProvider.getDataAccess(//
                    runtime.getMetaData(), runtime.getProperties());
            List<Object[]> argsList = new ArrayList<Object[]>(batchRuntimes.size());
            for (StatementRuntime batchRuntime : batchRuntimes) {
                argsList.add(batchRuntime.getArgs());
            }
            int[] batchResult = dataAccess.batchUpdate(sql, argsList);
            if (batchs.size() == 1) {
                updatedArray = batchResult;
            } else {
                int index_at_sub_batch = 0;
                for (StatementRuntime batchRuntime : batchRuntimes) {
                    Integer _index_at_batch_ = batchRuntime.getProperty("_index_at_batch_");
                    updatedArray[_index_at_batch_] = batchResult[index_at_sub_batch++];
                }
            }
        }
        return updatedArray;
    }
  • 2.0u8的版本
    private Object executeBatch(StatementRuntime... runtimes) {
        int[] updatedArray = new int[runtimes.length];
        Map<String, List<StatementRuntime>> batchs = new HashMap<String, List<StatementRuntime>>();
        for (int i = 0; i < runtimes.length; i++) {
            StatementRuntime runtime = runtimes[i];
            List<StatementRuntime> batch = batchs.get(runtime.getSQL());
            if (batch == null) {
                batch = new ArrayList<StatementRuntime>(runtimes.length);
                batchs.put(runtime.getSQL(), batch);
            }
            runtime.setAttribute("_index_at_batch_", i); // 該runtime在batch中的位置
            batch.add(runtime);
        }
        // TODO: 多個真正的batch可以考慮并行執(zhí)行(而非順序執(zhí)行)~待定
        for (Map.Entry<String, List<StatementRuntime>> batch : batchs.entrySet()) {
            String sql = batch.getKey();
            List<StatementRuntime> batchRuntimes = batch.getValue();
            StatementRuntime runtime = batchRuntimes.get(0);
            DataAccess dataAccess = dataAccessFactory.getDataAccess(//
                runtime.getMetaData(), runtime.getAttributes());
            List<Object[]> argsList = new ArrayList<Object[]>(batchRuntimes.size());
            for (StatementRuntime batchRuntime : batchRuntimes) {
                argsList.add(batchRuntime.getArgs());
            }
            int[] batchResult = dataAccess.batchUpdate(sql, argsList);
            if (batchs.size() == 1) {
                updatedArray = batchResult;
            } else {
                int index_at_sub_batch = 0;
                for (StatementRuntime batchRuntime : batchRuntimes) {
                    Integer _index_at_batch_ = batchRuntime.getAttribute("_index_at_batch_");
                    updatedArray[_index_at_batch_] = batchResult[index_at_sub_batch++];
                }
            }
        }
        if (returnType == void.class) {
            return null;
        }
        if (returnType == int[].class) {
            return updatedArray;
        }
        if (returnType == Integer.class || returnType == Boolean.class) {
            int updated = 0;
            for (int value : updatedArray) {
                updated += value;
            }
            return returnType == Boolean.class ? updated > 0 : updated;
        }
        throw new InvalidDataAccessApiUsageException(
            "bad return type for batch update: " + runtimes[0].getMetaData().getMethod());
    }

小結(jié)

雖然可以理解為是框架本身的一個問題,但是發(fā)現(xiàn)對本身框架使用中的一些細節(jié)了解的不夠深刻,已經(jīng)對一些不太常見但很有用的信息熟悉不足,導致了還是花費了一些時間在這個問題上。
但總體來說還是蠻有意思的一個問題,一是記錄下來給自己做一個回顧,二是如果能幫助到遇到同樣問題的同學就太好了~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗。 張土汪:刷leetcod...
    土汪閱讀 12,921評論 0 33
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,057評論 0 9
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,556評論 19 139
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx閱讀 8,485評論 0 16
  • 二寶快八個月了~四月份產(chǎn)假休完,因為沒人帶寶,我也只能辭了工作再次成為全職媽媽!之前因為大寶我也做了十年的全職媽媽...
    buibui_fly閱讀 528評論 2 2

友情鏈接更多精彩內(nèi)容