dbunit经典的NoSuchColumnException解决之道

原创
2015/08/21 14:01
阅读数 2.4K

抱怨 

dbunit这么多人用,这个项目居然好像没有人在维护了,自动2012年9月release一个版本后,再没有更新了,寒心啊。 

dbunit有一个大大的BUG,即会解释不了MySQL表的结构,在使用@DataSet准备数据时,会抛出类似如下的异常: 

Java代码  收藏代码

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

  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)  

  5.     ... 35 more  

  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.  

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

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

  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>  



这个问题啊,让我死几回的心都有了,现在终于解决了,希望对大家有帮助!


展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部