dbunit經典的NoSuchColumnException解決之道

抱怨 

dbunit這麼多人用,這個項目竟然好像沒有人在維護了,自動2012年9月release一個版本後,再沒有更新了,寒心啊。 

dbunit有一個大大的BUG,即會解釋不了MySQL表的結構,在使用@DataSet準備數據時,會拋出相似以下的異常: 
java

Java代碼  收藏代碼mysql

  1. Caused by: org.unitils.core.UnitilsException: Error while executing DataSetLoadStrategy  sql

  2.     at org.unitils.dbunit.datasetloadstrategy.impl.BaseDataSetLoadStrategy.execute(BaseDataSetLoadStrategy.java:46)  數據庫

  3.     at org.unitils.dbunit.DbUnitModule.insertDataSet(DbUnitModule.java:230)  框架

  4.     at org.unitils.dbunit.DbUnitModule.insertDataSet(DbUnitModule.java:153)  ide

  5.     ... 35 more  fetch

  6. 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.  ui

  7.     at org.dbunit.dataset.AbstractTableMetaData.getColumnIndex(AbstractTableMetaData.java:117)  spa

  8.     at org.dbunit.operation.AbstractOperation.getOperationMetaData(AbstractOperation.java:89)  .net

  9.     at org.dbunit.operation.AbstractBatchOperation.execute(AbstractBatchOperation.java:140)  

  10.     at org.dbunit.operation.CompositeOperation.execute(CompositeOperation.java:79)  

  11.     at org.unitils.dbunit.datasetloadstrategy.impl.CleanInsertLoadStrategy.doExecute(CleanInsertLoadStrategy.java:45)  

  12.     at org.unitils.dbunit.datasetloadstrategy.impl.BaseDataSetLoadStrategy.execute(BaseDataSetLoadStrategy.java:44)  

  13.     ... 37 more  


網上有不少痛苦的人在苦苦尋答案,但都依舊痛苦着... 
http://zfanxu.iteye.com/blog/1508339 
http://bbs.csdn.net/topics/310215234 

其實這是dbunit的一個BUG,好像不少版本都有這個問題,報告說解決了,其實並無解決。我使用最新的2.4.9的版本照樣會拋出這個問題。 

解決 

碰到問題光抱怨是沒有用的,又不能期望dbunit的做者改,只能本身着騰了。按照網上的幾篇文章改了dbunit的源碼,從新編譯上傳到本身的Maven私服上。終於解決了。 

爲了不你們再從新更改編譯,我把已經解譯好的dbunit jar放在附件中,你們須要的話能夠下載使用。 


繼續...
 

最近又在整基於DB2的unitils框架,發現又出現問題了,結果再次好好跟蹤了unitils及dbunit的源碼,終於有了顛覆性的重大發現: 

原來網上一直說的是DBUNIT框架致使這個問題的說明是錯誤的,真正的錯誤是unitils框架的錯誤!! 

由於DBUNIT已經爲不一樣數據庫提供了不一樣的接口實現: 

Java代碼  收藏代碼

  1. org.dbunit.database.IMetadataHandler  


而unitils(具體地說是DbUnitModule模塊)無論你什麼數據庫,它統一使用這個類: 

Java代碼  收藏代碼

  1. org.dbunit.database.DefaultMetadataHandler  



若是數據庫不特殊,固然用DefaultMetadataHandler這個沒有問題,若是特殊,則就取不到數據庫的Metadata信息了,結果異常就發生了。 

可是,目前的DBUnit的Db2MetadataHandler確實是有BUG的,因此個人解決方法是: 

1)複寫了unitils的DbUnitModule實現類; 
2)複寫了dbunit的Db2MetadataHandler實現類; 
3)配置unitils的配置文件,應用這些自定義的實現類。 

