dbunit經(jīng)典的NoSuchColumnException解決之道

抱怨來(lái)了

dbunit這么多人用兆衅,這個(gè)項(xiàng)目居然好像沒(méi)有人在維護(hù)了镀层,自動(dòng)2012年9月release一個(gè)版本后,再?zèng)]有更新了船逮,寒心啊顾腊。

dbunit有一個(gè)大大的BUG,即會(huì)解釋不了MySQL表的結(jié)構(gòu)傻唾,在使用@DataSet準(zhǔn)備數(shù)據(jù)時(shí)投慈,會(huì)拋出類(lèi)似如下的異常:

Caused by: org.unitils.core.UnitilsException: Error while executing DataSetLoadStrategy
    at org.unitils.dbunit.datasetloadstrategy.impl.BaseDataSetLoadStrategy.execute(BaseDataSetLoadStrategy.java:46)
    at org.unitils.dbunit.DbUnitModule.insertDataSet(DbUnitModule.java:230)
    at org.unitils.dbunit.DbUnitModule.insertDataSet(DbUnitModule.java:153)
    ... 35 more
Caused by: org.dbunit.dataset.NoSuchColumnException: t_upload_file.ID -  (Non-uppercase input column: id) in ColumnNameToIndexes cache map. Note that the map's column names are NOT case sensitive.
    at org.dbunit.dataset.AbstractTableMetaData.getColumnIndex(AbstractTableMetaData.java:117)
    at org.dbunit.operation.AbstractOperation.getOperationMetaData(AbstractOperation.java:89)
    at org.dbunit.operation.AbstractBatchOperation.execute(AbstractBatchOperation.java:140)
    at org.dbunit.operation.CompositeOperation.execute(CompositeOperation.java:79)
    at org.unitils.dbunit.datasetloadstrategy.impl.CleanInsertLoadStrategy.doExecute(CleanInsertLoadStrategy.java:45)
    at org.unitils.dbunit.datasetloadstrategy.impl.BaseDataSetLoadStrategy.execute(BaseDataSetLoadStrategy.java:44)
    ... 37 more

網(wǎng)上有很多痛苦的人在苦苦尋答案,但都依舊痛苦著...
http://zfanxu.iteye.com/blog/1508339
http://zfanxu.iteye.com/blog/1508339

其實(shí)這是dbunit的一個(gè)BUG冠骄,好像很多版本都有這個(gè)問(wèn)題伪煤,報(bào)告說(shuō)解決了,其實(shí)并沒(méi)有解決凛辣。我使用最新的2.4.9的版本照樣會(huì)拋出這個(gè)問(wèn)題抱既。

解決辦法

碰到問(wèn)題光抱怨是沒(méi)有用的,又不能指望dbunit的作者改扁誓,只能自己著騰了防泵。按照網(wǎng)上的幾篇文章改了dbunit的源碼,重新編譯上傳到自己的Maven私服上蝗敢。終于解決了捷泞。

為了避免大家再重新更改編譯,我把已經(jīng)解譯好的dbunit jar放在附件中寿谴,大家需要的話可以下載使用锁右。

繼續(xù)...

最近又在整基于DB2的unitils框架,發(fā)現(xiàn)又出現(xiàn)問(wèn)題了,結(jié)果再次好好跟蹤了unitils及dbunit的源碼咏瑟,終于有了顛覆性的重大發(fā)現(xiàn):

原來(lái)網(wǎng)上一直說(shuō)的是DBUNIT框架導(dǎo)致這個(gè)問(wèn)題的說(shuō)明是錯(cuò)誤的拂到,真正的錯(cuò)誤是unitils框架的錯(cuò)誤!码泞!

因?yàn)镈BUNIT已經(jīng)為不同數(shù)據(jù)庫(kù)提供了不同的接口實(shí)現(xiàn):

org.dbunit.database.IMetadataHandler
而unitils(具體地說(shuō)是DbUnitModule模塊)不管你什么數(shù)據(jù)庫(kù)兄旬,它統(tǒng)一使用這個(gè)類(lèi):
org.dbunit.database.DefaultMetadataHandler

如果數(shù)據(jù)庫(kù)不特殊,當(dāng)然用DefaultMetadataHandler這個(gè)沒(méi)有問(wèn)題余寥,如果特殊领铐,則就取不到數(shù)據(jù)庫(kù)的Metadata信息了,結(jié)果異常就發(fā)生了宋舷。

但是罐孝,目前的DBUnit的Db2MetadataHandler確實(shí)是有BUG的,所以我的解決方法是:

1)復(fù)寫(xiě)了unitils的DbUnitModule實(shí)現(xiàn)類(lèi)肥缔;
2)復(fù)寫(xiě)了dbunit的Db2MetadataHandler實(shí)現(xiàn)類(lèi)莲兢;
3)配置unitils的配置文件,應(yīng)用這些自定義的實(shí)現(xiàn)類(lèi)续膳。

