2012年9月26日 星期三

待辦事項管理 (Part II)

第二個範例 (Part II)

The following examples had been tested on Mozilla's Firefox and Microsoft's IE. The document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

資料庫的安裝與設定

為了上課方便,我們使用了 HSQLDB。 在撰寫這份範例的時候,HSQLDB 的版本為 1.8 版,請到 HSQLDB 網站下載; 下載的檔案是一個 .zip 的壓縮檔,請把它解壓縮到安裝 Tomcat 的分割區。 如果你依照我們之前的說明安裝 Tomcat 的話,那麼 HSQLDB 必須被解壓縮 到 D 槽(也就是 d:\hsqldb)。另外,請記得把 d:\hsqldb\lib\hsqldb.jar 複製到 d:\tomcat\common\lib 內;hsqldb.jar 是 HSQLDB 的 JDBC 驅動程式。 本範例所需要的表格只有一個,其資料結構如下;其中,id 為唯一的,代表 每一件事情的 ID、name 代表該事情的敘述、priority 代表處理該事情的 優先順序、以及 date 代表處理該事情的日期。
欄位名稱 資料型態
id varchar(50)
name varchar(50)
priority int
date date

為了能夠在 HSQLDB 建立這樣的表格,我們必須首先啟動 HSQLDB,啟動的 方式很簡單,請在"命令提示字元"視窗內,並在 d:\hsqldb\bin 的目錄下,輸入 runUtil DatabaseManager,執行後,你會看到 如下的畫面:
這個畫面中,請在 "Setting Name" 輸入 Event 當作整個設定值的代碼,下一次 重新啟動 HSQLDB 的時候,就可以直接從 "Recent" 的下拉式選單中選擇它。 另外,請在 URL 欄位輸入 jdbc:hsqldb:file:/hsdqldb/data/event, 其中 jdbc:hsqldb 是固定的,file 代表利用檔案的方式開啟,而最後的 /hsqldb/data/event 即代表該檔案的實際位置;以本範例而言,event 的 檔案可以在 d:\hsqldb\data\ 找到。輸入這些值的畫面如下:
輸入完成後,請點選 "OK" 按鈕,HSQLDB 的畫面如下:
請在這個畫面中,介於 "Clear" 和 "Execute" 的按鈕中間的區塊,輸入以下 指令來產生一個名為 event 的表格(table):
create table event (
id varchar(50),
name varchar(50),
priority int,
date date);
為了測試方便,請輸入已下指令來新增一筆資料到 event 表格:
insert into event values('111', 'Hi', 1, '2009-05-21');
新增完成後,我們可以利用以下的指令來查詢新增的資料是否已經被儲存 到資料庫了:
select * from event;
執行完 select 指令後的畫面如下:
從畫面中,我們可以清楚的看到查詢的結果。到這裡我們建議將 HSQL Database Manager 關掉,然後再重新啟動。啟動的方式跟之前相同, 只是這次是在 "Recent" 的地方選擇 "Event" 即可,開啟後的畫面如下:
請注意,重新啟動之後,左邊的視窗內包含一個 EVENT 的表格,而且 該表格的欄位名稱以及資料型態都可以清楚的看到。

資料庫相關的物件

