MicroFish

          Open & Open hits
          隨筆 - 33, 文章 - 2, 評(píng)論 - 4, 引用 - 0
          數(shù)據(jù)加載中……

          2007年3月8日

          HSQL 學(xué)習(xí)筆記1(zz)

          http://hi.baidu.com/wannachan/blog/item/8e82bf86fc5d663f67096ef1.html

          HSQL 學(xué)習(xí)筆記

          1.???? hsql 學(xué)習(xí)
          1.1.???? 學(xué)習(xí)目的
          本文檔是針對(duì)hSQL 數(shù)據(jù)庫方面的基礎(chǔ)學(xué)習(xí),為了使項(xiàng)目組成員能夠達(dá)到使用hSQL 數(shù)據(jù)庫的目的。
          1.2.???? 培訓(xùn)對(duì)象
          開發(fā)人員
          1.3.???? 常用詞及符號(hào)說明
          常用詞:
          hsql:一種免費(fèi)的跨平臺(tái)的數(shù)據(jù)庫系統(tǒng)
          E:\hsqldb:表示是在dos 命令窗口下面
          1.4.???? 參考信息
          doc\guide\guide.pdf

          2.???? HSQL
          2.1.???? HSQL 運(yùn)行工具
          java -cp ../lib/hsqldb.jar org.hsqldb.util.DatabaseManager
          注意hsqldb.jar 文件的文件路徑,最好能放到classpath 里面,或者放到當(dāng)前路徑下.
          java -cp hsqldb.jar org.hsqldb.util.DatabaseManager

          2.2.???? 運(yùn)行數(shù)據(jù)庫
          啟動(dòng)方式: Server Modes and
          In-Process Mode (also called Standalone Mode).

          一個(gè)test 數(shù)據(jù)庫會(huì)包含如下文件:
          ? test.properties
          ? test.script
          ? test.log
          ? test.data
          ? test.backup
          test.properties 文件包含關(guān)于數(shù)據(jù)庫的一般設(shè)置.
          test.script?? 文件包含表和其它數(shù)據(jù)庫,插入沒有緩存表的數(shù)據(jù).
          test.log 文件包含當(dāng)前數(shù)據(jù)庫的變更.
          test.data 文件包含緩存表的數(shù)據(jù)
          test.backup 文件是最近持久化狀態(tài)的表的數(shù)據(jù)文件的壓縮備份文件
          所有以上這個(gè)文件都是必要的,不能被刪除.如果數(shù)據(jù)庫沒有緩存表,test.data 和test.backup 文件將不會(huì)存在.另外,除了以上文件HSQLDB 數(shù)據(jù)庫可以鏈接到任何文本文件,比如cvs 文件.

          當(dāng)操作test 數(shù)據(jù)庫的時(shí)候, test.log 用于保存數(shù)據(jù)的變更. 當(dāng)正常SHUTDOWN,這個(gè)文件將被刪除. 否則(不是正常shutdown),這個(gè)文件將用于再次啟動(dòng)的時(shí)候,重做這些變更.test.lck 文件也用于記錄打開的數(shù)據(jù)庫的事實(shí), 正常SHUTDOWN,文件也被刪除.在一些情況下,test.data.old 文件會(huì)被創(chuàng)建,并刪除以前的.






          2.3.???? Server Mode
          java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 xdb

          命令行方式:


          啟動(dòng)數(shù)據(jù),數(shù)據(jù)庫文件mydb,數(shù)據(jù)庫名稱xdb

          也可以在 server.properties 文件中定義啟動(dòng)的數(shù)據(jù)庫,最多10個(gè)
          例如: server.properties:
          server.database.0=file:E:/hsqldb/data/mydb
          server.dbname.0=xdb

          server.database.1=file:E:/hsqldb/data/testdb
          server.dbname.1=testdb

          server.database.2=mem:adatabase
          server.dbname.2=quickdb
          啟動(dòng)命令: java -cp ../lib/hsqldb.jar org.hsqldb.Server
          運(yùn)行結(jié)果如下



          java 測(cè)試程序:
          package test;
          import junit.framework.TestCase;
          import java.sql.Connection;
          import java.sql.DriverManager;
          import java.sql.ResultSet;
          import java.sql.SQLException;
          import java.sql.Statement;

          public class TestConnect extends TestCase {
          ???? Connection connection;
          ???? protected void setUp()
          ???? {????????
          ???????? try {
          ???????????? Class.forName("org.hsqldb.jdbcDriver" );
          ???????????? connection = DriverManager.getConnection("jdbc:hsqldb:hsql://localhost/xdb","sa","");
          ????????????
          ????????????
          ???????? } catch (Exception e) {
          ???????????? // TODO Auto-generated catch block
          ???????????? e.printStackTrace();
          ???????? }
          ???? }
          ???? public void testselect()
          ???? {
          ???????? Statement stmt=null;
          ???????? ResultSet rs=null;
          ???????? try {
          ???????????? stmt = connection.createStatement();
          ???????????? String sql ="select * from test";
          ???????????? rs=stmt.executeQuery( sql);
          ???????????? while(rs.next() )
          ???????????? {
          ???????????????? System.out.println("id="+rs.getString("id"));
          ???????????????? System.out.println("name="+rs.getString("name"));
          ???????????? }
          ????????????
          ???????? } catch (SQLException e) {
          ???????????? // TODO Auto-generated catch block
          ???????????? e.printStackTrace();
          ???????? }
          ???????? finally
          ???????? {
          ???????????? try {
          ???????????????? rs.close() ;
          ???????????????? stmt.close();
          ???????????? } catch (SQLException e) {
          ???????????????? // TODO Auto-generated catch block
          ???????????????? e.printStackTrace();
          ???????????? }????????????
          ???????? }????
          ????????
          ???? }
          ???? protected void tearDown()
          ???? {
          ???????? try {
          ???????????? connection.close();
          ???????? } catch (Exception e) {
          ???????????? // TODO Auto-generated catch block
          ???????????? e.printStackTrace();
          ???????? }
          ???? }

          }
          以上在eclipse 中測(cè)試通過.

          2.4.???? In-Process (Standalone) Mode
          不需要啟動(dòng)server
          connection = DriverManager.getConnection("jdbc:hsqldb:file:E:/hsqldb/data/mydb","sa","");
          這樣就可以連接數(shù)據(jù)庫。
          只能在一個(gè)jvm 中使用,不能在多個(gè)jvm 中使用。
          這種模式是在相同的jvm 下作為你的應(yīng)用程序的一部分,運(yùn)行數(shù)據(jù)庫引擎。對(duì)大多數(shù)應(yīng)用程序,這種模式運(yùn)行會(huì)相當(dāng)快,作為數(shù)據(jù),不需要轉(zhuǎn)換和網(wǎng)絡(luò)傳輸。

          主要的缺點(diǎn)就是不可能從外面的應(yīng)用程序訪問到默認(rèn)數(shù)據(jù)庫,因此當(dāng)你的應(yīng)用運(yùn)行時(shí)候,你不能通過別的工具檢查數(shù)據(jù)庫內(nèi)容。在1.8.0 版本中,你可以在相同jvm 中的線程中運(yùn)行數(shù)據(jù)庫初始化,并提供外面訪問你的進(jìn)程內(nèi)數(shù)據(jù)庫。
          ???? 推薦在開發(fā)應(yīng)用中使用這種方式。
          連接串:
          Windows: DriverManager.getConnection("jdbc:hsqldb:file:E:/hsqldb/data/mydb","sa","");
          Unix: DriverManager.getConnection("jdbc:hsqldb:file:/opt/db/testdb","sa","");

          2.5.???? Memory-Only Databases
          當(dāng)隨即訪問內(nèi)存,數(shù)據(jù)庫不固定時(shí),可以采用內(nèi)存的方式運(yùn)行數(shù)據(jù)庫,由于沒有數(shù)據(jù)寫到硬盤上,這種方式使用在應(yīng)用數(shù)據(jù)和applets 和特殊應(yīng)用的內(nèi)部進(jìn)程中使用,URL:

          Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:aname", "sa", "");
          2.6.???? Using Multiple Databases in One JVM
          2.7.???? Different Types of Tables
          HSQLDB 支持 TEMP 表和三種類型的持久表(MEMORY 表, CACHED 表,TEXT表)

          當(dāng)使用 CREATE TABLE?? 命令時(shí),Memory 表時(shí)默認(rèn)類型,它們的數(shù)據(jù)整體保存在內(nèi)存當(dāng)中,但是任何改變它們的結(jié)構(gòu)或者內(nèi)容,它們會(huì)被寫到<dbname>.script 文件中。這個(gè)腳本文件在數(shù)據(jù)庫下一次打開的時(shí)候被對(duì)出,內(nèi)存表重新被創(chuàng)建內(nèi)容,根temp 表不同,內(nèi)存表時(shí)持久化的。

          CACHED 表通過CREATE CACHED TABLE 命令建立. 只有部分的它們的數(shù)據(jù)或者索引被保存在內(nèi)存中,允許大表占用幾百兆的內(nèi)存空間。例外一個(gè)優(yōu)點(diǎn),在數(shù)據(jù)庫引擎中,啟動(dòng)大量數(shù)據(jù)的緩存表需要花費(fèi)少量的時(shí)間,缺點(diǎn)是減慢了運(yùn)行和使用Hsqldb 的速度。表相對(duì)小的時(shí)候,不要使用cache 表,在小表中使用內(nèi)存數(shù)據(jù)庫。

          從版本 1.7.0 以后,支持text 表,使用 CSV (Comma Separated Value)?? 或者其它分隔符文本文件作為它們的數(shù)據(jù)源。你可以特殊指定一個(gè)存在的CSV 文件,例如從其它的數(shù)據(jù)或者程序中導(dǎo)出文件,作為TXT 表的數(shù)據(jù)源。 同時(shí),你可以指定一個(gè)空文件,通過數(shù)據(jù)庫引擎填充數(shù)據(jù)。TEXT 表將比cache 表更加效率高。Text 表可以指向不同的數(shù)據(jù)文件。

          * memory-only databases 數(shù)據(jù)庫只支持memory 表和cache 表,不支持text 表。
          2.8.???? 約束和索引
          HSQLDB 支持 PRIMARY KEY, NOT NULL, UNIQUE, CHECK and FOREIGN KEY 約束.





          3.???? sql 命令
          3.1.???? sql 支持
          select top 1 * from test;
          select limit 0 2 * from test;
          DROP TABLE test IF EXISTS;
          3.2.???? Constraints and Indexes
          主健約束:PRIMARY KEY
          唯一約束:
          唯一索引:
          外?。?br />CREATE TABLE child(c1 INTEGER, c2 VARCHAR, FOREIGN KEY (c1, c2) REFERENCES parent(p1, p2));

          3.3.???? 索引和查詢速度
          索引提高查詢速度,比提高排序速度。
          主健和唯一所列自動(dòng)創(chuàng)建索引,否則需要自己創(chuàng)建CREATE INDEX command。
          索引: 唯一索引和非唯一索引
          多列的索引,如果只是使用后面的,不使用第一個(gè),將不會(huì)條查詢速度。

          (TB is a very large table with only a few rows where TB.COL3 = 4)
          SELECT * FROM TA JOIN TB ON TA.COL1 = TB.COL2 AND TB.COL3 = 4;
          SELECT * FROM TB JOIN TA ON TA.COL1 = TB.COL2 AND TB.COL3 = 4;(faster)

          原因是 TB.COL3 可以被快速的估計(jì),如果TB 表放到前面(index on TB.COL3):
          一般規(guī)則是把縮小條件的列的表放在前面

          3.4.???? 使用where 還是join
          使用 WHERE?? 條件鏈接表可能會(huì)降低運(yùn)行速度.
          下面的例子將會(huì)比較慢,即使使用了索引:
          ???? SELECT ... FROM TA, TB, TC WHERE TC.COL3 = TA.COL1 AND TC.COL3=TB.COL2 AND TC.COL4 = 1
          這個(gè)查詢隱含TA.COL1 = TB.COL2 ,但是沒有直接設(shè)定這個(gè)條件.如果 TA 和 TB 每個(gè)表都包含100 條記錄,10000 組合將和 TC 關(guān)聯(lián),用于TC這個(gè)列的條件,盡管有索引在這個(gè)列上.使用JOIN 關(guān)鍵字, 在組合TC 之前,TA.COL1 = TB.COL2 條件直接并縮小組合 TA 和 TB 的行數(shù), 在運(yùn)行大數(shù)據(jù)量的表的結(jié)果是,將會(huì)很快:
          ???? SELECT ... FROM TA JOIN TB ON TA.COL1 = TB.COL2 JOIN TC ON TB.COL2 = TC.COL3 WHERE TC.COL4 = 1
          這個(gè)查詢可以提高一大步,如果改變表的順序, 所以 TC.COL1 = 1 將最先使用,這樣更小的集合將組合在一起:
          ???? SELECT ... FROM TC JOIN TB ON TC.COL3 = TB.COL2 JOIN TA ON TC.COL3 = TA.COL1 WHERE TC.COL4 = 1
          以上例子,數(shù)據(jù)引擎自動(dòng)應(yīng)用于TC.COL4 = 1 組合小的集合于其它表關(guān)聯(lián). Indexes TC.COL4, TB.COL2?? TA.COL1 都將使用索引,提高查詢速度.
          3.5.???? Subqueries and Joins
          使用join 和調(diào)整表的順序提高效率.
          例如:, 第二個(gè)查詢的速度將更快一些(TA.COL1 和TB.COL3都有索引):
          Example 2.2. Query comparison
          ???? SELECT ... FROM TA WHERE TA.COL1 = (SELECT MAX(TB.COL2) FROM TB WHERE TB.COL3 = 4)

          ???? SELECT ... FROM (SELECT MAX(TB.COL2) C1 FROM TB WHERE TB.COL3 = 4) T2 JOIN TA ON TA.COL1 = T2.C1
          第二個(gè)查詢將 MAX(TB.COL2) 與一個(gè)單記錄表相關(guān)聯(lián). 并使用TA.COL1索引,這將變得非??? 第一個(gè)查詢是將 TA 表中的每一條記錄不斷地與MAX(TB.COL2)匹配.
          3.6.???? 數(shù)據(jù)類型
          TINYINT, SMALLINT, INTEGER, BIGINT, NUMERIC and DECIMAL (without a decimal point) are supported integral types and map to byte, short, int, long and BigDecimal in Java.

          Integral Types:
          TINYINT, SMALLINT, INTEGER, BIGINT, NUMERIC and DECIMAL
          Other Numeric Types:
          REAL, FLOAT or DOUBLE
          Bit and Boolean Types:
          ???? BOOLEAN: UNDEFINED,TRUE,FALSE??
          NULL values are treated as undefined.
          Storage and Handling of Java Objects
          Sequences and Identity

          Identity Auto-Increment Columns:
          The next IDENTITY value to be used can be set with the
          ALTER TABLE ALTER COLUMN <column name> RESTART WITH <new value>;
          Sequences:
          SELECT NEXT VALUE FOR mysequence, col1, col2 FROM mytable WHERE ...
          ????
          3.7.???? 事務(wù)問題:
          SET PROPERTY "sql.tx_no_multi_rewrite" TRUE

          4.???? Connections
          通用驅(qū)動(dòng)jdbc:hsqldb:?? 下列協(xié)議標(biāo)識(shí)(mem: file: res: hsql: http: hsqls: https:)
          Table 4.1. Hsqldb URL Components
          Driver and Protocol???? Host and Port???? Database
          jdbc:hsqldb:mem:
          ???? not available???? accounts

          jdbc:hsqldb:mem:.
          jdbc:hsqldb:file:
          ???? not available???? mydb
          /opt/db/accounts
          C:/data/mydb

          數(shù)據(jù)庫路徑.
          jdbc:hsqldb:res:
          ???? not available???? /adirectory/dbname

          jars files are accessed in Java programs. The /adirectory above stands for a directory in one of the jars.
          jdbc:hsqldb:hsql:
          jdbc:hsqldb:hsqls:
          jdbc:hsqldb:http:
          jdbc:hsqldb:https:
          ???? //localhost
          //192.0.0.10:9500
          //dbserver.somedomain.com
          ???? /an_alias
          /enrollments
          /quickdb

          別名在server.properties or webserver.properties文件中指定
          ???? database.0=file:/opt/db/accounts
          ???? dbname.0=an_alias

          ???? database.1=file:/opt/db/mydb
          ???? dbname.1=enrollments

          ???? database.2=mem:adatabase
          ???? dbname.2=quickdb
          In the example below, the database files lists.* in the /home/dbmaster/ directory are associated with the empty alias:
          ???? database.3=/home/dbmaster/lists
          ???? dbname.3=
          4.1.???? Connection properties
          Connection properties are specified either by establishing the connection via the:
          ???? DriverManager.getConnection (String url, Properties info);
          method call, or the property can be appended to the full Connection URL.
          Table 4.2. Connection Properties
          get_column_name???? true???? column name in ResultSet
          This property is used for compatibility with other JDBC driver implementations. When true (the default), ResultSet.getColumnName(int c) returns the underlying column name
          When false, the above method returns the same value as ResultSet.getColumnLabel(int column) Example below:
          ???? jdbc:hsqldb:hsql://localhost/enrollments;get_column_name=false
          ????????????????????
          When a ResultSet is used inside a user-defined stored procedure, the default, true, is always used for this property.
          ifexists???? false???? connect only if database already exists
          Has an effect only with mem: and file: database. When true, will not create a new database if one does not already exist for the URL.
          When false (the default), a new mem: or file: database will be created if it does not exist.
          Setting the property to true is useful when troubleshooting as no database is created if the URL is malformed. Example below:
          ???? jdbc:hsqldb:file:enrollments;ifexists=true
          shutdown???? false???? shut down the database when the last connection is closed
          This mimics the behaviour of 1.7.1 and older versions. When the last connection to a database is closed, the database is automatically shut down. The property takes effect only when the first connection is made to the database. This means the connection that opens the database. It has no effect if used with subsequent, simultaneous connections.
          This command has two uses. One is for test suites, where connections to the database are made from one JVM context, immediately followed by another context. The other use is for applications where it is not easy to configure the environment to shutdown the database. Examples reported by users include web application servers, where the closing of the last connection conisides with the web app being shut down.


          4.2.???? Properties Files
          大小寫敏感 (e.g. server.silent=FALSE will have no effect, but server.silent=false will work).
          屬性文件和設(shè)定存儲(chǔ)如下 :
          Table 4.3. Hsqldb Server Properties Files
          File Name???? Location???? Function
          server.properties???? the directory where the command to run the Server class is issued???? settings for running HSQLDB as a database server communicating with the HSQL protocol
          webserver.properties???? the directory where the command to run the WebServer class is issued???? settings for running HSQLDB as a database server communicating with the HTTP protocol
          <dbname>.properties???? the directory where all the files for a database are located???? settings for each particular database
          Properties files for running the servers are not created automatically. You should create your own files that contain server.property=value pairs for each property.
          4.2.1.???? Server and Web Server Properties
          server.properties and webserver.properties 文件支持如下設(shè)定:
          Table 4.4. Property File Properties
          Value???? Default???? Description
          server.database.0???? test???? the path and file name of the first database file to use
          server.dbname.0???? ""???? lowercase server alias for the first database file
          server.urlid.0???? NONE???? SqlTool urlid used by UNIX init script. (This property is not used if your are running Server/Webserver on a platform other than UNIX, or of you are not using our UNIX init script).
          server.silent???? true???? no extensive messages displayed on console
          server.trace???? false???? JDBC trace messages displayed on console
          In 1.8.0, 每個(gè)服務(wù)器支持同時(shí)啟動(dòng)10個(gè)不同的數(shù)據(jù)庫. The server.database.0 property defines the filename / path whereas the server.dbname.0 defines the lowercase alias used by clients to connect to that database. The digit 0 is incremented for the second database and so on. Values for the server.database.{0-9} property can use the mem:, file: or res: prefixes and properties as discussed above under CONNECTIONS. For example,
          ???? database.0=mem:temp;sql.enforce_strict_size=true;
          Values specific to server.properties are:
          Table 4.5. Server Property File Properties
          Value???? Default???? Description
          server.port???? 9001???? TCP/IP port used for talking to clients. All databases are served on the same port.
          server.no_system_exit???? true???? no System.exit() call when the database is closed
          Values specific to webserver.properties are:
          Table 4.6. WebServer Property File Properties
          Value???? Default???? Description
          server.port???? 80???? TCP/IP port used for talking to clients
          server.default_page???? index.html???? the default web page for server
          server.root???? ./???? the location of served pages
          .<extension>???? ????? multiple entries such as .html=text/html define the mime types of the static files served by the web server. See the source for WebServer.java for a list.
          All the above values can be specified on the command line to start the server by omitting the server. prefix.
          5.???? SqlTool
          Mem 數(shù)據(jù)庫:
          E:\hsqldb>java -jar ./lib/hsqldb.jar mem
          Hsql Server:
          (前提是xdb server 已經(jīng)啟動(dòng)):
          (java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 xdb)
          java -jar ./hsqldb.jar xdb

          posted @ 2007-03-26 17:18 劉璐 閱讀(708) | 評(píng)論 (0)編輯 收藏

          (轉(zhuǎn))用DbUnit進(jìn)行SqlMap單元測(cè)試- -

          http://starrynight.blogdriver.com/starrynight/621943.html
          DbUnit簡介

          為依賴于其他外部系統(tǒng)(如數(shù)據(jù)庫或其他接口)的代碼編寫單元測(cè)試是一件很困難的工作。在這種情況下,有效的單元必須隔離測(cè)試對(duì)象和外部依賴,以便管理測(cè)試對(duì)象的狀態(tài)和行為。

          使用mock object對(duì)象,是隔離外部依賴的一個(gè)有效方法。如果我們的測(cè)試對(duì)象是依賴于DAO的代碼,mock object技術(shù)很方便。但如果測(cè)試對(duì)象變成了DAO本身,又如何進(jìn)行單元測(cè)試呢?

          開源的DbUnit項(xiàng)目,為以上的問題提供了一個(gè)相當(dāng)優(yōu)雅的解決方案。使用DbUnit,開發(fā)人員可以控制測(cè)試數(shù)據(jù)庫的狀態(tài)。進(jìn)行一個(gè)DAO單元測(cè)試之前,DbUnit為數(shù)據(jù)庫準(zhǔn)備好初始化數(shù)據(jù);而在測(cè)試結(jié)束時(shí),DbUnit會(huì)把數(shù)據(jù)庫狀態(tài)恢復(fù)到測(cè)試前的狀態(tài)。

          下面的例子使用DbUnit為iBATIS SqlMap的DAO編寫單元測(cè)試。

          準(zhǔn)備測(cè)試數(shù)據(jù)
          首先,要為單元測(cè)試準(zhǔn)備數(shù)據(jù)。使用DbUnit,我們可以用XML文件來準(zhǔn)備測(cè)試數(shù)據(jù)集。下面的XML文件稱為目標(biāo)數(shù)據(jù)庫的Seed File,代表目標(biāo)數(shù)據(jù)庫的表名和數(shù)據(jù),它為測(cè)試準(zhǔn)備了兩個(gè)Employee的數(shù)據(jù)。employee對(duì)應(yīng)數(shù)據(jù)庫的表名,employee_uid、start_date、first_name和last_name都是表employee的列名。

          <?xml version="1.0" encoding="GB2312"?>
          <dataset>
          ??? <employee employee_uid="0001"
          ??? ??? start_date="2001-01-01"
          ??? ??? first_name="liutao"
          ??? ??? last_name="liutao" />
          ???
          ??? <employee employee_uid="0002"
          ??? ??? start_date="2001-04-01"
          ??? ??? first_name="wangchuang"
          ??? ??? last_name="wangchuang" />
          </dataset>

          缺省情況下,DbUnit在單元測(cè)試開始之前刪除Seed File中所有表的數(shù)據(jù),然后導(dǎo)入Seed File的測(cè)試數(shù)據(jù)。在Seed File中不存在的表,DbUnit則不處理。
          Seed File可以手工編寫,也可以用程序?qū)С霈F(xiàn)有的數(shù)據(jù)庫數(shù)據(jù)并生成。

          SqlMap代碼
          我們要測(cè)試的SqlMap映射文件如下所示:
          <select id="queryEmployeeById" parameterClass="java.lang.String"
          ??? resultClass="domain.Employee">
          ??? select employee_uid as userId,
          ??? ??? start_date as startDate,
          ??? ??? first_name as firstName,
          ??? ??? last_name as lastName
          ??? from EMPLOYEE where employee_uid=#value#
          </select>
          <delete id="removeEmployeeById" parameterClass="java.lang.String">
          ??? delete from EMPLOYEE where employee_uid=#value#
          </delete>
          <update id="updateEmpoyee" parameterClass="domain.Employee">
          ??? update EMPLOYEE
          ??? set start_date=#startDate#,
          ??? first_name=#firstName#,
          ??? last_name=#lastName#
          ??? where employee_uid=#userId#
          </update>
          <insert id="insertEmployee" parameterClass="domain.Employee">
          ??? insert into employee (employee_uid,
          ??? ??? start_date, first_name, last_name)
          ??? ??? values (#userId#, #startDate#, #firstName#, #lastName#)
          </insert>

          編寫DbUnit TestCase
          為了方便測(cè)試,首先為SqlMap的單元測(cè)試編寫一個(gè)抽象的測(cè)試基類,代碼如下。

          public abstract class BaseSqlMapTest extends DatabaseTestCase {
          ??? protected static SqlMapClient sqlMap;

          ??? protected IDatabaseConnection getConnection() throws Exception {
          ??? ??? return new DatabaseConnection(getJdbcConnection());
          ??? }
          ??? protected void setUp() throws Exception {
          ??? ??? super.setUp();
          ??? ??? init();
          ??? }
          ??? protected void tearDown() throws Exception {
          ??? ??? super.tearDown();
          ??? ??? getConnection().close();
          ??? ??? if (sqlMap != null) {
          ??? ??? ??? DataSource ds = sqlMap.getDataSource();
          ??? ??? ??? Connection conn = ds.getConnection();
          ??? ??? ??? conn.close();
          ??? ??? }
          ??? }
          ??? protected void init() throws Exception {
          ??? ??? initSqlMap("sqlmap/SqlMapConfig.xml", null);
          ??? }
          ??? protected SqlMapClient getSqlMapClient() {
          ??? ??? return sqlMap;
          ??? }
          ??? protected void initSqlMap(String configFile, Properties props)
          ??? ??? ??? throws Exception {
          ??? ??? Reader reader = Resources.getResourceAsReader(configFile);
          ??? ??? sqlMap = SqlMapClientBuilder.buildSqlMapClient(reader, props);
          ??? ??? reader.close();
          ??? }
          ??? protected void initScript(String script) throws Exception {
          ??? ??? DataSource ds = sqlMap.getDataSource();
          ??? ??? Connection conn = ds.getConnection();
          ??? ???
          ??? ??? Reader reader = Resources.getResourceAsReader(script);
          ??? ??? ScriptRunner runner = new ScriptRunner();
          ??? ??? runner.setStopOnError(false);
          ??? ??? runner.setLogWriter(null);
          ??? ??? runner.setErrorLogWriter(null);

          ??? ??? runner.runScript(conn, reader);
          ??? ??? conn.commit();
          ??? ??? conn.close();
          ??? ??? reader.close();
          ??? }
          ??? private Connection getJdbcConnection() throws Exception {
          ??? ??? Properties props = new Properties();
          ??? ??? props.load(Resources.getResourceAsStream("sqlmap/SqlMapConfig.properties"));
          ??? ??? Class driver = Class.forName(props.getProperty("driver"));
          ??? ??? Connection conn = DriverManager.getConnection(props.getProperty("url"),
          ??? ??? ??? ??? props.getProperty("username"), props.getProperty("password"));
          ??? ??? return conn;
          ??? }
          }

          然后為每個(gè)SqlMap映射文件編寫一個(gè)測(cè)試用例,extends上面的抽象類。如編寫Employ.xml的測(cè)試用例如下,它覆蓋了DbUnit的DatabaseTestCase類的getDataSet方法。

          public class EmployeeDaoTest extends BaseSqlMapTest {
          ???
          ??? protected IDataSet getDataSet() throws Exception {
          ??? ??? Reader reader = Resources.getResourceAsReader("config/employee_seed.xml");
          ??? ??? return new FlatXmlDataSet(reader);
          ??? }
          ??? public void testQueryEmpoyeeById() throws Exception {
          ??? ??? String id = "0001";
          ??? ??? Employee emp = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
          ??? ??? assertNotNull(emp);
          ??? ??? assertEquals("0001", emp.getUserId());
          ??? ??? assertEquals("liutao", emp.getFirstName());
          ??? }
          ??? public void testRemoveEmployeeById() throws Exception {
          ??? ??? String id = "0001";
          ??? ??? int num = sqlMap.delete("removeEmployeeById", id);
          ??? ??? assertEquals(1, num);
          ??? ???
          ??? ??? // 注意這里, 確認(rèn)刪除不能使用SqlMap的查詢, 很奇怪!
          ??? ??? ITable table = getConnection().createQueryTable("removed",
          ??? ??? ??? ??? "select * from employee where employee_uid='0001'");
          ??? ??? assertEquals(0, table.getRowCount());
          ??? }
          ??? public void testUpdateEmployee() throws Exception {
          ??? ??? String id = "0002";
          ??? ??? Employee emp = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
          ??? ??? emp.setLastName("wch");
          ??? ??? sqlMap.update("updateEmpoyee", emp);
          ??? ???
          ??? ??? Employee emp1 = (Employee)sqlMap.queryForObject("queryEmployeeById", id);
          ??? ??? assertEquals("wch", emp1.getLastName());
          ??? }
          ??? public void testInsertEmployee() throws Exception {
          ??? ??? Employee emp = new Employee();
          ??? ??? emp.setUserId("0005");
          ??? ??? emp.setStartDate("2003-09-09");
          ??? ??? emp.setFirstName("macy");
          ??? ??? emp.setLastName("macy");
          ??? ??? sqlMap.insert("insertEmployee", emp);
          ??? ???
          ??? ??? Employee emp1 = (Employee)sqlMap.queryForObject("queryEmployeeById", "0005");
          ??? ??? assertEquals(emp.getFirstName(), emp1.getFirstName());
          ??? ??? assertEquals(emp.getStartDate(), emp1.getStartDate());
          ??? }
          }

          以上例子中的綠色代碼部分使用ITable接口來查詢已刪除的數(shù)據(jù)。因?yàn)槭褂肧qlMapClient.queryForObject方法查詢,已刪除的數(shù)據(jù)還存在,真奇怪(有時(shí)間再研究)。

          DbUnit的斷言
          我們可以使用DbUnit的Assertion類的方法來比較數(shù)據(jù)是否相同。

          public class Assertion {
          ??? public static void assertEquals(ITable expected, ITable actual)
          ??? public static void assertEquals(IDataSet expected, IDataSet actual)
          }

          DatabaseTestCase的getSetUpOperation和getTearDownOperation方法
          缺省情況下,DbUnit執(zhí)行每個(gè)測(cè)試前,都會(huì)執(zhí)行CLEAN_INSERT操作,刪除Seed File中所有表的數(shù)據(jù),并插入文件的測(cè)試數(shù)據(jù)。你可以通過覆蓋getSetUpOperation和getTearDownOperation方法改變setUp和tearDown的行為。

          protected DatabaseOperation getSetUpOperation() throws Exception {
          ??? return DatabaseOperation.REFRESH;
          }
          protected DatabaseOperation getTearDownOperation() throws Exception {
          ???
          return DatabaseOperation.NONE;
          }

          REFRESH操作執(zhí)行測(cè)試前并不執(zhí)行CLEAN操作,只是導(dǎo)入文件中的數(shù)據(jù),如果目標(biāo)數(shù)據(jù)庫數(shù)據(jù)已存在,DbUnit使用文件的數(shù)據(jù)來更新數(shù)據(jù)庫。

          使用Ant
          上面的方法通過extends DbUnit的DatabaseTestCase來控制數(shù)據(jù)庫的狀態(tài)。而
          使用DbUnit的Ant Task,完全可以通過Ant腳本的方式來實(shí)現(xiàn)。

          <taskdef name="dbunit" classname="org.dbunit.ant.DbUnitTask"/>
          <!-- 執(zhí)行set up 操作 -->
          <dbunit driver="org.hsqldb.jdbcDriver"
          ??????? url="jdbc:hsqldb:hsql://localhost/xdb"
          ??????? userid="sa" password="">
          ??? <operation type="INSERT" src="employee_seed.xml"/>
          </dbunit>
          <!-- run all tests in the source tree -->
          <junit printsummary="yes" haltonfailure="yes">
          ? <formatter type="xml"/>
          ? <batchtest fork="yes" todir="${reports.tests}">
          ??? <fileset dir="${src.tests}">
          ????? <include name="**/*Test*.java"/>
          ??? </fileset>
          ? </batchtest>
          </junit>
          <!-- 執(zhí)行tear down 操作 -->
          <dbunit driver="org.hsqldb.jdbcDriver"
          ??????? url="jdbc:hsqldb:hsql://localhost/xdb"
          ??????? userid="sa" password="">
          ??? <operation type="DELETE" src="employee_seed.xml"/>
          </dbunit>

          以上的Ant腳本把junit task放在DbUnit的Task中間,可以達(dá)到控制數(shù)據(jù)庫狀態(tài)的目標(biāo)。

          由此可知,DbUnit可以靈活控制目標(biāo)數(shù)據(jù)庫的測(cè)試狀態(tài),從而使編寫SqlMap單元測(cè)試變得更加輕松。

          本文抄襲了資源列表的“Effective Unit Test with DbUnit”,但重新編寫了代碼示例。

          網(wǎng)上資源

          1、DbUnit Framework

          2、Effective Unit Testing with DbUnit

          3、Control your test-environement with DbUnit and Anthill

          posted @ 2007-03-26 17:16 劉璐 閱讀(737) | 評(píng)論 (0)編輯 收藏

          抽象類和接口的區(qū)別

          abstract?class和interface是Java語言中對(duì)于抽象類定義進(jìn)行支持的兩種機(jī)制,正是由于這兩種機(jī)制的存在,才賦予了Java強(qiáng)大的面向?qū)ο竽芰?。abstract?class和interface之間在對(duì)于抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發(fā)者在進(jìn)行抽象類定義時(shí)對(duì)于abstract?class和interface的選擇顯得比較隨意。其實(shí),兩者之間還是有很大的區(qū)別的,對(duì)于它們的選擇甚至反映出對(duì)于問題領(lǐng)域本質(zhì)的理解、對(duì)于設(shè)計(jì)意圖的理解是否正確、合理。本文將對(duì)它們之間的區(qū)別進(jìn)行一番剖析,試圖給開發(fā)者提供一個(gè)在二者之間進(jìn)行選擇的依據(jù)。??

          理解抽象類??

          abstract?class和interface在Java語言中都是用來進(jìn)行抽象類(本文中的抽象類并非從abstract?class翻譯而來,它表示的是一個(gè)抽象體,而abstract?class為Java語言中用于定義抽象類的一種方法,請(qǐng)讀者注意區(qū)分)定義的,那么什么是抽象類,使用抽象類能為我們帶來什么好處呢???

          在面向?qū)ο蟮母拍钪?,我們知道所有的?duì)象都是通過類來描繪的,但是反過來卻不是這樣。并不是所有的類都是用來描繪對(duì)象的,如果一個(gè)類中沒有包含足夠的信息來描繪一個(gè)具體的對(duì)象,這樣的類就是抽象類。抽象類往往用來表征我們?cè)趯?duì)問題領(lǐng)域進(jìn)行分析、設(shè)計(jì)中得出的抽象概念,是對(duì)一系列看上去不同,但是本質(zhì)上相同的具體概念的抽象。比如:如果我們進(jìn)行一個(gè)圖形編輯軟件的開發(fā),就會(huì)發(fā)現(xiàn)問題領(lǐng)域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬于形狀這樣一個(gè)概念,形狀這個(gè)概念在問題領(lǐng)域是不存在的,它就是一個(gè)抽象概念。正是因?yàn)槌橄蟮母拍钤趩栴}領(lǐng)域沒有對(duì)應(yīng)的具體概念,所以用以表征抽象概念的抽象類是不能夠?qū)嵗摹??

          在面向?qū)ο箢I(lǐng)域,抽象類主要用來進(jìn)行類型隱藏。我們可以構(gòu)造出一個(gè)固定的一組行為的抽象描述,但是這組行為卻能夠有任意個(gè)可能的具體實(shí)現(xiàn)方式。這個(gè)抽象描述就是抽象類,而這一組任意個(gè)可能的具體實(shí)現(xiàn)則表現(xiàn)為所有可能的派生類。模塊可以操作一個(gè)抽象體。由于模塊依賴于一個(gè)固定的抽象體,因此它可以是不允許修改的;同時(shí),通過從這個(gè)抽象體派生,也可擴(kuò)展此模塊的行為功能。熟悉OCP的讀者一定知道,為了能夠?qū)崿F(xiàn)面向?qū)ο笤O(shè)計(jì)的一個(gè)最核心的原則OCP(Open-Closed?Principle),抽象類是其中的關(guān)鍵所在。??


          從語法定義層面看abstract?class和interface??

          在語法層面,Java語言對(duì)于abstract?class和interface給出了不同的定義方式,下面以定義一個(gè)名為Demo的抽象類為例來說明這種不同。??

          使用abstract?class的方式定義Demo抽象類的方式如下:??

          abstract?class?Demo?{??
          ?abstract?void?method1();??
          ?abstract?void?method2();??
          ?…??
          }??

          使用interface的方式定義Demo抽象類的方式如下:??

          interface?Demo?{??
          ?void?method1();??
          ?void?method2();??
          ?…??
          }??

          在abstract?class方式中,Demo可以有自己的數(shù)據(jù)成員,也可以有非abstarct的成員方法,而在interface方式的實(shí)現(xiàn)中,Demo只能夠有靜態(tài)的不能被修改的數(shù)據(jù)成員(也就是必須是static?final的,不過在interface中一般不定義數(shù)據(jù)成員),所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstract?class。??

          ??????從編程的角度來看,abstract?class和interface都可以用來實(shí)現(xiàn)"design?by?contract"的思想。但是在具體的使用上面還是有一些區(qū)別的。??

          首先,abstract?class在Java語言中表示的是一種繼承關(guān)系,一個(gè)類只能使用一次繼承關(guān)系。但是,一個(gè)類卻可以實(shí)現(xiàn)多個(gè)interface。也許,這是Java語言的設(shè)計(jì)者在考慮Java對(duì)于多重繼承的支持方面的一種折中考慮吧。??

          其次,在abstract?class的定義中,我們可以賦予方法的默認(rèn)行為。但是在interface的定義中,方法卻不能擁有默認(rèn)行為,為了繞過這個(gè)限制,必須使用委托,但是這會(huì)?增加一些復(fù)雜性,有時(shí)會(huì)造成很大的麻煩。??

          在抽象類中不能定義默認(rèn)行為還存在另一個(gè)比較嚴(yán)重的問題,那就是可能會(huì)造成維護(hù)上的麻煩。因?yàn)槿绻髞硐胄薷念惖慕缑妫ㄒ话阃ㄟ^abstract?class或者interface來表示)以適應(yīng)新的情況(比如,添加新的方法或者給已用的方法中添加新的參數(shù))時(shí),就會(huì)非常的麻煩,可能要花費(fèi)很多的時(shí)間(對(duì)于派生類很多的情況,尤為如此)。但是如果界面是通過abstract?class來實(shí)現(xiàn)的,那么可能就只需要修改定義在abstract?class中的默認(rèn)行為就可以了。??

          同樣,如果不能在抽象類中定義默認(rèn)行為,就會(huì)導(dǎo)致同樣的方法實(shí)現(xiàn)出現(xiàn)在該抽象類的每一個(gè)派生類中,違反了"one?rule,one?place"原則,造成代碼重復(fù),同樣不利于以后的維護(hù)。因此,在abstract?class和interface間進(jìn)行選擇時(shí)要非常的小心。??


          從設(shè)計(jì)理念層面看abstract?class和interface??

          上面主要從語法定義和編程的角度論述了abstract?class和interface的區(qū)別,這些層面的區(qū)別是比較低層次的、非本質(zhì)的。本小節(jié)將從另一個(gè)層面:abstract?class和interface所反映出的設(shè)計(jì)理念,來分析一下二者的區(qū)別。作者認(rèn)為,從這個(gè)層面進(jìn)行分析才能理解二者概念的本質(zhì)所在。??

          前面已經(jīng)提到過,abstarct?class在Java語言中體現(xiàn)了一種繼承關(guān)系,要想使得繼承關(guān)系合理,父類和派生類之間必須存在"is?a"關(guān)系,即父類和派生類在概念本質(zhì)上應(yīng)該是相同的(參考文獻(xiàn)〔3〕中有關(guān)于"is?a"關(guān)系的大篇幅深入的論述,有興趣的讀者可以參考)。對(duì)于interface?來說則不然,并不要求interface的實(shí)現(xiàn)者和interface定義在概念本質(zhì)上是一致的,僅僅是實(shí)現(xiàn)了interface定義的契約而已。為了使論述便于理解,下面將通過一個(gè)簡單的實(shí)例進(jìn)行說明。??

          考慮這樣一個(gè)例子,假設(shè)在我們的問題領(lǐng)域中有一個(gè)關(guān)于Door的抽象概念,該Door具有執(zhí)行兩個(gè)動(dòng)作open和close,此時(shí)我們可以通過abstract?class或者interface來定義一個(gè)表示該抽象概念的類型,定義方式分別如下所示:??

          使用abstract?class方式定義Door:??

          abstract?class?Door?{??
          ?abstract?void?open();??
          ?abstract?void?close();??
          }??

          ???
          使用interface方式定義Door:??


          interface?Door?{??
          ?void?open();??
          ?void?close();??
          }??

          ???
          其他具體的Door類型可以extends使用abstract?class方式定義的Door或者implements使用interface方式定義的Door??雌饋砗孟袷褂胊bstract?class和interface沒有大的區(qū)別。??

          如果現(xiàn)在要求Door還要具有報(bào)警的功能。我們?cè)撊绾卧O(shè)計(jì)針對(duì)該例子的類結(jié)構(gòu)呢(在本例中,主要是為了展示abstract?class和interface反映在設(shè)計(jì)理念上的區(qū)別,其他方面無關(guān)的問題都做了簡化或者忽略)?下面將羅列出可能的解決方案,并從設(shè)計(jì)理念層面對(duì)這些不同的方案進(jìn)行分析。??

          解決方案一:??

          簡單的在Door的定義中增加一個(gè)alarm方法,如下:??

          abstract?class?Door?{??
          ?abstract?void?open();??
          ?abstract?void?close();??
          ?abstract?void?alarm();??
          }??

          ???
          或者??

          interface?Door?{??
          ?void?open();??
          ?void?close();??
          ?void?alarm();??
          }??

          ???
          那么具有報(bào)警功能的AlarmDoor的定義方式如下:??

          class?AlarmDoor?extends?Door?{??
          ?void?open()?{?…?}??
          ?void?close()?{?…?}??
          ?void?alarm()?{?…?}??
          }??

          ???
          或者??

          class?AlarmDoor?implements?Door?{??
          ?void?open()?{?…?}??
          ?void?close()?{?…?}??
          ?void?alarm()?{?…?}??
          }??

          這種方法違反了面向?qū)ο笤O(shè)計(jì)中的一個(gè)核心原則ISP(Interface?Segregation?Priciple),在Door的定義中把Door概念本身固有的行為方法和另外一個(gè)概念"報(bào)警器"的行為方法混在了一起。這樣引起的一個(gè)問題是那些僅僅依賴于Door這個(gè)概念的模塊會(huì)因?yàn)?報(bào)警器"這個(gè)概念的改變(比如:修改alarm方法的參數(shù))而改變,反之依然。??

          解決方案二:??

          既然open、close和alarm屬于兩個(gè)不同的概念,根據(jù)ISP原則應(yīng)該把它們分別定義在代表這兩個(gè)概念的抽象類中。定義方式有:這兩個(gè)概念都使用abstract?class方式定義;兩個(gè)概念都使用interface方式定義;一個(gè)概念使用abstract?class方式定義,另一個(gè)概念使用interface方式定義。??

          顯然,由于Java語言不支持多重繼承,所以兩個(gè)概念都使用abstract?class方式定義是不可行的。后面兩種方式都是可行的,但是對(duì)于它們的選擇卻反映出對(duì)于問題領(lǐng)域中的概念本質(zhì)的理解、對(duì)于設(shè)計(jì)意圖的反映是否正確、合理。我們一一來分析、說明。??

          如果兩個(gè)概念都使用interface方式來定義,那么就反映出兩個(gè)問題:1、我們可能沒有理解清楚問題領(lǐng)域,AlarmDoor在概念本質(zhì)上到底是Door還是報(bào)警器?2、如果我們對(duì)于問題領(lǐng)域的理解沒有問題,比如:我們通過對(duì)于問題領(lǐng)域的分析發(fā)現(xiàn)AlarmDoor在概念本質(zhì)上和Door是一致的,那么我們?cè)趯?shí)現(xiàn)時(shí)就沒有能夠正確的揭示我們的設(shè)計(jì)意圖,因?yàn)樵谶@兩個(gè)概念的定義上(均使用interface方式定義)反映不出上述含義。??

          如果我們對(duì)于問題領(lǐng)域的理解是:AlarmDoor在概念本質(zhì)上是Door,同時(shí)它有具有報(bào)警的功能。我們?cè)撊绾蝸碓O(shè)計(jì)、實(shí)現(xiàn)來明確的反映出我們的意思呢?前面已經(jīng)說過,abstract?class在Java語言中表示一種繼承關(guān)系,而繼承關(guān)系在本質(zhì)上是"is?a"關(guān)系。所以對(duì)于Door這個(gè)概念,我們應(yīng)該使用abstarct?class方式來定義。另外,AlarmDoor又具有報(bào)警功能,說明它又能夠完成報(bào)警概念中定義的行為,所以報(bào)警概念可以通過interface方式定義。如下所示:??

          abstract?class?Door?{??
          ?abstract?void?open();??
          ?abstract?void?close();??
          }??
          interface?Alarm?{??
          ?void?alarm();??
          }??
          class?AlarmDoor?extends?Door?implements?Alarm?{??
          ?void?open()?{?…?}??
          ?void?close()?{?…?}??
          ????void?alarm()?{?…?}??
          }??

          ???
          這種實(shí)現(xiàn)方式基本上能夠明確的反映出我們對(duì)于問題領(lǐng)域的理解,正確的揭示我們的設(shè)計(jì)意圖。其實(shí)abstract?class表示的是"is?a"關(guān)系,interface表示的是"like?a"關(guān)系,大家在選擇時(shí)可以作為一個(gè)依據(jù),當(dāng)然這是建立在對(duì)問題領(lǐng)域的理解上的,比如:如果我們認(rèn)為AlarmDoor在概念本質(zhì)上是報(bào)警器,同時(shí)又具有Door的功能,那么上述的定義方式就要反過來了。

          posted @ 2007-03-08 13:27 劉璐 閱讀(313) | 評(píng)論 (0)編輯 收藏

          主站蜘蛛池模板: 上蔡县| 和龙市| 秦安县| 安徽省| 敖汉旗| 思茅市| 彰武县| 渑池县| 运城市| 邵武市| 宿州市| 南康市| 龙南县| 瓦房店市| 静宁县| 上林县| 金乡县| 磴口县| 苏尼特左旗| 潜山县| 怀仁县| 威远县| 保靖县| 上高县| 疏勒县| 福鼎市| 西和县| 象山县| 临洮县| 同江市| 永顺县| 元阳县| 于田县| 福建省| 宁远县| 通渭县| 论坛| 鄯善县| 黄骅市| 汉沽区| 体育|