rose jade处理DELETE语句时,偶尔报错

背景

项目中使用了paoding-rose作为开发框架,该框架作为国产的一个十分优秀的框架,在Jade方面处理的也非常好,但是在实际的使用过程中,发现了一个很有意思的问题,在使用Delete SQL语句批量删除数据时,当传参进去只有一个参数时,会因为返回类型的不知道导致抛异常。

这里还是先把解决方案说了好了,升级到最新2.0以上版本即可我们使用的是比较老的1.2.2版本

然后下面为了备忘,特别记录一下这个蛮有意思的问题。
Git上可以参考这个Issue


问题描述

问题主要体现为,偶然性会报ClassCastException错误,具体表现形式如下:

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的注解实现了SQL语句的执行地方,具体的SQL如下:

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

以上就是这个错误产生的基本现象,但是有意思的该问题并非一定会产生,根据详细的测试和分析发现,只有在传参List中元素个数大于1个以上时,会导致该错误的发生。


原因分析

  • 首先分析了这个现象以后觉得可能是传参进入的问题,导致了List转化存在的问题,但是后来请教了同事,其实rose这里和数据库中一般SQL语句的返回是不一致的,数据库中返回的是影响的行数,但是Rose中是一个是否更新成功的int[],其中1代表处理成功,0代表处理失败(具体为什么这么处理后面会分析到)。还有这里有个需要反省的就是,对getName()这个方法不熟悉,其实JVM已经告诉了我们问题点,出问题的类型是"[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

  • 所以之后根据这个返回值将SQL方法的返回值替换成为int[]类型,然而有趣的是这次反而在参数list只有一个值时报错了,于是进一步debug到源码中去看了一下(由于github上找不到1.2.2版本的内容,又找了好久source文件)

  • 这里就发现rose jade中关于这个处理很有意思:

  1. 在rose中,SQL语句只有两种类型:READ和WRITE,其中show、select等查询类的属于READ类型,而update、delete等则属于WRITE类型。
  2. 在WRITE类型中,会将ID IN (:list) 这种语句自动翻译成Batch语句来处理。但是在转换成Batch来处理时,在传入的list中只有一个值时,又会根据参数个数,将Batch转化成了SingleBatch,因此造成了这种不统一的情况(两者的返回值不一样,一个是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. 因此对照着源码又看了一下新版本,发现已经对executeBatch做了修正,增加了对一般数据库SQL返回影响行数的支持(有趣的是,也并没有放弃之前对int[]的支持,虽然可以理解为向下兼容,但是个人觉得其实也是想保持这种有趣的对数据处理的理解吧,挺有意思的)
  • 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可以考虑并行执行~待定
        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可以考虑并行执行(而非顺序执行)~待定
        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());
    }

小结

虽然可以理解为是框架本身的一个问题,但是发现对本身框架使用中的一些细节了解的不够深刻,已经对一些不太常见但很有用的信息熟悉不足,导致了还是花费了一些时间在这个问题上。
但总体来说还是蛮有意思的一个问题,一是记录下来给自己做一个回顾,二是如果能帮助到遇到同样问题的同学就太好了~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,719评论 0 33
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • Spark SQL, DataFrames and Datasets Guide Overview SQL Dat...
    Joyyx阅读 8,319评论 0 16
  • 二宝快八个月了~四月份产假休完,因为没人带宝,我也只能辞了工作再次成为全职妈妈!之前因为大宝我也做了十年的全职妈妈...
    buibui_fly阅读 364评论 2 2