以前一直用mysql做測試數(shù)據(jù)庫,多人協(xié)作起來每個人都要安裝配置數(shù)據(jù)庫,數(shù)據(jù)源,還得防著不能把自己的jdbc.properties傳上去把別人搞暈掉,現(xiàn)在改成輕便的嵌入式數(shù)據(jù)庫hsqldb,麻煩事少了很多。使用hsqldb作為測試數(shù)據(jù)庫,涉及到兩個問題,一個是Web應(yīng)用啟動關(guān)閉的時候要同時啟動和關(guān)閉hsqldb server,另外一個就是在執(zhí)行單元測試的時候也要啟動和關(guān)閉hsqldb server.

hsqldb有幾種啟動方式,其中有res,mem等方式數(shù)據(jù)庫結(jié)構(gòu)都是只讀的,hibernate hbm2ddl會報The database is in read only mode in statement [create]。所以最終還是決定使用獨(dú)立的server模式。但是需要自己控制啟動和關(guān)閉。

1.讓Hsqldb隨Web應(yīng)用啟動和關(guān)閉

這個實際上就是一個listener。代碼如下:

/**
 * 該類的職責(zé)是在WebApp啟動時自動開啟HSQL服務(wù).
 * 依然使用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里面的相應(yīng)類,把從ServletContext中獲取參數(shù)的過程全部替換成了Config.getString(),這個Config是靜態(tài)導(dǎo)入的一個Configuration對象,使用了Apache commons Configuration,讀取的配置文件如下:

metawork.hsql.dbPath={classpath}hsqldb
metawork.hsql.dbName
=mwdb
metawork.hsql.port
=9002

其中指定了數(shù)據(jù)庫文件位置、數(shù)據(jù)庫名稱、服務(wù)端口。使用種方式來配置啟動hsqldb,主要是為了后面的單元測試的情況能夠和這里使用相同的一套配置。注意我把數(shù)據(jù)庫文件放在了${app.home}/WEB-INF/classes/hsqldb/目錄。

如上配置,在系統(tǒng)的jdbc.properties里面就可以這樣寫了:


jdbc.driverClassName
=org.hsqldb.jdbcDriver
jdbc.url
=jdbc:hsqldb:hsql://localhost:9002/mwdb
jdbc.username
=sa
jdbc.password
=


2.單元測試

上面的HsqlListener控制了在web應(yīng)用啟動的時候啟動hsqldb,web應(yīng)用停止的時候停止hsqldb,但是我們在做單元測試的時候,是沒有啟動Web應(yīng)用的,于是就有可能導(dǎo)致無法連接到數(shù)據(jù)庫。解決的方法是在單元測試運(yùn)行開始之前,啟動數(shù)據(jù)庫。寫單元測試基類如下:

@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.");
   }
}

上面的測試基類里面用靜態(tài)初始化快初始化啟動了hsqldb,并最后在onTearDown的時候關(guān)閉數(shù)據(jù)庫。這個測試基類繼承了Spring的測試類AbstractTransactionalDataSourceSpringContextTests,如果你使用原始的junit TestCase,那么直接在tearDown方法中關(guān)閉數(shù)據(jù)庫就好了。

3.數(shù)據(jù)庫圖形化訪問

用慣了mysql的mysql query browser,總是想著看數(shù)據(jù)庫里面的內(nèi)容,怎么辦呢。你的系統(tǒng)啟動起來以后,在shell里面執(zhí)行如下命令:

"%JAVA_HOME%/bin/javaw" -classpath ../webapp/WEB-INF/lib/hsqldb-1.8.0.7.jar org.hsqldb.util.DatabaseManager

要注意把其中的hsqldb-xxx.jar的地址改成你自己的地址。啟動之后按照你配置的數(shù)據(jù)庫端口,名稱等等去連接就可以了。