I have been using DBUnit to perform the database loading and unloading. It has worked very well for us.
Our project uses Spring and Hibernate and our junit / dbunit test hierarchy reads the data from xml files.
Take a look at the book 'Hibernate Quickly'. I am not an author or affiliated with the authors in anyway. They have a section on using dbunit which I used as my starting point.
This approach loads up the initial test data, we when create new objects, we have to clear and flush the hibernate session, then perform a findById kind of operation to see if the data made it there.
As for concurrent testing, we have similar issue and we through a slightly more complicated setup on the test data XML files. The ids used in the test data XML file are offset by each person/system running them by an id assigned to them. We preprocess the file with Velocity so my ID might be 1000, but continuous integration machine would have an ID of 3000 ( for example ). Fortunately only a small fraction of our tables are shared.
If you can, you can also assign different databases or schemas - which we also do. This is where the majority of our tables live so we have assigned everyone their own database / schema.
Below is my version of their dbunitdatabase class. Hope it helps. It lets you set a schema name, read properties files for connection information, read a comma separated list of xml datafiles so you dont have to repeat the test data each time.
Code:
package your.package.name.here;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.Properties;
import org.dbunit.DatabaseTestCase;
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.dataset.CompositeDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.operation.DatabaseOperation;
/**
* Base class that offers some convenience methods to assist in the setup of a
* DBUnit test case.
*
* The implementation for this class was borrorwed heavily from the class shown
* in 'Hibernate Quickly', Manning Press, Patrick Peak and Nick Heudecker.
*
* This class assumes there is a properties file called db.properties that
* has the following properties defined:
* db.connection.driver
* db.connection.url
* db.connection.username
* db.connection.password
* These can all be overridden in any extended class.
* It also assumes that the database schema DTD is in a file called:
* database-schema.dtd.
* Again this can be overridden in the extended class.
*
* All of these files are assumed to be on the classpath.
*
* @author pryan
*
*/
public abstract class DBUnitDatabaseTestCase extends DatabaseTestCase {
/**
* Attributes that define the properties that are needed
* to create connections to the database and the name of the
* propery file that contains properties.
*
* Any test case that extends this class can override these values.
*/
protected static String driver = "db.connection.driver";
protected static String url = "db.connection.url";
protected static String username = "db.connection.username";
protected static String password = "db.connection.password";
protected static String schema = null;
protected static String db_property_filename = "db.properties";
protected static String xml_schema_file = "database-schema.dtd";
/**
* schemaName will be used, if set to specify the schema that
* dbunit will use for its dataset operations.
* Subclasses can directly set this value to override the value
* in the datasource.oracle.schema property. This is most often
* required to test the dpr_system schema.
*/
protected String schemaName;
/**
* Collect all of the connections created so we can close them when in
* TearDown.
*/
private ArrayList<IDatabaseConnection> connectionsToClose =
new ArrayList<IDatabaseConnection>();
protected InputStream loadFromClasspath(String s ) throws Exception {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
InputStream is = cl.getResourceAsStream(s);
if( is == null ) {
throw new Exception("Could not locate the resource: " + s +
" on the classpath");
}
return is;
}
public DBUnitDatabaseTestCase(String name) {
super(name);
}
/**
* Must be implemented by concrete test cases and should return the filename
* of the dataset used to load the data for the set of tests.
* @return
*/
protected abstract String getDataSetFilename();
/**
* Get a connection to the database which is defined in the properties
* file. This method will also install the CustomDataTypeFactory which
* currently installs a more robust Date, Timestamp format capability
* else you are forced to use the default timestamp of the form:
* yyyy-MM-dd HH:mm:ss.
* @see CustomDataTypeFactory
* @see CustomTimestampDataType
*/
@Override
protected IDatabaseConnection getConnection() throws Exception {
Class.forName(getProperty(driver));
Connection c = DriverManager.getConnection(getProperty(url),
getProperty(username), getProperty(password));
// Allow for the schemaName to be directly set by JUnit tests
// that may need to override the schema setup in the properties file
// for certain specific tests.
IDatabaseConnection idc = null;
if( schemaName == null && schema == null ) {
// then they dont care about a schema and just get a regular connection
idc = new DatabaseConnection(c);
} else if( schemaName == null && schema != null ) {
// then they want the properties file to be read use that schema
schemaName = getProperty(schema);
idc = new DatabaseConnection(c,schemaName);
} else if( schemaName != null ) {
// then the schemaName is set and we should ignore the properties file
// or we used the properties file once so just continue to use that schema name
idc = new DatabaseConnection(c,schemaName);
}
// keep reference to connection so we can cleanly close the connection
// with the test case is torn down.
connectionsToClose.add(idc);
return idc;
}
/**
* Helper method to read the dataset and typically called by the
* DBUnit plumbing.
*/
@Override
protected IDataSet getDataSet() throws Exception {
String file = getDataSetFilename();
if( file == null ) {
return null;
}
else {
IDataSet ds = null;
String[] fileNames = file.split(",");
for (String fileName : fileNames) {
IDataSet subset = new FlatXmlDataSet(
loadFromClasspath(fileName));
ds = (ds == null)?subset:new CompositeDataSet(ds, subset);
}
return ds;
}
}
/**
* @return REFRESH DatabaseOperation. Refreshes the database to match the
* dataset.
*/
@Override
protected DatabaseOperation getSetUpOperation() throws Exception {
return DatabaseOperation.REFRESH;
}
/**
* @return DELETE_ALL DatabaseOperation. Removes, truncates all data in
* tables specified in the database test xml file.
*/
@Override
protected DatabaseOperation getTearDownOperation() throws Exception {
return DatabaseOperation.DELETE;
}
/**
* Closes all of the connections obtained via that getConnection method.
*/
@Override
protected void tearDown() throws Exception {
super.tearDown();
for (IDatabaseConnection conn : connectionsToClose) {
conn.close();
}
}
/**
* generally useful method to get a guid
* @return
*/
protected String getGUID() {
String guid = new java.rmi.dgc.VMID().toString();
return guid;
}
/**
* Returns property value from config file.
*
* @param key property key
* @return property value from config file
* @throws Exception if error occurs during property retrieval process
*/
protected String getProperty(String key) throws Exception {
if (_props == null) {
_props = new Properties();
_props.load(loadFromClasspath(db_property_filename));
}
return _props.getProperty(key);
}
private Properties _props = null;
}