以前一直用mysql做測試數據庫,多人協作起來每個人都要安裝配置數據庫,數據源,還得防著不能把自己的jdbc.properties傳上去把別人搞暈掉,現在改成輕便的嵌入式數據庫hsqldb,麻煩事少了很多。使用hsqldb作為測試數據庫,涉及到兩個問題,一個是Web應用啟動關閉的時候要同時啟動和關閉hsqldb server,另外一個就是在執行單元測試的時候也要啟動和關閉hsqldb server.
hsqldb有幾種啟動方式,其中有res,mem等方式數據庫結構都是只讀的,hibernate hbm2ddl會報The database is in read only mode in statement [create]。所以最終還是決定使用獨立的server模式。但是需要自己控制啟動和關閉。
1.讓Hsqldb隨Web應用啟動和關閉
這個實際上就是一個listener。代碼如下:
/**
* 該類的職責是在WebApp啟動時自動開啟HSQL服務.
* 依然使用Server方式,不受AppServer的影響.
*
* @author frank
* @author calvin
*/
public class HsqlListener implements ServletContextListener {
protected static Log logger = LogFactory.getLog(HsqlListener.class);
private final static String DEFAULT_DB_PATH = "{user.home}/springside/db";
public void contextInitialized(ServletContextEvent sce) {
logger.info("HsqlListener initialize
");
String dbName = Config.getString("metawork.hsql.dbName");
int port = -1;
try {
port = Integer.parseInt(Config.getString("metawork.hsql.port"));
}
catch (Exception e) {
}
if (StringUtils.isEmpty(dbName)) {
logger.error("Cant' get hsqldb.dbName from web.xml Context Param");
return;
}
String path = null;
try {
path = getDbPath(sce);
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
File dbDir = new File(path);
if (!dbDir.exists()) {
logger.info("Create Path:" + path);
if (!dbDir.mkdirs()) {
logger.error("Can not create DB Dir for Hsql:" + dbDir);
return;
}
}
if (!path.endsWith("/"))
path = path + "/";
startServer(path, dbName, port);
}
private String getDbPath(ServletContextEvent sce) throws URISyntaxException {
String path = Config.getString("metawork.hsql.dbPath");
if (StringUtils.isEmpty(path)) {
path = DEFAULT_DB_PATH;
}
if (path.startsWith("{user.home}")) {
path = path.replaceFirst("\\{user.home\\}", System.getProperty(
"user.home").replace('\\', '/'));
}
if (path.startsWith("{webapp.root}")) {
path = path.replaceFirst("\\{webapp.root\\}", sce
.getServletContext().getRealPath("/").replace('\\', '/'));
}
if (path.startsWith("{classpath}")) {
path = path.replaceFirst("\\{classpath\\}", this.getClass().getClassLoader().getResource("").toURI().toString().replace("file:/", ""));
}
return path;
}
private void startServer(String dbPath, String dbName, int port) {
Server server = new Server();
server.setDatabaseName(0, dbName);
server.setDatabasePath(0, dbPath + dbName);
if (port != -1)
server.setPort(port);
server.setSilent(true);
server.start();
logger.info("hsqldb started
");
// 等待Server啟動
try {
Thread.sleep(800);
}
catch (InterruptedException e) {
// do nothing
}
}
public void contextDestroyed(ServletContextEvent sce) {
String dbName = Config.getString("metawork.hsql.dbName");
Connection conn = null;
try {
Class.forName("org.hsqldb.jdbcDriver");
conn = DriverManager.getConnection("jdbc:hsqldb:hsql://localhost:" + Config.getString("metawork.hsql.port") + "/" + dbName,
"sa", "");
Statement stmt = conn.createStatement();
stmt.executeUpdate("SHUTDOWN;");
} catch (Exception e) {
//do nothing
}
}
}
這個類修改自SpringSide1.0M3里面的相應類,把從ServletContext中獲取參數的過程全部替換成了Config.getString(),這個Config是靜態導入的一個Configuration對象,使用了Apache commons Configuration,讀取的配置文件如下:
metawork.hsql.dbPath={classpath}hsqldb
metawork.hsql.dbName=mwdb
metawork.hsql.port=9002
其中指定了數據庫文件位置、數據庫名稱、服務端口。使用種方式來配置啟動hsqldb,主要是為了后面的單元測試的情況能夠和這里使用相同的一套配置。注意我把數據庫文件放在了${app.home}/WEB-INF/classes/hsqldb/目錄。
如上配置,在系統的jdbc.properties里面就可以這樣寫了:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://localhost:9002/mwdb
jdbc.username=sa
jdbc.password=
2.單元測試
上面的HsqlListener控制了在web應用啟動的時候啟動hsqldb,web應用停止的時候停止hsqldb,但是我們在做單元測試的時候,是沒有啟動Web應用的,于是就有可能導致無法連接到數據庫。解決的方法是在單元測試運行開始之前,啟動數據庫。寫單元測試基類如下:
@ContextConfiguration
public class BaseDaoTestCase extends AbstractTransactionalDataSourceSpringContextTests{
private static final Logger logger = Logger.getLogger(BaseDaoTestCase.class);
static {
logger.info("Start up hsqldb
");
String dbName = Config.getString("metawork.hsql.dbName");
int port = -1;
port = Integer.parseInt(Config.getString("metawork.hsql.port"));
String path = null;
path = getDbPath();
File dbDir = new File(path);
if (!dbDir.exists()) {
dbDir.mkdirs();
}
if (!path.endsWith("/"))
path = path + "/";
startServer(path, dbName, port);
logger.info("Hsqldb started successfully.");
}
@Override
protected String[] getConfigLocations() {
return new String[] { "classpath*:/context/*.xml" };
}
public void testX(){
}
private static String getDbPath(){
String path = Config.getString("metawork.hsql.dbPath");
if (path.startsWith("{classpath}")) {
try {
path = path.replaceFirst("\\{classpath\\}", BaseDaoTestCase.class.getClassLoader().getResource("").toURI().toString().replace("file:/", ""));
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return path;
}
private static void startServer(String dbPath, String dbName, int port) {
Server server = new Server();
server.setDatabaseName(0, dbName);
server.setDatabasePath(0, dbPath + dbName);
if (port != -1)
server.setPort(port);
server.setSilent(true);
server.start();
// 等待Server啟動
try {
Thread.sleep(800);
}
catch (InterruptedException e) {
// do nothing
}
}
@Override
public void onTearDown(){
logger.info("Shutdown hsqldb
");
String dbName = Config.getString("metawork.hsql.dbName");
Connection conn = null;
try {
Class.forName("org.hsqldb.jdbcDriver");
conn = DriverManager.getConnection("jdbc:hsqldb:hsql://localhost:" + Config.getString("metawork.hsql.port") + "/" + dbName,
"sa", "");
Statement stmt = conn.createStatement();
stmt.executeUpdate("SHUTDOWN;");
} catch (Exception e) {
//do nothing
}
logger.info("Shutdown hsqldb successfully.");
}
}
上面的測試基類里面用靜態初始化快初始化啟動了hsqldb,并最后在onTearDown的時候關閉數據庫。這個測試基類繼承了Spring的測試類
AbstractTransactionalDataSourceSpringContextTests,如果你使用原始的junit TestCase,那么直接在tearDown方法中關閉數據庫就好了。
3.數據庫圖形化訪問
用慣了mysql的mysql query browser,總是想著看數據庫里面的內容,怎么辦呢。你的系統啟動起來以后,在shell里面執行如下命令:
"%JAVA_HOME%/bin/javaw" -classpath ../webapp/WEB-INF/lib/hsqldb-1.8.0.7.jar org.hsqldb.util.DatabaseManager
要注意把其中的hsqldb-xxx.jar的地址改成你自己的地址。啟動之后按照你配置的數據庫端口,名稱等等去連接就可以了。