当前位置: 首页 > 工具软件 > DbUnit > 使用案例 >

dbunit mysql bug no_dbunit经典的NoSuchColumnException解决之道

敖永丰
2023-12-01

抱怨来了

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

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

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

其实这是dbunit的一个BUG,好像很多版本都有这个问题,报告说解决了,其实并没有解决。我使用最新的2.4.9的版本照样会抛出这个问题。

解决办法

碰到问题光抱怨是没有用的,又不能指望dbunit的作者改,只能自己着腾了。按照网上的几篇文章改了dbunit的源码,重新编译上传到自己的Maven私服上。终于解决了。

为了避免大家再重新更改编译,我把已经解译好的dbunit jar放在附件中,大家需要的话可以下载使用。

继续...

最近又在整基于DB2的unitils框架,发现又出现问题了,结果再次好好跟踪了unitils及dbunit的源码,终于有了颠覆性的重大发现:

原来网上一直说的是DBUNIT框架导致这个问题的说明是错误的,真正的错误是unitils框架的错误!!

因为DBUNIT已经为不同数据库提供了不同的接口实现:

org.dbunit.database.IMetadataHandler

而unitils(具体地说是DbUnitModule模块)不管你什么数据库,它统一使用这个类:

org.dbunit.database.DefaultMetadataHandler

如果数据库不特殊,当然用DefaultMetadataHandler这个没有问题,如果特殊,则就取不到数据库的Metadata信息了,结果异常就发生了。

但是,目前的DBUnit的Db2MetadataHandler确实是有BUG的,所以我的解决方法是:

1)复写了unitils的DbUnitModule实现类;

2)复写了dbunit的Db2MetadataHandler实现类;

3)配置unitils的配置文件,应用这些自定义的实现类。

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");

//注意这儿:根据不同的数据库(unitils的database.dialect配置参数)为dbunit

//指定使用不同的IMetadataHandler实现(其它数据库都可以用默认的,还有一个Netezza也是特别的,这里忽略了)

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

config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY,

new Db2DataTypeFactory());

//由于dbunit自身提供的Db2MetadataHandler有BUG,所以这里使用自己写的

//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

...

总结一下

采用前面的解决方案只能解决mysql的问题,且直接改dbunit的源码,是不好的方案,现在我把它废弃了,大家就不要了。

采用第二种方案吧,是优雅的解决方案,没有更改dbunit的源码,仅通过unitils的扩展配置实现了,所以你不要下载附件的dbunit-2.4.8.2.jar了,直接使用最新的dbunit版本吧:

org.dbunit

dbunit

2.4.9>

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

 类似资料: