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 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> batchs = new HashMap>();
        for (int i = 0; i < runtimes.length; i++) {
            StatementRuntime runtime = runtimes[i];
            List batch = batchs.get(runtime.getSQL());
            if (batch == null) {
                batch = new LinkedList();
                batchs.put(runtime.getSQL(), batch);
            }
            runtime.setProperty("_index_at_batch_", i); // 该runtime在batch中的位置
            batch.add(runtime);
        }
        // TODO: 多个真正的batch可以考虑并行执行~待定
        for (Map.Entry> batch : batchs.entrySet()) {
            String sql = batch.getKey();
            List batchRuntimes = batch.getValue();
            StatementRuntime runtime = batchRuntimes.get(0);
            DataAccess dataAccess = dataAccessProvider.getDataAccess(//
                    runtime.getMetaData(), runtime.getProperties());
            List argsList = new ArrayList(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> batchs = new HashMap>();
        for (int i = 0; i < runtimes.length; i++) {
            StatementRuntime runtime = runtimes[i];
            List batch = batchs.get(runtime.getSQL());
            if (batch == null) {
                batch = new ArrayList(runtimes.length);
                batchs.put(runtime.getSQL(), batch);
            }
            runtime.setAttribute("_index_at_batch_", i); // 该runtime在batch中的位置
            batch.add(runtime);
        }
        // TODO: 多个真正的batch可以考虑并行执行(而非顺序执行)~待定
        for (Map.Entry> batch : batchs.entrySet()) {
            String sql = batch.getKey();
            List batchRuntimes = batch.getValue();
            StatementRuntime runtime = batchRuntimes.get(0);
            DataAccess dataAccess = dataAccessFactory.getDataAccess(//
                runtime.getMetaData(), runtime.getAttributes());
            List argsList = new ArrayList(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());
    }

小结

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

你可能感兴趣的:(rose jade处理DELETE语句时,偶尔报错)