Java代碼  收藏代碼

  1. package com.ridge.test.unitils.ext;  

  2.   

  3. import org.dbunit.database.DatabaseConfig;  

  4. import org.dbunit.database.DefaultMetadataHandler;  

  5. import org.dbunit.dataset.DataSetException;  

  6. import org.dbunit.dataset.IDataSet;  

  7. import org.dbunit.dataset.datatype.IDataTypeFactory;  

  8. import org.dbunit.dataset.filter.ITableFilterSimple;  

  9. import org.dbunit.ext.db2.Db2DataTypeFactory;  

  10. import org.dbunit.ext.db2.Db2MetadataHandler;  

  11. import org.dbunit.ext.mysql.MySqlDataTypeFactory;  

  12. import org.dbunit.ext.mysql.MySqlMetadataHandler;  

  13. import org.unitils.core.UnitilsException;  

  14. import org.unitils.core.dbsupport.DbSupport;  

  15. import org.unitils.core.dbsupport.DefaultSQLHandler;  

  16. import org.unitils.core.dbsupport.SQLHandler;  

  17. import org.unitils.dbunit.DbUnitModule;  

  18. import org.unitils.dbunit.util.DbUnitDatabaseConnection;  

  19.   

  20. import javax.sql.DataSource;  

  21.   

  22. import static org.dbunit.database.DatabaseConfig.FEATURE_BATCHED_STATEMENTS;  

  23. import static org.dbunit.database.DatabaseConfig.PROPERTY_DATATYPE_FACTORY;  

  24. import static org.dbunit.database.DatabaseConfig.PROPERTY_ESCAPE_PATTERN;  

  25. import static org.unitils.core.dbsupport.DbSupportFactory.getDbSupport;  

  26. import static org.unitils.core.util.ConfigUtils.getInstanceOf;  

  27.   

  28. /** 

  29.  * @author : chenxh(quickselect@163.com) 

  30.  * @date: 13-10-9 

  31.  */  

  32. public class MyDbunitModule extends DbUnitModule {  

  33.   

  34.     protected DbUnitDatabaseConnection createDbUnitConnection(String schemaName) {  

  35.         // A DbSupport instance is fetched in order to get the schema name in correct case  

  36.         DataSource dataSource = getDatabaseModule().getDataSourceAndActivateTransactionIfNeeded();  

  37.         SQLHandler sqlHandler = new DefaultSQLHandler(dataSource);  

  38.         DbSupport dbSupport = getDbSupport(configuration, sqlHandler, schemaName);  

  39.   

  40.         // Create connection  

  41.         DbUnitDatabaseConnection connection = new DbUnitDatabaseConnection(dataSource, dbSupport.getSchemaName());  

  42.         DatabaseConfig config = connection.getConfig();  

  43.   

  44.         // Make sure that dbunit's correct IDataTypeFactory, that handles dbms specific data type issues, is used  

  45.         IDataTypeFactory dataTypeFactory = getInstanceOf(IDataTypeFactory.class, configuration, dbSupport.getDatabaseDialect());  

  46.         config.setProperty(PROPERTY_DATATYPE_FACTORY, dataTypeFactory);  

  47.         // Make sure that table and column names are escaped using the dbms-specific identifier quote string  

  48.         if (dbSupport.getIdentifierQuoteString() != null)  

  49.             config.setProperty(PROPERTY_ESCAPE_PATTERN, dbSupport.getIdentifierQuoteString() + '?' + dbSupport.getIdentifierQuoteString());  

  50.         // Make sure that batched statements are used to insert the data into the database  

  51.         config.setProperty(FEATURE_BATCHED_STATEMENTS, "true");  

  52.         // Make sure that Oracle's recycled tables (BIN$) are ignored (value is used to ensure dbunit-2.2 compliancy)  

  53.         config.setProperty("http://www.dbunit.org/features/skipOracleRecycleBinTables""true");  

  54.   

  55.         //注意這兒:根據不一樣的數據庫(unitils的database.dialect配置參數)爲dbunit  

  56.         //指定使用不一樣的IMetadataHandler實現(其它數據庫均可以用默認的,還有一個Netezza也是特別的,這裏忽略了)  

  57.         if("db2".equalsIgnoreCase(configuration.getProperty("database.dialect"))){  

  58.             config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,  

  59.                     new Db2DataTypeFactory());  

  60.               

  61.             //因爲dbunit自身提供的Db2MetadataHandler有BUG,因此這裏使用本身寫的  

  62.             //MyDb2MetadataHandler,源碼在後面了。  

  63.             config.setProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER,  

  64.                     new MyDb2MetadataHandler());  

  65.         }else if("mysql".equalsIgnoreCase(configuration.getProperty("database.dialect"))){  

  66.             config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,  

  67.                     new MySqlDataTypeFactory());  

  68.             config.setProperty(DatabaseConfig.PROPERTY_METADATA_HANDLER,  

  69.                     new MySqlMetadataHandler());  

  70.         }  

  71.         return connection;  

  72.     }  

  73. }  




下面是MyDb2MetadataHandler的源碼: 

