<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    MicroFish

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

    2007年3月7日

    HSQL 學習筆記1(zz)

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

    HSQL 學習筆記

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

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

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

    一個test 數(shù)據(jù)庫會包含如下文件:
    ? 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 文件包含當前數(shù)據(jù)庫的變更.
    test.data 文件包含緩存表的數(shù)據(jù)
    test.backup 文件是最近持久化狀態(tài)的表的數(shù)據(jù)文件的壓縮備份文件
    所有以上這個文件都是必要的,不能被刪除.如果數(shù)據(jù)庫沒有緩存表,test.data 和test.backup 文件將不會存在.另外,除了以上文件HSQLDB 數(shù)據(jù)庫可以鏈接到任何文本文件,比如cvs 文件.

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






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

    命令行方式:


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

    也可以在 server.properties 文件中定義啟動的數(shù)據(jù)庫,最多10個
    例如: 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
    啟動命令: java -cp ../lib/hsqldb.jar org.hsqldb.Server
    運行結(jié)果如下



    java 測試程序:
    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 中測試通過.

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

    主要的缺點就是不可能從外面的應用程序訪問到默認數(shù)據(jù)庫,因此當你的應用運行時候,你不能通過別的工具檢查數(shù)據(jù)庫內(nèi)容。在1.8.0 版本中,你可以在相同jvm 中的線程中運行數(shù)據(jù)庫初始化,并提供外面訪問你的進程內(nèi)數(shù)據(jù)庫。
    ???? 推薦在開發(fā)應用中使用這種方式。
    連接串:
    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
    當隨即訪問內(nèi)存,數(shù)據(jù)庫不固定時,可以采用內(nèi)存的方式運行數(shù)據(jù)庫,由于沒有數(shù)據(jù)寫到硬盤上,這種方式使用在應用數(shù)據(jù)和applets 和特殊應用的內(nèi)部進程中使用,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表)

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

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

    從版本 1.7.0 以后,支持text 表,使用 CSV (Comma Separated Value)?? 或者其它分隔符文本文件作為它們的數(shù)據(jù)源。你可以特殊指定一個存在的CSV 文件,例如從其它的數(shù)據(jù)或者程序中導出文件,作為TXT 表的數(shù)據(jù)源。 同時,你可以指定一個空文件,通過數(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
    唯一約束:
    唯一索引:
    外健:
    CREATE TABLE child(c1 INTEGER, c2 VARCHAR, FOREIGN KEY (c1, c2) REFERENCES parent(p1, p2));

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

    (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 可以被快速的估計,如果TB 表放到前面(index on TB.COL3):
    一般規(guī)則是把縮小條件的列的表放在前面

    3.4.???? 使用where 還是join
    使用 WHERE?? 條件鏈接表可能會降低運行速度.
    下面的例子將會比較慢,即使使用了索引:
    ???? SELECT ... FROM TA, TB, TC WHERE TC.COL3 = TA.COL1 AND TC.COL3=TB.COL2 AND TC.COL4 = 1
    這個查詢隱含TA.COL1 = TB.COL2 ,但是沒有直接設(shè)定這個條件.如果 TA 和 TB 每個表都包含100 條記錄,10000 組合將和 TC 關(guān)聯(lián),用于TC這個列的條件,盡管有索引在這個列上.使用JOIN 關(guān)鍵字, 在組合TC 之前,TA.COL1 = TB.COL2 條件直接并縮小組合 TA 和 TB 的行數(shù), 在運行大數(shù)據(jù)量的表的結(jié)果是,將會很快:
    ???? SELECT ... FROM TA JOIN TB ON TA.COL1 = TB.COL2 JOIN TC ON TB.COL2 = TC.COL3 WHERE TC.COL4 = 1
    這個查詢可以提高一大步,如果改變表的順序, 所以 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ù)引擎自動應用于TC.COL4 = 1 組合小的集合于其它表關(guān)聯(lián). Indexes TC.COL4, TB.COL2?? TA.COL1 都將使用索引,提高查詢速度.
    3.5.???? Subqueries and Joins
    使用join 和調(diào)整表的順序提高效率.
    例如:, 第二個查詢的速度將更快一些(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
    第二個查詢將 MAX(TB.COL2) 與一個單記錄表相關(guān)聯(lián). 并使用TA.COL1索引,這將變得非常快. 第一個查詢是將 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ū)動jdbc:hsqldb:?? 下列協(xié)議標識(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è)定存儲如下 :
    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, 每個服務(wù)器支持同時啟動10個不同的數(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)啟動):
    (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 劉璐 閱讀(704) | 評論 (0)編輯 收藏

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

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

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

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

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

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

    準備測試數(shù)據(jù)
    首先,要為單元測試準備數(shù)據(jù)。使用DbUnit,我們可以用XML文件來準備測試數(shù)據(jù)集。下面的XML文件稱為目標數(shù)據(jù)庫的Seed File,代表目標數(shù)據(jù)庫的表名和數(shù)據(jù),它為測試準備了兩個Employee的數(shù)據(jù)。employee對應數(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在單元測試開始之前刪除Seed File中所有表的數(shù)據(jù),然后導入Seed File的測試數(shù)據(jù)。在Seed File中不存在的表,DbUnit則不處理。
    Seed File可以手工編寫,也可以用程序?qū)С霈F(xiàn)有的數(shù)據(jù)庫數(shù)據(jù)并生成。

    SqlMap代碼
    我們要測試的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
    為了方便測試,首先為SqlMap的單元測試編寫一個抽象的測試基類,代碼如下。

    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;
    ??? }
    }

    然后為每個SqlMap映射文件編寫一個測試用例,extends上面的抽象類。如編寫Employ.xml的測試用例如下,它覆蓋了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);
    ??? ???
    ??? ??? // 注意這里, 確認刪除不能使用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ù)。因為使用SqlMapClient.queryForObject方法查詢,已刪除的數(shù)據(jù)還存在,真奇怪(有時間再研究)。

    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í)行每個測試前,都會執(zhí)行CLEAN_INSERT操作,刪除Seed File中所有表的數(shù)據(jù),并插入文件的測試數(shù)據(jù)。你可以通過覆蓋getSetUpOperation和getTearDownOperation方法改變setUp和tearDown的行為。

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

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

    使用Ant
    上面的方法通過extends DbUnit的DatabaseTestCase來控制數(shù)據(jù)庫的狀態(tài)。而
    使用DbUnit的Ant Task,完全可以通過Ant腳本的方式來實現(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中間,可以達到控制數(shù)據(jù)庫狀態(tài)的目標。

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

    本文抄襲了資源列表的“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 劉璐 閱讀(733) | 評論 (0)編輯 收藏

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

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

    理解抽象類??

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

    在面向?qū)ο蟮母拍钪校覀冎浪械膶ο蠖际峭ㄟ^類來描繪的,但是反過來卻不是這樣。并不是所有的類都是用來描繪對象的,如果一個類中沒有包含足夠的信息來描繪一個具體的對象,這樣的類就是抽象類。抽象類往往用來表征我們在對問題領(lǐng)域進行分析、設(shè)計中得出的抽象概念,是對一系列看上去不同,但是本質(zhì)上相同的具體概念的抽象。比如:如果我們進行一個圖形編輯軟件的開發(fā),就會發(fā)現(xiàn)問題領(lǐng)域存在著圓、三角形這樣一些具體概念,它們是不同的,但是它們又都屬于形狀這樣一個概念,形狀這個概念在問題領(lǐng)域是不存在的,它就是一個抽象概念。正是因為抽象的概念在問題領(lǐng)域沒有對應的具體概念,所以用以表征抽象概念的抽象類是不能夠?qū)嵗摹??

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


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

    在語法層面,Java語言對于abstract?class和interface給出了不同的定義方式,下面以定義一個名為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方式的實現(xiàn)中,Demo只能夠有靜態(tài)的不能被修改的數(shù)據(jù)成員(也就是必須是static?final的,不過在interface中一般不定義數(shù)據(jù)成員),所有的成員方法都是abstract的。從某種意義上說,interface是一種特殊形式的abstract?class。??

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

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

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

    在抽象類中不能定義默認行為還存在另一個比較嚴重的問題,那就是可能會造成維護上的麻煩。因為如果后來想修改類的界面(一般通過abstract?class或者interface來表示)以適應新的情況(比如,添加新的方法或者給已用的方法中添加新的參數(shù))時,就會非常的麻煩,可能要花費很多的時間(對于派生類很多的情況,尤為如此)。但是如果界面是通過abstract?class來實現(xiàn)的,那么可能就只需要修改定義在abstract?class中的默認行為就可以了。??

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


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

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

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

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

    使用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。看起來好像使用abstract?class和interface沒有大的區(qū)別。??

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

    解決方案一:??

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

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

    ???
    或者??

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

    ???
    那么具有報警功能的AlarmDoor的定義方式如下:??

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

    ???
    或者??

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

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

    解決方案二:??

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

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

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

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

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

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

    關(guān)于session的詳細解釋

    一、術(shù)語session

      在我的經(jīng)驗里,session這個詞被濫用的程度大概僅次于transaction,更加有趣的是transaction與session在某些語境下的含義是相同的。

      session,中文經(jīng)常翻譯為會話,其本來的含義是指有始有終的一系列動作/消息,比如打電話時從拿起電話撥號到掛斷電話這中間的一系列過程可以稱之為一個session。有時候我們可以看到這樣的話“在一個瀏覽器會話期間,...”,這里的會話一詞用的就是其本義,是指從一個瀏覽器窗口打開到關(guān)閉這個期間①。最混亂的是“用戶(客戶端)在一次會話期間”這樣一句話,它可能指用戶的一系列動作(一般情況下是同某個具體目的相關(guān)的一系列動作,比如從登錄到選購商品到結(jié)賬登出這樣一個網(wǎng)上購物的過程,有時候也被稱為一個transaction),然而有時候也可能僅僅是指一次連接,也有可能是指含義①,其中的差別只能靠上下文來推斷②。

      然而當session一詞與網(wǎng)絡(luò)協(xié)議相關(guān)聯(lián)時,它又往往隱含了“面向連接”和/或“保持狀態(tài)”這樣兩個含義,“面向連接”指的是在通信雙方在通信之前要先建立一個通信的渠道,比如打電話,直到對方接了電話通信才能開始,與此相對的是寫信,在你把信發(fā)出去的時候你并不能確認對方的地址是否正確,通信渠道不一定能建立,但對發(fā)信人來說,通信已經(jīng)開始了。“保持狀態(tài)”則是指通信的一方能夠把一系列的消息關(guān)聯(lián)起來,使得消息之間可以互相依賴,比如一個服務(wù)員能夠認出再次光臨的老顧客并且記得上次這個顧客還欠店里一塊錢。這一類的例子有“一個TCP session”或者“一個POP3 session”③。

      而到了web服務(wù)器蓬勃發(fā)展的時代,session在web開發(fā)語境下的語義又有了新的擴展,它的含義是指一類用來在客戶端與服務(wù)器之間保持狀態(tài)的解決方案④。有時候session也用來指這種解決方案的存儲結(jié)構(gòu),如“把xxx保存在session里”⑤。由于各種用于web開發(fā)的語言在一定程度上都提供了對這種解決方案的支持,所以在某種特定語言的語境下,session也被用來指代該語言的解決方案,比如經(jīng)常把Java里提供的javax.servlet.http.HttpSession簡稱為session⑥。

      鑒于這種混亂已不可改變,本文中session一詞的運用也會根據(jù)上下文有不同的含義,請大家注意分辨。

      在本文中,使用中文“瀏覽器會話期間”來表達含義①,使用“session機制”來表達含義④,使用“session”表達含義⑤,使用具體的“HttpSession”來表達含義⑥

      二、HTTP協(xié)議與狀態(tài)保持

      HTTP協(xié)議本身是無狀態(tài)的,這與HTTP協(xié)議本來的目的是相符的,客戶端只需要簡單的向服務(wù)器請求下載某些文件,無論是客戶端還是服務(wù)器都沒有必要紀錄彼此過去的行為,每一次請求之間都是獨立的,好比一個顧客和一個自動售貨機或者一個普通的(非會員制)大賣場之間的關(guān)系一樣。

      然而聰明(或者貪心?)的人們很快發(fā)現(xiàn)如果能夠提供一些按需生成的動態(tài)信息會使web變得更加有用,就像給有線電視加上點播功能一樣。這種需求一方面迫使HTML逐步添加了表單、腳本、DOM等客戶端行為,另一方面在服務(wù)器端則出現(xiàn)了CGI規(guī)范以響應客戶端的動態(tài)請求,作為傳輸載體的HTTP協(xié)議也添加了文件上載、cookie這些特性。其中cookie的作用就是為了解決HTTP協(xié)議無狀態(tài)的缺陷所作出的努力。至于后來出現(xiàn)的session機制則是又一種在客戶端與服務(wù)器之間保持狀態(tài)的解決方案。

      讓我們用幾個例子來描述一下cookie和session機制之間的區(qū)別與聯(lián)系。筆者曾經(jīng)常去的一家咖啡店有喝5杯咖啡免費贈一杯咖啡的優(yōu)惠,然而一次性消費5杯咖啡的機會微乎其微,這時就需要某種方式來紀錄某位顧客的消費數(shù)量。想象一下其實也無外乎下面的幾種方案:

      1、該店的店員很厲害,能記住每位顧客的消費數(shù)量,只要顧客一走進咖啡店,店員就知道該怎么對待了。這種做法就是協(xié)議本身支持狀態(tài)。

      2、發(fā)給顧客一張卡片,上面記錄著消費的數(shù)量,一般還有個有效期限。每次消費時,如果顧客出示這張卡片,則此次消費就會與以前或以后的消費相聯(lián)系起來。這種做法就是在客戶端保持狀態(tài)。

      3、發(fā)給顧客一張會員卡,除了卡號之外什么信息也不紀錄,每次消費時,如果顧客出示該卡片,則店員在店里的紀錄本上找到這個卡號對應的紀錄添加一些消費信息。這種做法就是在服務(wù)器端保持狀態(tài)。

      由于HTTP協(xié)議是無狀態(tài)的,而出于種種考慮也不希望使之成為有狀態(tài)的,因此,后面兩種方案就成為現(xiàn)實的選擇。具體來說cookie機制采用的是在客戶端保持狀態(tài)的方案,而session機制采用的是在服務(wù)器端保持狀態(tài)的方案。同時我們也看到,由于采用服務(wù)器端保持狀態(tài)的方案在客戶端也需要保存一個標識,所以session機制可能需要借助于cookie機制來達到保存標識的目的,但實際上它還有其他選擇。

      三、理解cookie機制

      cookie機制的基本原理就如上面的例子一樣簡單,但是還有幾個問題需要解決:“會員卡”如何分發(fā);“會員卡”的內(nèi)容;以及客戶如何使用“會員卡”。

      正統(tǒng)的cookie分發(fā)是通過擴展HTTP協(xié)議來實現(xiàn)的,服務(wù)器通過在HTTP的響應頭中加上一行特殊的指示以提示瀏覽器按照指示生成相應的cookie。然而純粹的客戶端腳本如JavaScript或者VBScript也可以生成cookie。

      而cookie的使用是由瀏覽器按照一定的原則在后臺自動發(fā)送給服務(wù)器的。瀏覽器檢查所有存儲的cookie,如果某個cookie所聲明的作用范圍大于等于將要請求的資源所在的位置,則把該cookie附在請求資源的HTTP請求頭上發(fā)送給服務(wù)器。意思是麥當勞的會員卡只能在麥當勞的店里出示,如果某家分店還發(fā)行了自己的會員卡,那么進這家店的時候除了要出示麥當勞的會員卡,還要出示這家店的會員卡。

      cookie的內(nèi)容主要包括:名字,值,過期時間,路徑和域。

      其中域可以指定某一個域比如.google.com,相當于總店招牌,比如寶潔公司,也可以指定一個域下的具體某臺機器比如www.google.com或者froogle.google.com,可以用飄柔來做比。

      路徑就是跟在域名后面的URL路徑,比如/或者/foo等等,可以用某飄柔專柜做比。

      路徑與域合在一起就構(gòu)成了cookie的作用范圍。

      如果不設(shè)置過期時間,則表示這個cookie的生命期為瀏覽器會話期間,只要關(guān)閉瀏覽器窗口,cookie就消失了。這種生命期為瀏覽器會話期的cookie被稱為會話cookie。會話cookie一般不存儲在硬盤上而是保存在內(nèi)存里,當然這種行為并不是規(guī)范規(guī)定的。如果設(shè)置了過期時間,瀏覽器就會把cookie保存到硬盤上,關(guān)閉后再次打開瀏覽器,這些cookie仍然有效直到超過設(shè)定的過期時間。

      存儲在硬盤上的cookie可以在不同的瀏覽器進程間共享,比如兩個IE窗口。而對于保存在內(nèi)存里的cookie,不同的瀏覽器有不同的處理方式。對于IE,在一個打開的窗口上按Ctrl-N(或者從文件菜單)打開的窗口可以與原窗口共享,而使用其他方式新開的IE進程則不能共享已經(jīng)打開的窗口的內(nèi)存cookie;對于Mozilla Firefox0.8,所有的進程和標簽頁都可以共享同樣的cookie。一般來說是用javascript的window.open打開的窗口會與原窗口共享內(nèi)存cookie。瀏覽器對于會話cookie的這種只認cookie不認人的處理方式經(jīng)常給采用session機制的web應用程序開發(fā)者造成很大的困擾。

      下面就是一個goolge設(shè)置cookie的響應頭的例子

    HTTP/1.1 302 Found
    Location: http://www.google.com/intl/zh-CN/
    Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
    Content-Type: text/html

      這是使用HTTPLook這個HTTP Sniffer軟件來俘獲的HTTP通訊紀錄的一部分


      瀏覽器在再次訪問goolge的資源時自動向外發(fā)送cookie

      使用Firefox可以很容易的觀察現(xiàn)有的cookie的值

      使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理。


      IE也可以設(shè)置在接受cookie前詢問

      這是一個詢問接受cookie的對話框。

      四、理解session機制

     session機制是一種服務(wù)器端的機制,服務(wù)器使用一種類似于散列表的結(jié)構(gòu)(也可能就是使用散列表)來保存信息。

      當程序需要為某個客戶端的請求創(chuàng)建一個session的時候,服務(wù)器首先檢查這個客戶端的請求里是否已包含了一個session標識 - 稱為session id,如果已包含一個session id則說明以前已經(jīng)為此客戶端創(chuàng)建過session,服務(wù)器就按照session id把這個session檢索出來使用(如果檢索不到,可能會新建一個),如果客戶端請求不包含session id,則為此客戶端創(chuàng)建一個session并且生成一個與此session相關(guān)聯(lián)的session id,session id的值應該是一個既不會重復,又不容易被找到規(guī)律以仿造的字符串,這個session id將被在本次響應中返回給客戶端保存。

      保存這個session id的方式可以采用cookie,這樣在交互過程中瀏覽器可以自動的按照規(guī)則把這個標識發(fā)揮給服務(wù)器。一般這個cookie的名字都是類似于SEEESIONID,而。比如weblogic對于web應用程序生成的cookie,JSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764,它的名字就是JSESSIONID。

      由于cookie可以被人為的禁止,必須有其他機制以便在cookie被禁止時仍然能夠把session id傳遞回服務(wù)器。經(jīng)常被使用的一種技術(shù)叫做URL重寫,就是把session id直接附加在URL路徑的后面,附加方式也有兩種,一種是作為URL路徑的附加信息,表現(xiàn)形式為http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764另一種是作為查詢字符串附加在URL后面,表現(xiàn)形式為http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
    這兩種方式對于用戶來說是沒有區(qū)別的,只是服務(wù)器在解析的時候處理的方式不同,采用第一種方式也有利于把session id的信息和正常程序參數(shù)區(qū)分開來。

      為了在整個交互過程中始終保持狀態(tài),就必須在每個客戶端可能請求的路徑后面都包含這個session id。

      另一種技術(shù)叫做表單隱藏字段。就是服務(wù)器會自動修改表單,添加一個隱藏字段,以便在表單提交時能夠把session id傳遞回服務(wù)器。比如下面的表單



      在被傳遞給客戶端之前將被改寫成




      這種技術(shù)現(xiàn)在已較少應用,筆者接觸過的很古老的iPlanet6(SunONE應用服務(wù)器的前身)就使用了這種技術(shù)。實際上這種技術(shù)可以簡單的用對action應用URL重寫來代替。

      在談?wù)搒ession機制的時候,常常聽到這樣一種誤解“只要關(guān)閉瀏覽器,session就消失了”。其實可以想象一下會員卡的例子,除非顧客主動對店家提出銷卡,否則店家絕對不會輕易刪除顧客的資料。對session來說也是一樣的,除非程序通知服務(wù)器刪除一個session,否則服務(wù)器會一直保留,程序一般都是在用戶做log off的時候發(fā)個指令去刪除session。然而瀏覽器從來不會主動在關(guān)閉之前通知服務(wù)器它將要關(guān)閉,因此服務(wù)器根本不會有機會知道瀏覽器已經(jīng)關(guān)閉,之所以會有這種錯覺,是大部分session機制都使用會話cookie來保存session id,而關(guān)閉瀏覽器后這個session id就消失了,再次連接服務(wù)器時也就無法找到原來的session。如果服務(wù)器設(shè)置的cookie被保存到硬盤上,或者使用某種手段改寫瀏覽器發(fā)出的HTTP請求頭,把原來的session id發(fā)送給服務(wù)器,則再次打開瀏覽器仍然能夠找到原來的session。

      恰恰是由于關(guān)閉瀏覽器不會導致session被刪除,迫使服務(wù)器為seesion設(shè)置了一個失效時間,當距離客戶端上一次使用session的時間超過這個失效時間時,服務(wù)器就可以認為客戶端已經(jīng)停止了活動,才會把session刪除以節(jié)省存儲空間。

      五、理解javax.servlet.http.HttpSession

      HttpSession是Java平臺對session機制的實現(xiàn)規(guī)范,因為它僅僅是個接口,具體到每個web應用服務(wù)器的提供商,除了對規(guī)范支持之外,仍然會有一些規(guī)范里沒有規(guī)定的細微差異。這里我們以BEA的Weblogic Server8.1作為例子來演示。

      首先,Weblogic Server提供了一系列的參數(shù)來控制它的HttpSession的實現(xiàn),包括使用cookie的開關(guān)選項,使用URL重寫的開關(guān)選項,session持久化的設(shè)置,session失效時間的設(shè)置,以及針對cookie的各種設(shè)置,比如設(shè)置cookie的名字、路徑、域,cookie的生存時間等。

      一般情況下,session都是存儲在內(nèi)存里,當服務(wù)器進程被停止或者重啟的時候,內(nèi)存里的session也會被清空,如果設(shè)置了session的持久化特性,服務(wù)器就會把session保存到硬盤上,當服務(wù)器進程重新啟動或這些信息將能夠被再次使用,Weblogic Server支持的持久性方式包括文件、數(shù)據(jù)庫、客戶端cookie保存和復制。

      復制嚴格說來不算持久化保存,因為session實際上還是保存在內(nèi)存里,不過同樣的信息被復制到各個cluster內(nèi)的服務(wù)器進程中,這樣即使某個服務(wù)器進程停止工作也仍然可以從其他進程中取得session。

      cookie生存時間的設(shè)置則會影響瀏覽器生成的cookie是否是一個會話cookie。默認是使用會話cookie。有興趣的可以用它來試驗我們在第四節(jié)里提到的那個誤解。

      cookie的路徑對于web應用程序來說是一個非常重要的選項,Weblogic Server對這個選項的默認處理方式使得它與其他服務(wù)器有明顯的區(qū)別。后面我們會專題討論。

      關(guān)于session的設(shè)置參考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869

      六、HttpSession常見問題

      (在本小節(jié)中session的含義為⑤和⑥的混合)

      1、session在何時被創(chuàng)建

      一個常見的誤解是以為session在有客戶端訪問時就被創(chuàng)建,然而事實是直到某server端程序調(diào)用HttpServletRequest.getSession(true)這樣的語句時才被創(chuàng)建,注意如果JSP沒有顯示的使用 <%@page session="false"%>關(guān)閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的session對象的來歷。

      由于session會消耗內(nèi)存資源,因此,如果不打算使用session,應該在所有的JSP中關(guān)閉它。

      2、session何時被刪除

      綜合前面的討論,session在下列情況下被刪除a.程序調(diào)用HttpSession.invalidate();或b.距離上一次收到客戶端發(fā)送的session id時間間隔超過了session的超時設(shè)置;或c.服務(wù)器進程被停止(非持久session)

      3、如何做到在瀏覽器關(guān)閉時刪除session

      嚴格的講,做不到這一點。可以做一點努力的辦法是在所有的客戶端頁面里使用javascript代碼window.oncolose來監(jiān)視瀏覽器的關(guān)閉動作,然后向服務(wù)器發(fā)送一個請求來刪除session。但是對于瀏覽器崩潰或者強行殺死進程這些非常規(guī)手段仍然無能為力。

      4、有個HttpSessionListener是怎么回事

      你可以創(chuàng)建這樣的listener去監(jiān)控session的創(chuàng)建和銷毀事件,使得在發(fā)生這樣的事件時你可以做一些相應的工作。注意是session的創(chuàng)建和銷毀動作觸發(fā)listener,而不是相反。類似的與HttpSession有關(guān)的listener還有HttpSessionBindingListener,HttpSessionActivationListener和HttpSessionAttributeListener。

    ??????? 5、存放在session中的對象必須是可序列化的嗎

      不是必需的。要求對象可序列化只是為了session能夠在集群中被復制或者能夠持久保存或者在必要時server能夠暫時把session交換出內(nèi)存。在Weblogic Server的session中放置一個不可序列化的對象在控制臺上會收到一個警告。我所用過的某個iPlanet版本如果session中有不可序列化的對象,在session銷毀時會有一個Exception,很奇怪。

      6、如何才能正確的應付客戶端禁止cookie的可能性

      對所有的URL使用URL重寫,包括超鏈接,form的action,和重定向的URL,具體做法參見[6]
    http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770

      7、開兩個瀏覽器窗口訪問應用程序會使用同一個session還是不同的session

      參見第三小節(jié)對cookie的討論,對session來說是只認id不認人,因此不同的瀏覽器,不同的窗口打開方式以及不同的cookie存儲方式都會對這個問題的答案有影響。

      8、如何防止用戶打開兩個瀏覽器窗口操作導致的session混亂

      這個問題與防止表單多次提交是類似的,可以通過設(shè)置客戶端的令牌來解決。就是在服務(wù)器每次生成一個不同的id返回給客戶端,同時保存在session里,客戶端提交表單時必須把這個id也返回服務(wù)器,程序首先比較返回的id與保存在session里的值是否一致,如果不一致則說明本次操作已經(jīng)被提交過了。可以參看《J2EE核心模式》關(guān)于表示層模式的部分。需要注意的是對于使用javascript window.open打開的窗口,一般不設(shè)置這個id,或者使用單獨的id,以防主窗口無法操作,建議不要再window.open打開的窗口里做修改操作,這樣就可以不用設(shè)置。

      9、為什么在Weblogic Server中改變session的值后要重新調(diào)用一次session.setValue
    做這個動作主要是為了在集群環(huán)境中提示W(wǎng)eblogic Server session中的值發(fā)生了改變,需要向其他服務(wù)器進程復制新的session值。

      10、為什么session不見了

      排除session正常失效的因素之外,服務(wù)器本身的可能性應該是微乎其微的,雖然筆者在iPlanet6SP1加若干補丁的Solaris版本上倒也遇到過;瀏覽器插件的可能性次之,筆者也遇到過3721插件造成的問題;理論上防火墻或者代理服務(wù)器在cookie處理上也有可能會出現(xiàn)問題。

      出現(xiàn)這一問題的大部分原因都是程序的錯誤,最常見的就是在一個應用程序中去訪問另外一個應用程序。我們在下一節(jié)討論這個問題。

      七、跨應用程序的session共享

      常常有這樣的情況,一個大項目被分割成若干小項目開發(fā),為了能夠互不干擾,要求每個小項目作為一個單獨的web應用程序開發(fā),可是到了最后突然發(fā)現(xiàn)某幾個小項目之間需要共享一些信息,或者想使用session來實現(xiàn)SSO(single sign on),在session中保存login的用戶信息,最自然的要求是應用程序間能夠訪問彼此的session。

      然而按照Servlet規(guī)范,session的作用范圍應該僅僅限于當前應用程序下,不同的應用程序之間是不能夠互相訪問對方的session的。各個應用服務(wù)器從實際效果上都遵守了這一規(guī)范,但是實現(xiàn)的細節(jié)卻可能各有不同,因此解決跨應用程序session共享的方法也各不相同。

      首先來看一下Tomcat是如何實現(xiàn)web應用程序之間session的隔離的,從Tomcat設(shè)置的cookie路徑來看,它對不同的應用程序設(shè)置的cookie路徑是不同的,這樣不同的應用程序所用的session id是不同的,因此即使在同一個瀏覽器窗口里訪問不同的應用程序,發(fā)送給服務(wù)器的session id也可以是不同的。

      根據(jù)這個特性,我們可以推測Tomcat中session的內(nèi)存結(jié)構(gòu)大致如下。

      筆者以前用過的iPlanet也采用的是同樣的方式,估計SunONE與iPlanet之間不會有太大的差別。對于這種方式的服務(wù)器,解決的思路很簡單,實際實行起來也不難。要么讓所有的應用程序共享一個session id,要么讓應用程序能夠獲得其他應用程序的session id。

      iPlanet中有一種很簡單的方法來實現(xiàn)共享一個session id,那就是把各個應用程序的cookie路徑都設(shè)為/(實際上應該是/NASApp,對于應用程序來講它的作用相當于根)。

    /NASApp

      需要注意的是,操作共享的session應該遵循一些編程約定,比如在session attribute名字的前面加上應用程序的前綴,使得setAttribute("name", "neo")變成setAttribute("app1.name", "neo"),以防止命名空間沖突,導致互相覆蓋。


      在Tomcat中則沒有這么方便的選擇。在Tomcat版本3上,我們還可以有一些手段來共享session。對于版本4以上的Tomcat,目前筆者尚未發(fā)現(xiàn)簡單的辦法。只能借助于第三方的力量,比如使用文件、數(shù)據(jù)庫、JMS或者客戶端cookie,URL參數(shù)或者隱藏字段等手段。

      我們再看一下Weblogic Server是如何處理session的。

      從截屏畫面上可以看到Weblogic Server對所有的應用程序設(shè)置的cookie的路徑都是/,這是不是意味著在Weblogic Server中默認的就可以共享session了呢?然而一個小實驗即可證明即使不同的應用程序使用的是同一個session,各個應用程序仍然只能訪問自己所設(shè)置的那些屬性。這說明Weblogic Server中的session的內(nèi)存結(jié)構(gòu)可能如下

      對于這樣一種結(jié)構(gòu),在session機制本身上來解決session共享的問題應該是不可能的了。除了借助于第三方的力量,比如使用文件、數(shù)據(jù)庫、JMS或者客戶端cookie,URL參數(shù)或者隱藏字段等手段,還有一種較為方便的做法,就是把一個應用程序的session放到ServletContext中,這樣另外一個應用程序就可以從ServletContext中取得前一個應用程序的引用。示例代碼如下,

      應用程序A

    context.setAttribute("appA", session);

      應用程序B

    contextA = context.getContext("/appA");
    HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");

      值得注意的是這種用法不可移植,因為根據(jù)ServletContext的JavaDoc,應用服務(wù)器可以處于安全的原因?qū)τ赾ontext.getContext("/appA");返回空值,以上做法在Weblogic Server 8.1中通過。

      那么Weblogic Server為什么要把所有的應用程序的cookie路徑都設(shè)為/呢?原來是為了SSO,凡是共享這個session的應用程序都可以共享認證的信息。一個簡單的實驗就可以證明這一點,修改首先登錄的那個應用程序的描述符weblogic.xml,把cookie路徑修改為/appA訪問另外一個應用程序會重新要求登錄,即使是反過來,先訪問cookie路徑為/的應用程序,再訪問修改過路徑的這個,雖然不再提示登錄,但是登錄的用戶信息也會丟失。注意做這個實驗時認證方式應該使用FORM,因為瀏覽器和web服務(wù)器對basic認證方式有其他的處理方式,第二次請求的認證不是通過session來實現(xiàn)的。具體請參看[7] secion 14.8 Authorization,你可以修改所附的示例程序來做這些試驗。

      八、總結(jié)

      session機制本身并不復雜,然而其實現(xiàn)和配置上的靈活性卻使得具體情況復雜多變。這也要求我們不能把僅僅某一次的經(jīng)驗或者某一個瀏覽器,服務(wù)器的經(jīng)驗當作普遍適用的經(jīng)驗,而是始終需要具體情況具體分析。

    posted @ 2007-03-07 10:50 劉璐 閱讀(278) | 評論 (0)編輯 收藏

    主站蜘蛛池模板: 亚洲国产成人精品无码区在线观看| 亚洲日本中文字幕天天更新 | 青娱乐免费在线视频| 亚洲国产最大av| 亚洲乱亚洲乱少妇无码| 无码av免费一区二区三区| 亚洲AV无码精品国产成人| 亚洲欧洲美洲无码精品VA | 国产亚洲情侣一区二区无| 免费无遮挡无码永久视频| 色噜噜的亚洲男人的天堂| 亚洲AV永久无码精品成人| 日本免费人成视频播放| 久久免费的精品国产V∧| 亚洲AV成人精品日韩一区| 日韩精品亚洲人成在线观看| 在线播放免费播放av片| 免费无码VA一区二区三区| 香港经典a毛片免费观看看| 亚洲无人区视频大全| 国产亚洲精久久久久久无码AV| 国产乱码免费卡1卡二卡3卡| 中文字幕乱码系列免费| 亚洲国产无线乱码在线观看| 亚洲福利在线视频| 中文字幕日韩亚洲| 国产在线播放免费| 桃子视频在线观看高清免费完整 | 国产又大又黑又粗免费视频| 99久久免费精品高清特色大片| 少妇亚洲免费精品| 亚洲综合一区无码精品| 亚洲欧洲日产国码二区首页| 亚洲片一区二区三区| 午夜色a大片在线观看免费| 久草视频免费在线观看| 亚洲免费人成在线视频观看| 免费在线观看自拍性爱视频| 精品久久久久久久久亚洲偷窥女厕| 亚洲视屏在线观看| 亚洲AV日韩精品久久久久|