日期:2014-05-16  浏览次数:20484 次

修改dbunit的几个bug兼对dbunit进行扩展
最近在对unitils进行扩展, 主要是数据库这块儿的内容, 对, 就是dbunit, dbunit给我的感觉是扩展性太差了, 很多类的构造函数采用包可见, 抽象类的抽象方法包可见, 根本没办法继承复写某些方法, 可定制性和unitils比起来也差的不是一点点, 根本就是一个封闭的系统. 导致很多代码不得不大段的拷贝代码来满足自身的需要.

我这里采用了excel格式来构造测试数据, 目前发现的dbunit(使用版本为2.4.6)的几个问题:
  • 有些大数字会采用科学计数法来表示, 导致在解析的时候数据不正确
  • dbunit内部对日期时间的处理会用到TimeZone这个东东, 这个在国际化方面应该是有价值的, 但是在我们的测试中却会导致时间与格林威治时间做8个小时的偏移转换
  • 对excel中的空行没有进行处理(比如excel本来只有两行数据, 但是不知什么原因会存在一些空行, 导致在插入数据库的时候会有Null相关的错误)
  • 对测试数据不仅涉及到根据主键清理, 有时候还需要根据一些特殊的字段值进行清理, 比如唯一键, 而这个需要进行扩展, 而XlsTable的包可见, 基本上必须另外实现一套(TMD,恨得让人直咬牙).


针对以上问题的解决方案:
科学计数法问题
这里需要对XlsTable中的getValue()方法下进行处理, 具体代码如下:
    static final Pattern pattern = Pattern.compile("[eE]");
    private BigDecimal toBigDecimal(double cellValue) {
        String resultString = String.valueOf(cellValue);

        // 针对科学计数法的处理(对于小数位数精确到5位)
        if (pattern.matcher(resultString).find()) {
            DecimalFormat format = new DecimalFormat("#####.#####");
            resultString = format.format(cellValue);
        }


        if (resultString.endsWith(".0")) {
            resultString = resultString.substring(0, resultString.length() - 2);
        }

        BigDecimal result = new BigDecimal(resultString);
        return result;

    }

这里暂定精确到小数5位, 有没有更好的解决方案?

日期时间问题
在某个地方对TimeZone做个初始化, 具体原理懒得去探究了, 反正我这样解决了问题
        // 由于dbunit对excel的时间处理会使用TimeZone.getOffset()做一个偏移转换, 这里需要设置一下
        TimeZone.setDefault(TimeZone.getTimeZone("GMT+8"));


空行的问题
这个需要实现一个IBatchStatement, 在批量处理数据的时候, 如果遇到空行应该跳过, dbunit有一个自己的实现:BatchStatement, 日!把构造函数声明为包可见, 让你无从继承改写. 无奈copy一些代码重写了一个. 另外还需要重写一个PreparedStatementFactory, 用来创建自己的那个IBatchStatement实现类.
public class TPreparedBatchStatement implements IPreparedBatchStatement {
    boolean notAllNull; // 所有参数是否为null
    boolean noParameter = true; // 是否指定参数
    private int _index;

    ...

    public void close() throws SQLException {
        ...
    }

    public void addValue(Object value, DataType dataType)
            throws TypeCastException, SQLException {
        logger.debug("addValue(value={}, dataType={}) - start", value, dataType);

        noParameter = false;

        // Special NULL handling
        if (value == null || value == ITable.NO_VALUE) {
            _statement.setNull(++_index, dataType.getSqlType());
            return;
        }

        notAllNull = true;

        dataType.setSqlValue(value, ++_index, _statement);
    }

    public void addBatch() throws SQLException {
        logger.debug("addBatch() - start");
        // 没有参数, 或者有参数, 但是参数不全为null
        if (noParameter || (!noParameter && notAllNull)) {
            _statement.addBatch();
            notAllNull = false;
            noParameter = true;
        }
        _index = 0;
    }
...

具体就是加了几个判断, 批量处理的行数据为null的时候不进行批量操作

其实dbunit也有一个类似unitils的控制中心:DatabaseConfig类. 但是可配置的东东太少太少, 我需要的没有, 有的我一个都不需要:(, 无语.

根据唯一键清数据
这个借鉴了dbunit原来的做法(现在去掉了唯一键标识的功能), 给字段加下划线style来标识. 本来打算根据数据库的metadata信息来获取唯一键的, 但是jdbc没有提供这样的接口, 而这种用下划线标识具有更好的扩展性, 可维护性, 可移植性.
具体做法是在XlsTable的createMetaData()方法中检查字段的style, 然后利用了Column中的remark属性来存储唯一键标识信息.
            Column column = null;
            // 标识唯一键
            byte underline = cell.getCellStyle().getFont(workbook).getUnderline();
            if (underline == 1) {
                column = new Column(columnName, DataType.UNKNOWN, null, null, null, "unique", null);
            } else {
                column = new Column(columnName, DataType.UNKNOWN);
            }
            columnList.add(column);


然后重新定义了一个DatabaseOperation:
public class DeleteByUniqueKeyOperation extends DatabaseOperation {
    pri