Java代碼  收藏代碼

  1. package com.ridge.test.unitils.ext;  

  2.   

  3. import org.dbunit.ext.db2.Db2MetadataHandler;  

  4. import java.sql.DatabaseMetaData;  

  5. import java.sql.ResultSet;  

  6. import java.sql.SQLException;  

  7.   

  8. import org.dbunit.util.SQLHelper;  

  9. import org.slf4j.Logger;  

  10. import org.slf4j.LoggerFactory;  

  11.   

  12. /** 

  13.  * @author : chenxh(quickselect@163.com) 

  14.  * @date: 13-10-9 

  15.  */  

  16. public class MyDb2MetadataHandler extends Db2MetadataHandler {  

  17.     private static final Logger logger = LoggerFactory.getLogger(MyDb2MetadataHandler.class);  

  18.   

  19.     public ResultSet getTables(DatabaseMetaData metaData, String schemaName, String[] tableType)  

  20.             throws SQLException  

  21.     {  

  22.         if(logger.isTraceEnabled())  

  23.             logger.trace("tableExists(metaData={}, schemaName={}, tableType={}) - start",  

  24.                     new Object[] {metaData, schemaName, tableType} );  

  25.         return metaData.getTables(null, schemaName, "%", tableType);  

  26.     }  

  27.   

  28.     public boolean tableExists(DatabaseMetaData metaData, String schema, String tableName)  

  29.             throws SQLException  

  30.     {  

  31.         ResultSet tableRs = metaData.getTables(null, schema, tableName, null);  

  32.         try  

  33.         {  

  34.             return tableRs.next();  

  35.         }  

  36.         finally  

  37.         {  

  38.             SQLHelper.close(tableRs);  

  39.         }  

  40.     }  

  41.   

  42.     public ResultSet getColumns(DatabaseMetaData databaseMetaData, String schemaName, String tableName)  

  43.             throws SQLException {  

  44.         // Note that MySQL uses the catalogName instead of the schemaName, so  

  45.         // pass in the given schema name as catalog name (first argument).  

  46.   

  47.         ResultSet resultSet = databaseMetaData.getColumns(  

  48.                 null, schemaName, tableName, "%");  

  49.         return resultSet;  

  50.     }  

  51.   

  52.     public boolean matches(ResultSet columnsResultSet, String catalog,  

  53.                            String schema, String table, String column,  

  54.                            boolean caseSensitive) throws SQLException  

  55.     {  

  56.         String catalogName = columnsResultSet.getString(1);  

  57.         String schemaName = columnsResultSet.getString(2);  

  58.         String tableName = columnsResultSet.getString(3);  

  59.         String columnName = columnsResultSet.getString(4);  

  60.   

  61.         // MYSQL provides only a catalog but no schema  

  62.         if(schema != null && schemaName == null && catalog==null && catalogName != null){  

  63.             logger.debug("Switching catalog/schema because the are mutually null");  

  64.             schemaName = catalogName;  

  65.             catalogName = null;  

  66.         }  

  67.   

  68.         boolean areEqual =  

  69.                 areEqualIgnoreNull(table, tableName, caseSensitive) &&  

  70.                         areEqualIgnoreNull(column, columnName, caseSensitive);  

  71.         return areEqual;  

  72.     }  

  73.   

  74.     private boolean areEqualIgnoreNull(String value1, String value2,  

  75.                                        boolean caseSensitive) {  

  76.         return SQLHelper.areEqualIgnoreNull(value1, value2, caseSensitive);  

  77.     }  

  78. }  



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

Java代碼  收藏代碼

  1. ...  

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

  3. ...  



總結 

採用前面的解決方案只能解決mysql的問題,且直接改dbunit的源碼,是很差的方案,如今我把它廢棄了,你們就不要了。 

採用第二種方案吧,是優雅的解決方案,沒有更改dbunit的源碼,僅經過unitils的擴展配置實現了,因此你不要下載附件的dbunit-2.4.8.2.jar了,直接使用最新的dbunit版本吧: 

Xml代碼  收藏代碼

  1. <dependency>  

  2.     <groupId>org.dbunit</groupId>  

  3.     <artifactId>dbunit</artifactId>  

  4.     <version>2.4.9</version>>  

  5.     </exclusions>  

  6. </dependency>  



這個問題啊,讓我死幾次的心都有了,如今終於解決了,但願對你們有幫助!

相關文章
相關標籤/搜索