完成了資料庫的設定之後,我們採用物件導向的設計方式來設計所需要的 相關類別。由於物件的架構和關聯式資料庫的資料結構不同,因此一般來說, 好的程式設計都會利用 DAO (Data Access Object) 的物件來存取資料庫。 DAO 是一種常見的設計模式(design pattern),它的結構圖如下所示:
在圖的左上角是一個應用程式的物件,該物件希望能夠存取圖右邊的資料來源; 資料來源可以是一個檔案,也可以是一個資料庫,甚至也可以是遠端某個提供 資料的物件。為了隱藏資料來源,也為了讓應用程式的維護成本降低(以後 不論是資料來源從檔案改成資料庫、或者從資料庫換成其他的資料來源),應用 程式不直接存取資料來源,而是藉由 DAO 物件來存取資料來源,也就是說 DAO 物件包含了所有連結資料來源的細節以及設定,這些是應用程式不需要 知道的。
在應用程式實際存取的資料是以圖下方的資料對應物件來代表;若資料來源是 關聯式資料庫,而且資料結構就像 event 一樣簡單,一個資料物件即代表一筆 關聯式資料庫中的 record。資料對應物件是由 DAO 物件所產生的,所有應用程式 對資料對應物件的讀取、修改、刪除等動作,都由 DAO 物件負責完成。
依照圖片所示,資料來源其實就是範例中 HSQLDB 中的 event 表格; DAO 物件以及資料對應物件就是如下的 EventDAO 以及 Event 類別。 首先我們介紹 Event 類別;由於 Event 物件代表一筆 event 表格中的 資料,所以我們可以將 event 表格中的每一個欄位名稱對應到 Event 類別中 的資料成員,當然資料成員的資料型態儘可能跟欄位的資料型態相同。 (請注意: 資料型態在不同系統中(如 Java 和 HSQLDB) 可能有不同的表達方式,所以在實務應用上要特別小心;定義完了資料成員 之後,我們只需要為每一個資料成員設計一個對應的 accessor 和 mutator 即可。Event 的程式碼如下:
import java.util.Date;

public class Event {
  private String id;
  private String name;
  private int priority;
  private Date date;

  public Event() {}
  public Event(String id,String name,int priority,Date date) {
    this.id = id;
    this.name = name;
    this.priority = priority;
    this.date = date;
  }
  public Date getDate() {
    return date;
  }
  public void setDate(Date date) {
    this.date = date;
  }
  public int getPriority() {
    return priority;
  }
  public void setPriority(int priority) {
    this.priority = priority;
  }
  public String getId() {
    return id;
  }
  public void setId(String id) {
    this.id = id;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}
由之前 DAO 設計模式的說明,我們知道 EventDAO 類別包含所有連結 HSQLDB 的設定以及細節。由於這個類別跟大多數使用 JDBC 來連結資料庫的程式類似, 我們就不多做說明,需要進一步參考資料的,可以參考我的 資料處理入門。 EventDAO 的建構元載入 JDBC 的驅動程式,並包含了增(insert)、 刪(delete)、改(update)、查(findAll)四個方法。程式內比較需要 說明的部分:
  1. HSQLDB 的 JDBC 驅動程式的名稱為 org.hsqldb.jdbcDriver,而聯結資料庫 的 URL 為 jdbc:hsqldb:file:/hsqldb/data/event。
  2. 不論連線資料庫或者存取資料是否成功,最後都需要把 stmt 和 conn 物件 close() 掉,這可以從方法中看得出來。切記:這些動作不能少。
  3. Event 中 date 的資料型態為 java.util.Date,但是在新增或者修改 date 的資料時,HSQLDB 卻只接受符合 "yyyy-MM-dd HH:mm:ss" 格式的字串,因此 程式中借用 SimpleDateFormat 把 date 物件轉成符合該格式的字串。
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class EventDAO {
  private final String url = "jdbc:hsqldb:file:/hsqldb/data/event";
  private final String user = "sa";
  private final String pwd = "";

  public EventDAO() {
    try {
      // 載入 HSQLDB 的 JDBC 驅動程式
      Class.forName("org.hsqldb.jdbcDriver");
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

  public List findAll(){
    Statement stmt = null;
    Connection conn = null;
    List allEvents = new ArrayList();
    try {
      conn = DriverManager.getConnection(url, user, pwd);
      stmt = conn.createStatement();
      ResultSet rs = stmt.executeQuery("select * from event");

      // fetch all events from database
      Event evt;
      while(rs.next()) {
        evt = new Event();
        evt.setId(rs.getString(1));
        evt.setName(rs.getString(2));
        evt.setPriority(rs.getInt(3));
        evt.setDate(rs.getDate(4));
        allEvents.add(evt);
      }
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
    return allEvents;
  }

  public boolean delete(Event evt){
    Connection conn = null;
    Statement stmt = null;
    boolean result = false;
    try {
      conn = DriverManager.getConnection(url, user, pwd);
      stmt = conn.createStatement();
      if (stmt.executeUpdate("delete from event where id = '" + evt.getId() + "'") > 0)
        result = true;
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
    return result;
  }

  public boolean insert(Event evt){
    Connection conn = null;
    Statement stmt = null;
    boolean result = false;
    try {
      conn = DriverManager.getConnection(url, user, pwd);
      stmt = conn.createStatement();
      if (stmt.executeUpdate("insert into event(id,name,priority,date) " +
                             "values ('" + UUID.randomUUID().toString() + "','" +                      

                             evt.getName() + "'," + evt.getPriority() + ",'" + 
                             new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(evt.getDate()) 
                             + "')") > 0)
        result = true;
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
    return result;
  }

  public boolean update(Event evt){
    Connection conn = null;
    Statement stmt = null;
    boolean result = false;
    try {
      conn = DriverManager.getConnection(url, user, pwd);
      stmt = conn.createStatement();                            
      if (stmt.executeUpdate("update event set name = '" + evt.getName() + 
                             "', priority = " + evt.getPriority() + ", date = '" + 
                             new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(evt.getDate()) + 
                             "' where id = '" + evt.getId() + "'") > 0)
        result = true;
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
    return result;
  }
}
在本範例中所提供的 DAO 類別過於簡單,實務上還需要配合其他的考量, 例如,資料存取的一致性,我們建議可以去閱讀 Persistence Options for Object-Oriented Programs。除了可以自行 設計 DAO 之外,目前被廣泛使用的 DAO 有 Hibernate,有興趣的讀者,可以自行學習。
請把這兩個程式碼編譯後,放置到適當的位置。如果你是依照本範例的安裝方式 進行,那麼請把 Event.class 和 EventDAO.class 放置到 d:\tomcat\webapps\xml\WEB-INF\classes 目錄內。

Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu


















沒有留言:

張貼留言