处理步骤

第一步,检查报异常的方法,发现是一个SQL查询报出来的异常,大意是从MYSQL查询出来的数据转换成数字类型的,内容如下

org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [SELECT colum_xxxxx FROM table_xxxxxx where STATUS = 0 order by LAST_MODIFY_TIME limit 300]; '815996050122815896400122' in column '1' is outside valid range for the datatype BIGINT.; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLDataException: '815996050122815896400122' in column '1' is outside valid range for the datatype BIGINT.

从上的内容来看,想要把815996050122815896400122的结果转换成BIGINT类型失败,因为超出了BIGINT的范围

这里提一下,MySQL BIGINT 类型的范围在 -9223372036854775808 和 9223372036854775807 之间,与Java的Long类型范围一致,为一个19位数字。

而给定的值为一个22位数字,这显然会失败。

接下来去查看对应表结构,发现对应字段是varchar(32)的字符串类型的。那么显然A同事在做开发这个功能的时候,这个22位长度的数字可以正常转成字符串存进去,但是另外一个B同事在取数据的时候根据自己的业务场景想要将Mysql查出来的结果直接映射为Long类型,这时候就出现异常了。

到这一步,可能觉得那把取值的时候改为String型不就可以了么?于是尝试着评估下改为String型会带来哪些影响。以下是是处理过程,涉及到具体业务场景的考虑。

首先这个字段需要对12取模,然后把对应数据存入不同的库里。其次这个字段需要请求淘宝的官方API查询数据,相关调用部分都要进行修改。然后对应的数据需开放了内部服务接口给其他系统调用,这块相关字段类型也需要修改,以及还有其他的一些零碎的地方。瞬间感觉好坑。近几个月迭代的需求都会要做一些修改调整。

但是,重新看下需求,这个字段的来源其实是淘宝给的分销采购单的退款单号,淘宝API上的给的定义类型就是Long类型的,再看下目前线上生产环境的数据是11位的数字,所以实际上来说,B同事在一开始做这块查询的时候用Long类型取值是没有问题的。而A同事在做写入功能和数据库设计的时候,没有考虑具体的业务场景,只是考虑觉得这个ID应该预留长度空间,给数据库设计了个varchar(32)的字段存储,写入的时候用String型存入,咱们也不能说他做得不对。本质上来说是两个模块的人对需求理解不一致,沟通不充分产生的结果。

那么对这个异常处理的方式也就很明显了,在写入的时候对该字段进行校验,如果不能转换成Long类型的退款单号ID字符串,给业务人员报错,这显然是不正确的退款单号。

照着这个思路,我们有两种实现方法

第一种,直接Long.parseLong(idString)来尝试将字符串转换为Long类型对象,用try catch将Long.parseLong()转换的代码包含起来,尝试捕获异常,如果转换失败,那么给业务提示不正确的单号。

但是根据一个开发准则“不应使用异常来实现业务逻辑(或流程控制)”,以及最小惊讶原则(Principle of Least Astonishment),这提醒着我,不应当这样来处理。

http://wiki.c2.com/?DontUseExceptionsForFlowControl

于是试着用第二种方式,自己来实现判断一个字符串能否转换成Long类型对象,代码如下

    public boolean validateLongNumStr(String longNumStr) {
        //清除先导0
        String nonePreZeroNum = longNumStr.replaceFirst("^0+", "");
        String longMax = Long.toString(Long.MAX_VALUE);
        //长度比longMax长的必定错误
        if ( nonePreZeroNum.length() > longMax.length()){
            return false;
        }
        //长度相同的比较字典序即可
        if (nonePreZeroNum.length() == longMax.length() && longMax.compareTo(nonePreZeroNum) < 0){
            return false;
        }
        return true;
    }
  1. 清除先导0,目标字符串需要提前处理清除掉非数字字符
  2. 生成一个Long类型的最大值,即9223372036854775807的字符串
  3. 如果目标字符串长度大于这个longMax的字符串,则必定会转换失败
  4. 如果长度相等,则比较下两个字符串的字典序,如果目标字符比longMax的字典序大,那么也会转换失败
  5. 剩下的就是长度比longMax短的字符串,可以顺利转换成功

当然,别忘了写下对应单元测试