package com.ridge.test.unitils.ext;

import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.DefaultMetadataHandler;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.datatype.IDataTypeFactory;
import org.dbunit.dataset.filter.ITableFilterSimple;
import org.dbunit.ext.db2.Db2DataTypeFactory;
import org.dbunit.ext.db2.Db2MetadataHandler;
import org.dbunit.ext.mysql.MySqlDataTypeFactory;
import org.dbunit.ext.mysql.MySqlMetadataHandler;
import org.unitils.core.UnitilsException;
import org.unitils.core.dbsupport.DbSupport;
import org.unitils.core.dbsupport.DefaultSQLHandler;
import org.unitils.core.dbsupport.SQLHandler;
import org.unitils.dbunit.DbUnitModule;
import org.unitils.dbunit.util.DbUnitDatabaseConnection;

import javax.sql.DataSource;

import static org.dbunit.database.DatabaseConfig.FEATURE_BATCHED_STATEMENTS;
import static org.dbunit.database.DatabaseConfig.PROPERTY_DATATYPE_FACTORY;
import static org.dbunit.database.DatabaseConfig.PROPERTY_ESCAPE_PATTERN;
import static org.unitils.core.dbsupport.DbSupportFactory.getDbSupport;
import static org.unitils.core.util.ConfigUtils.getInstanceOf;

/**
 * @author : chenxh(quickselect@163.com)
 * @date: 13-10-9
 */
public class MyDbunitModule extends DbUnitModule {

    protected DbUnitDatabaseConnection createDbUnitConnection(String schemaName) {
        // A DbSupport instance is fetched in order to get the schema name in correct case
        DataSource dataSource = getDatabaseModule().getDataSourceAndActivateTransactionIfNeeded();
        SQLHandler sqlHandler = new DefaultSQLHandler(dataSource);
        DbSupport dbSupport = getDbSupport(configuration, sqlHandler, schemaName);

        // Create connection
        DbUnitDatabaseConnection connection = new DbUnitDatabaseConnection(dataSource, dbSupport.getSchemaName());
        DatabaseConfig config = connection.getConfig();

        // Make sure that dbunit's correct IDataTypeFactory, that handles dbms specific data type issues, is used
        IDataTypeFactory dataTypeFactory = getInstanceOf(IDataTypeFactory.class, configuration, dbSupport.getDatabaseDialect());
        config.setProperty(PROPERTY_DATATYPE_FACTORY, dataTypeFactory);
        // Make sure that table and column names are escaped using the dbms-specific identifier quote string
        if (dbSupport.getIdentifierQuoteString() != null)
            config.setProperty(PROPERTY_ESCAPE_PATTERN, dbSupport.getIdentifierQuoteString() + '?' + dbSupport.getIdentifierQuoteString());
        // Make sure that batched statements are used to insert the data into the database
        config.setProperty(FEATURE_BATCHED_STATEMENTS, "true");
        // Make sure that Oracle's recycled tables (BIN$) are ignored (value is used to ensure dbunit-2.2 compliancy)
        config.setProperty("http://www.dbunit.org/features/skipOracleRecycleBinTables", "true");

        //注意這兒:根據(jù)不同的數(shù)據(jù)庫(kù)(unitils的database.dialect配置參數(shù))為dbunit
        //指定使用不同的IMetadataHandler實(shí)現(xiàn)(其它數(shù)據(jù)庫(kù)都可以用默認(rèn)的改艇,還有一個(gè)Netezza也是特別的,這里忽略了)
        if("db2".equalsIgnoreCase(configuration.getProperty("database.dialect"))){
            config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,
                    new Db2DataTypeFactory());
            
            //由于dbunit自身提供的Db2MetadataHandler有BUG坟岔,所以這里使用自己寫(xiě)的
            //MyDb2MetadataHandler,源碼在后面了谒兄。
            config.setProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER,
                    new MyDb2MetadataHandler());
        }else if("mysql".equalsIgnoreCase(configuration.getProperty("database.dialect"))){
            config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,
                    new MySqlDataTypeFactory());
            config.setProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER,
                    new MySqlMetadataHandler());
        }
        return connection;
    }
}

下面是MyDb2MetadataHandler的源碼:

package com.ridge.test.unitils.ext;

import org.dbunit.ext.db2.Db2MetadataHandler;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.dbunit.util.SQLHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author : chenxh(quickselect@163.com)
 * @date: 13-10-9
 */
public class MyDb2MetadataHandler extends Db2MetadataHandler {
    private static final Logger logger = LoggerFactory.getLogger(MyDb2MetadataHandler.class);

    public ResultSet getTables(DatabaseMetaData metaData, String schemaName, String[] tableType)
            throws SQLException
    {
        if(logger.isTraceEnabled())
            logger.trace("tableExists(metaData={}, schemaName={}, tableType={}) - start",
                    new Object[] {metaData, schemaName, tableType} );
        return metaData.getTables(null, schemaName, "%", tableType);
    }

    public boolean tableExists(DatabaseMetaData metaData, String schema, String tableName)
            throws SQLException
    {
        ResultSet tableRs = metaData.getTables(null, schema, tableName, null);
        try
        {
            return tableRs.next();
        }
        finally
        {
            SQLHelper.close(tableRs);
        }
    }

    public ResultSet getColumns(DatabaseMetaData databaseMetaData, String schemaName, String tableName)
            throws SQLException {
        // Note that MySQL uses the catalogName instead of the schemaName, so
        // pass in the given schema name as catalog name (first argument).

        ResultSet resultSet = databaseMetaData.getColumns(
                null, schemaName, tableName, "%");
        return resultSet;
    }

    public boolean matches(ResultSet columnsResultSet, String catalog,
                           String schema, String table, String column,
                           boolean caseSensitive) throws SQLException
    {
        String catalogName = columnsResultSet.getString(1);
        String schemaName = columnsResultSet.getString(2);
        String tableName = columnsResultSet.getString(3);
        String columnName = columnsResultSet.getString(4);

        // MYSQL provides only a catalog but no schema
        if(schema != null && schemaName == null && catalog==null && catalogName != null){
            logger.debug("Switching catalog/schema because the are mutually null");
            schemaName = catalogName;
            catalogName = null;
        }

        boolean areEqual =
                areEqualIgnoreNull(table, tableName, caseSensitive) &&
                        areEqualIgnoreNull(column, columnName, caseSensitive);
        return areEqual;
    }

    private boolean areEqualIgnoreNull(String value1, String value2,
                                       boolean caseSensitive) {
        return SQLHelper.areEqualIgnoreNull(value1, value2, caseSensitive);
    }
}

最后一步,更改unitils.properties的配置:

...
unitils.module.dbunit.className=com.ridge.test.unitils.ext.MyDbunitModule
...

總結(jié)一下

采用前面的解決方案只能解決mysql的問(wèn)題社付,且直接改dbunit的源碼承疲,是不好的方案,現(xiàn)在我把它廢棄了鸥咖,大家就不要了燕鸽。

采用第二種方案吧,是優(yōu)雅的解決方案啼辣,沒(méi)有更改dbunit的源碼啊研,僅通過(guò)unitils的擴(kuò)展配置實(shí)現(xiàn)了,所以你不要下載附件的dbunit-2.4.8.2.jar了鸥拧,直接使用最新的dbunit版本吧:

        <dependency>
            <groupId>org.dbunit</groupId>
            <artifactId>dbunit</artifactId>
            <version>2.4.9</version>>
            </exclusions>
        </dependency>

這個(gè)問(wèn)題啊党远,讓我死幾回的心都有了,現(xiàn)在終于解決了富弦,希望對(duì)大家有幫助沟娱!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市腕柜,隨后出現(xiàn)的幾起案子济似,更是在濱河造成了極大的恐慌柳爽,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碱屁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蛾找,警方通過(guò)查閱死者的電腦和手機(jī)娩脾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)打毛,“玉大人柿赊,你說(shuō)我怎么就攤上這事』猛鳎” “怎么了碰声?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熬甫。 經(jīng)常有香客問(wèn)我胰挑,道長(zhǎng),這世上最難降的妖魔是什么椿肩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任瞻颂,我火速辦了婚禮,結(jié)果婚禮上郑象,老公的妹妹穿的比我還像新娘贡这。我一直安慰自己,他們只是感情好厂榛,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布盖矫。 她就那樣靜靜地躺著,像睡著了一般击奶。 火紅的嫁衣襯著肌膚如雪辈双。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,184評(píng)論 1 308
  • 那天柜砾,我揣著相機(jī)與錄音辐马,去河邊找鬼。 笑死局义,一個(gè)胖子當(dāng)著我的面吹牛喜爷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播萄唇,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼檩帐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了另萤?” 一聲冷哼從身側(cè)響起湃密,我...
    開(kāi)封第一講書(shū)人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤诅挑,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后泛源,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拔妥,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年达箍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了没龙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缎玫,死狀恐怖硬纤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赃磨,我是刑警寧澤筝家,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站邻辉,受9級(jí)特大地震影響溪王,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜值骇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一在扰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雷客,春花似錦芒珠、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至部逮,卻和暖如春娜汁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兄朋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工掐禁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颅和。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓傅事,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親峡扩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蹭越,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359