顯示具有 資料庫 標籤的文章。 顯示所有文章
顯示具有 資料庫 標籤的文章。 顯示所有文章

2012年10月13日 星期六

Java 與資料處理入門

資料處理入門

Many examples in this document are adapted from Java: How To Program (3rd Ed.), written by Deitel and Deitel, and Thinking in Java (2nd Edition), written by Bruce Eckel. All examples are solely used for educational purposes. Hopefully, I am not violating any copyright issue here. If so, please do email me.
Please install JDK 1.3.1_02 or later with Java Plugin to view this page. Also, this page is best viewed with browsers (for examples, Mozilla 0.99 or later, IE 6.x or later) with CSS2 support. This document is provided as is. You are welcomed to use it for non-commercial purpose.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

目錄

  1. 使用 Properties 來初始化你的程式
  2. 資料庫
  3. JDBC 的種類
  4. 資料庫存取的基本步驟
  5. 資料庫新增、刪除、查詢、修改
  6. JDBC 的種類
  7. 如何從 Unix (或 Linux)連資料庫?
  8. 從 applet 連資料庫的問題
  9. 讀取 Excel 的資料

MySQL Server 與中文

MySQL Server 與中文

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

絕大多數的 Java 程式開發人員在使用 MySQL 之後,都會發現 Java 程式 在存取中文資料的時候很容易出現亂碼,但是英文的資料卻不會。這個問題 之所以存在的主要原因之一就是資料的編碼;身為一個程式開發人員,如果你的 程式(不限定於 Java)發生了中文亂碼的情形,你應該問問自己以下的問題: 資料庫儲存中文資料時,是以什麼 樣的編碼方式儲存,大五碼嗎?資料庫跟客戶端程式互相傳遞資料的時候, 它們又是以什麼樣的編碼在傳遞,大五碼嗎?你的應用程式中的中文資料 是以什麼方式編碼,大五碼嗎?在了解了這些編碼的情形之後,絕大部分的 問題應該可以迎刃而解。

2012年9月26日 星期三

利用純 JDBC 驅動程式來存取資料庫

MySQL Server 簡介

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

本文假設你已經安裝了 MySQL Server 5.1.x 版,而且你也依據之前的說明, 建立了使用者 jlu,而且你也為 jlu 建立了一個資料庫 eric,並且在資料庫中, 建立了表格 Product。在下列的步驟中,我們將說明如何利用純 JDBC 驅動程式 來開發 Java 程式以便於對表格 Product 進行增、刪、改、查的動作。 以下文件,我們假設讀者熟析 Java 程式;如果讀者想學習 Java 的物件導向 設計的技巧,我們建議購買作者所寫的 呂瑞麟與陳宜惠著,Java 101: 物件導向程式設計,上奇出版,09/2008 以及其 勘誤表
  1. 為了讓 Java 程式(包含 JSP、Java Servlets 等)能夠存取資料庫, 昇陽(Sun) 定義了 JDBC 的介面,而各家的資料庫管理系統可以依據這些介面定義, 開發出其資料庫系統的 JDBC 驅動程式。JDBC 驅動程式又分成四大類,其細節 可以參考 資料處理入門,在本文中,我們只針對 MySQL 提供的純 JDBC 驅動程式來說明。
  2. 純 JDBC 驅動程式:若要使用純 JDBC 驅動程式,請安裝 MySQL Connector/J。作者下載的是 5.1.x 版的 ZIP Archive,檔案名稱為 mysql-connector-java-5.1.x.zip。將檔案解壓縮之後, 請在解壓縮的目錄內,將 mysql-connector-java-5.1.x-bin.jar 設定到適當的 環境變數 CLASSPATH 內;如果你的開發環境包含 Tomcat,則我們建議 你將 mysql-connector-java-5.1.x-bin.jar 放置於 tomcat\shared\lib 目錄內。假設 Tomcat 安裝於 e:\tomcat,請將該 jar 檔放置於 tomcat\shared\lib 目錄,並設定適當的環境變數 CLASSPATH。 設定環境變數可以由控制台來完成,或者在"命令提示字元"視窗內輸入:
    set CLASSPATH=.;e:\tomcat\shared\lib\mysql-connector-java-5.1.x-bin.jar;%CLASSPATH%
    
  3. 開發 JDBC 程式:一般來說,以 Java 語言(含 JSP 和 servlet)來連結資料庫,大多需要完成以下的步驟:
    1. 利用 Class.forName(驅動程式的名稱); 載入 JDBC 的驅動程式;以 MySQL 的 JDBC 驅動程式的名稱為例,載入的用法為 Class.forName("com.mysql.jdbc.Driver");。每一種驅動程式都有其相對應的名稱,開發人員在使用之前必須先搞清楚。
    2. 利用 Connection conn = DriverManager.getConnection(資料庫的位置, 帳號名 稱, 密碼); 來產生一個 Java 程式和資料庫之間的連線(也就是 Connection 物件 conn)。 DriverManager.getConnection() 內有三個參數,第一個參數說明程式想要 跟哪一個資料庫連線;同樣的,這個參數的值會因為使用的 JDBC 驅動程式不同而 不同,以我們的範例為例,因為我們使用 MySQL 的 JDBC 驅動程式,而且因為 MySQL 的位置在本機(也就是 127.0.0.1),且因為資料庫的名稱為 "eric", 所以第一個參數值必須為 "jdbc:mysql://127.0.0.1/eric"(其中 jdbc:mysql: 是固定不變的,最後 "//IP 位址/資料庫名稱" 會隨著 MySQL 安裝的 IP 位址以及該 MySQL 上的資料庫名稱的不同而改變)。第二個以及 第三個參數分別為連結該資料庫所需要的"帳號名稱"以及"密碼"。
    3. 連線完成之後,我們可以開始執行 SQL 的指令了。執行 SQL 指令的方式是先 借由 conn 來產生一個 Statement 的物件,然後再藉由 Statement 的物件來執行 SQL 指令。產生 Statement 物件的方式 為 Statement aStatement = conn.createStatement();,其中 aStatement 即為 Statement 物件的名稱。
    4. SQL 指令主要執行"增、刪、改、查"四個動作,而這四個動作中只有"查詢" 會回傳一個表格的資料,其他三種都只回傳一個代表執行是否成功的整數。
      1. 如果 SQL 指令執行"增、刪、改",執行的方式為 aStatement.executeUpdate(SQL指令);,該方法回傳總共被改變的資料筆數。以在 Product 新增一筆編號 5 的產 品為例,我們的程式碼即為 aStatement.executeUpdate("insert into Product values(5,'鍵盤',14.5,2)");;由於只有一筆資料被新增,所以執行後,aStatement.executeUpdate() 會回傳 1。
      2. 如果 SQL 指令執行"查詢",執行的方式為 aStatement.executeQuery(SQL指令 );,該方法回傳查詢的結果;由於 SQL 查詢的結果也是一個表格,該表格由 Java 的 ResultSet 物件所代表。以查詢整個 Product 的資料為例,我們的程式碼即為 ResultSet rs = aStatement.executeQuery("select * from Product");
    5. 如果 SQL 指令是查詢,程式大多會進一步處理該查詢結果,也就是 ResultSet 物件。一個 ResultSet 物件包含兩種資料,一種是該回傳表格的 Metadata(由 ResultSetMetaData 物件所代表;該物件包含總共有幾個欄位、欄位的名稱、 欄位的資料型態等資料),另一種是表格的資料。
      1. 我們可以經由 ResultSetMetaData rsmeta = rs.getMetaData(); 來 取得 ResultSetMetaData 物件;然後經由 int cols = rsmeta.getColumnCount(); 來取得回傳表格的總欄位數;在得到總欄位數 cols 之後,我們就可以經由一個 簡單的 for 迴圈,將每一個欄位的名稱以及資料型態,經由 rsmeta.getColumnLabel(i) 以及 rsmeta.getColumnType(i)
      2. 經由 ResultSet 物件 rs 可以取得實際的查詢資料。在預設的情形下,一開始 rs 指向資料的第 0 筆,我們可以經由 rs.next() 來依序取得下一筆的資料, 一旦下一筆資料不存在,rs.next() 會回傳 false。當 rs.next() 指向某一筆資料的 時候,我們可以利用之前 rsmeta 來取得總欄位數,然後利用 rs.getString(i) 將一個一個欄位的資料以字串的方式取出。除了 getString(i) 的方式之外, 我們也可以利用 getDate(i)getTime(i)getDouble(i)getInt(i) 等方法分別取出資料型態為 Date、Time、double、int 的資料。
    6. 最後,但也是很重要的:在程式結束以前,我們必須將相關的資料庫資源釋放 出來。資源釋放的方式是經由呼叫該物件的 close() 方法達成;若釋放 Statement 物件,則與該 Statement 物件相關的 ResultSet 物件也會被釋放。 另外,在程式的最後,我們也必須經由 conn.close(); 將連線釋放。




    由於這個程式跟之前的 NewODBC.java 幾乎一樣,我們只針對不同的地方 (以綠色標示)作說明。

    import java.sql.*;
    
    public class NewJDBC {
      // 設定 JDBC 驅動程式的名稱:com.mysql.jdbc.Driver
      static String classname = "com.mysql.jdbc.Driver";
    
      // 設定 JDBC 的連線資訊,其中 jdbc:mysql:// 是固定不變的
      // 在 // 之後,首先加上 IP 或者主機名稱,在本例中是 127.0.0.1
      // IP 之後,請接上斜線(/)以及資料庫的名稱,在本例中是 eric
      static String jdbcURL = "jdbc:mysql://127.0.0.1/eric";
      static String UID = "jlu";
      static String PWD = "newpasswd";
      static Connection conn = null;
    
      public static void main( String argv[] ) {
        if(argv.length != 1) {
          System.out.println("Usage: java NewJDBC Product");
          System.exit(2);
        }
        String aQuery = "select * from " + argv[0];
        String iSQL = "insert into " + argv[0] + " values(5,'鍵盤',14.5,2)";
        String uSQL = "update " + argv[0] + " set Name='無線鍵盤' where ID=5";
        String dSQL = "delete from " + argv[0] + " where ID=5";
    
        try {
          // 載入 JDBC 驅動程式
          Class.forName(classname).newInstance();
    
          // connect to Database
          conn = DriverManager.getConnection(jdbcURL,UID,PWD);
    
          // Display current content
          System.out.println("Display current content");
          ShowResults(aQuery);
    
          // Insert a new record
          System.out.println("\nInserting a new record .....");
          InsertNew(iSQL);
          ShowResults(aQuery);
    
          // Update record
          System.out.println("\nUpdateing a record .....");
          UpdateNew(uSQL);
          ShowResults(aQuery);
    
          // Delete record
          System.out.println("\nDeleting a record .....");
          DeleteNew(dSQL);
          ShowResults(aQuery);
    
          conn.close();
        } catch (Exception sqle) {
          System.out.println(sqle);
          System.exit(1);
        }
      }
    
      private static void DeleteNew(String dSQL) {
        try {
          Statement aStatement = conn.createStatement();
          aStatement.executeUpdate(dSQL);
        } catch (Exception e) {
          System.out.println("Delete Error: " + e);
          System.exit(1);
        }
      }
    
      private static void UpdateNew(String uSQL) {
        try {
          Statement aStatement = conn.createStatement();
          aStatement.executeUpdate(uSQL);
        } catch (Exception e) {
          System.out.println("Update Error: " + e);
          System.exit(1);
        }
      }
    
      private static void InsertNew(String iSQL) {
        try {
          Statement aStatement = conn.createStatement();
          aStatement.executeUpdate(iSQL);
        } catch (Exception e) {
          System.out.println("Insert Error: " + e);
          System.exit(1);
        }
      }
    
    
      private static void ShowResults(String aQuery) {
        try {
          Statement aStatement = conn.createStatement();
          ResultSet rs = aStatement.executeQuery(aQuery);
    
          ResultSetMetaData rsmeta = rs.getMetaData();
          int cols = rsmeta.getColumnCount();
    
          // Display column headers
          for(int i=1; i<=cols; i++) {
            if(i > 1) System.out.print("\t");
            System.out.print(rsmeta.getColumnLabel(i));
          }
          System.out.print("\n");
    
          // Display query results.
          while(rs.next()) {
            for(int i=1; i<=cols; i++) {
              if (i > 1) System.out.print("\t");
              System.out.print(rs.getString(i)); 
            }
            System.out.print("\n");
          }
    
          // Clean up
          aStatement.close();
        }
        // a better exception handling can be used here.
        catch (Exception e) {
          System.out.println("Exception Occurs.");
        }
      }
    }
    




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


在資料庫中建立表格 (tables)

MySQL Server 簡介

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材


本文假設你已經安裝了 MySQL Server 5.1.x 版,而且你也依據之前的說明, 建立了使用者 jlu,而且你也為 jlu 建立了一個資料庫 eric,並把這個資料庫 所有權限給了 jlu;也就是說,jlu 可以任意在資料庫 eric 中新增、修改、 以及刪除表格(tables)。 在以下的步驟中,jlu 即將在資料庫 eric 中新增一個表格 Product:
  1. 以 jlu 登入
    // 以 jlu 登入,並使用資料庫 eric
    mysql -u jlu -p eric
    
    // 如果要登入遠端 hostname 的 MySQL Server
    mysql -h hostname -u jlu -p eric
    
    // 如果 jlu 不喜歡或者想更改 root 指定的密碼,可以執行以下指令
    // 同樣的,newpassword 指的是你想輸入的密碼
    set password for jlu@localhost=password('newpassword');
    
  2. 產生表格 Product:每一樣產品包含編號、名稱、價格、以及數量。
    // create table
    create table Product (
      ID int,
      Name varchar(30),
      Price decimal(5,2),
      Qty int);
    
    // 查看 Product 是否已經產生
    // Windows 版產生的 table 名稱變成 product
    show tables;
    
  3. 資料型態:雖然 SQL 有部分資料型態的定義,但大多數的資料庫系統都有其 自己的資料型態的定義,我們在下列資料中,說明幾種比較常見的資料型態。如果 讀者需要進一步的資訊,請到 MySQL 5.1 Reference Manual 中,參考 "Data Types" 的資料。
    • 數字資料型態:
      • SMALLINT (2 bytes) 以及 INT or INTEGER (4 bytes)
      • DEC or DECIMAL 以及 NUMERIC (例如. salary DECIMAL(5,2))
    • 近似值的數字資料型態: FLOAT, REAL, 以及 DOUBLE PRECISION
    • 字串: CHAR 以及 VARCHAR (例如. name VARCHAR(20))
    • 日期與時間: DATE (例如. '2004-12-4') 以及 DATETIME (例如. '2004-12-4 13:15:0')
  4. 為表格 Product 建立範例資料
    insert into Product values (1, 'Monitor', 200.5, 4);
    insert into Product values(2, '無線存取器', 110, 3);
    insert into Product values(3, '無線滑鼠', 11.99, 10);
    insert into Product values(4, '無線輸入超值組合包', 111.99, 8);
    
    // 資料新增之後,檢查一下輸入是否正確
    select * from Product;
    
  5. 執行一些簡單的增、刪、改、查:
    // 查詢所有的產品名稱以及數量
    select Name, Qty from Product;
    
    // 查詢所有價格高於 100 的產品
    select * from Product where Price > 100;
    
    // 查詢編號為 3 的產品名稱以及價格
    select Name, Price from Product where ID = 3;
    
    // 修改編號 3 的產品價格,並利用前一個查詢指令來確認
    update Product set Price=15.3 where ID=3;
    
    // 將編號 3 的產品刪除
    delete from Product where ID=3;
    



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




利用 JDBC-ODBC 驅動程式來存取資料庫

MySQL Server 簡介

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

本文假設你已經安裝了 MySQL Server 5.1.x 版,而且你也依據之前的說明, 建立了使用者 jlu,而且你也為 jlu 建立了一個資料庫 eric,並且在資料庫中, 建立了表格 Product。在下列的步驟中,我們將說明如何利用 JDBC 驅動程式 來開發 Java 程式以便於對表格 Product 進行增、刪、改、查的動作。 以下文件,我們假設讀者熟析 Java 程式;如果讀者想學習 Java 的物件導向 設計的技巧,我們建議購買作者所寫的 呂瑞麟與陳宜惠著,Java 101: 物件導向程式設計,上奇出版,09/2008 以及其 勘誤表
  1. 為了讓 Java 程式(包含 JSP、Java Servlets 等)能夠存取資料庫, 昇陽(Sun) 定義了 JDBC 的介面,而各家的資料庫管理系統可以依據這些介面定義, 開發出其資料庫系統的 JDBC 驅動程式。JDBC 驅動程式又分成四大類,其細節 可以參考 資料處理入門,在本文中,我們只針對 JDBC-ODBC 驅動程式來說明。
  2. JDBC-ODBC 驅動程式:若要使用 JDBC-ODBC 驅動程式,請安裝 MySQL Connector/ODBC。作者下載的是 5.1.x 版的 ZIP Archive,檔案名稱為 mysql-connector-odbc-noinstall-5.1.x-win32.zip。將檔案解壓縮之後, 請在解壓縮的目錄內,執行 install.bat 即可。請注意,如果你的開發環境 不是 Windows,那麼你的作業環境必須安裝以下驅動程式之一:unixODBC, Apple iODBC, 或者 iODBC。 安裝 JDBC-ODBC 驅動程式之後,你需要在控制台上設定 ODBC 的資訊,這些 資訊包含連結資料庫的電腦在哪裡(IP 或者主機名稱)、資料庫名稱、帳號/密碼、 以及代表以上資訊的一個名稱,該名稱為 Data Source Name(資料來源名稱; 或簡稱 dsn)。你如果 使用的是非 Windows 的 ODBC 驅動程式,則請依據該程式的設定方式進行。 首先,在 Windows XP 的環境下,請利用控制台 --> 效能及維護 --> 系統管理工具 --> 資料來源 (ODBC),並開啟"資料來源",開啟後畫面如下:
    然後,請在"系統資料來源名稱"上點一下,其畫面如下:
    請在"新增"按鈕上點一下,螢幕上會出現"建立新資料來源"的對話視窗,將可以 選擇的驅動程式清單往下拉,你會看到如以下畫面的 "MySQL ODBC 5.1 Driver", 請選擇它,並點一下"完成"按鈕。
    在點選完"完成"按鈕後,螢幕會出現一個 MySQL Connector/ODBC 的設定畫面。

    以這個範例來說,dsn 的名稱為 csie,因此請在第一個欄位填入 csie。 第二個欄位(Description)是用來描述該 dsn 的;如果你建立了許多的 dsn, 這個欄位的資料可以提醒你它的作用為何,在這個範例中,我們不填入任何資料。 第三個欄位 Server 指的是 MySQL 的所在位置,它可以是遠端的 Server 或者是當地端的 localhost;因為我們都假設在同一步電腦上執行這些 動作,所以這個欄位我們可以填入 127.0.0.1 或者 localhost。 第四個欄位 Port 指的是 MySQL Server 的連線端口號,由於我們並未修改 MySQL 的設定,而 MySQL 的預設端口號就是 3306,因此這個部分不必修改; 如果你認為使用標準端口號會造成比較大的網路安全漏洞,你可以在伺服器端 和 dsn 同時修改連線端口號。
    第五、六、七個欄位分別是使用者帳號(User)、密碼(Password)、以及 資料庫名稱(Database);依據之前的設定,我們可以分別填入 jlu、newpasswd、 以及 eric。完成後的畫面如下:

    填寫完以上資料之後,你可以點一下 "Test" 按鈕在測試一下設定是否正確,能不能 完成連線等。如果一切沒問題,最後我們必須對資料庫連線的編碼方式進行設定, 設定的方式是在 "Details>>" 按鈕點一下,然後依照以下畫面,在 "Misc Options" 上點一下,然後在 "Character Set:" 內選擇 "big5"。

    設定完成並點選完 "OK" 按鈕後,以下的畫面會出現,我們也完成了全部的設定, 這時就可以把該視窗關閉。

  3. 程式開發:一般來說,以 Java 語言(含 JSP 和 servlet)來連結 資料庫,大多需要完成以下的步驟:
    1. 利用 Class.forName(驅動程式的名稱); 載入 JDBC 的驅動程式;以 JDBC-ODBC 驅動程式的名稱為例,載入的用法為 Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");。每一種驅動程式都有其相對應的名稱,開發人員在使用之前必須 先搞清楚。
    2. 利用 Connection conn = DriverManager.getConnection(資料庫的位置, 帳號名稱, 密碼); 來產生一個 Java 程式和資料庫之間的連線(也就是 Connection 物件 conn)。 DriverManager.getConnection() 內有三個參數,第一個參數說明程式想要 跟哪一個資料庫連線;同樣的,這個參數的值會因為使用的 JDBC 驅動程式不同而 不同,以我們的範例為例,因為我們使用 JDBC-ODBC 驅動程式,而經由 ODBC 產生 資料庫連線必須藉助"資料來源名稱"(DSN);因為我們之前設定了一個名為 csie 的 DSN,所以第一個參數值必須為 "jdbc:odbc:csie"(其中 jdbc:odbc: 是 固定不變的,最後一個會隨著之前設定的 DSN 名稱改變而變動)。第二個以及 第三個參數分別為連結該資料庫所需要的"帳號名稱"以及"密碼"。
    3. 連線完成之後,我們可以開始執行 SQL 的指令了。執行 SQL 指令的方式是先 借由 conn 來產生一個 Statement 的物件,然後再藉由 Statement 的物件來執行 SQL 指令。產生 Statement 物件的方式 為 Statement aStatement = conn.createStatement();,其中 aStatement 即為 Statement 物件的名稱。
    4. SQL 指令主要執行"增、刪、改、查"四個動作,而這四個動作中只有"查詢" 會回傳一個表格的資料,其他三種都只回傳一個代表執行是否成功的整數。
      1. 如果 SQL 指令執行"增、刪、改",執行的方式為 aStatement.executeUpdate(SQL指令);,該方法回傳總共被改變的資料筆數。以在 Product 新增一筆編號 5 的產品為例,我們的程式碼即為 aStatement.executeUpdate("insert into Product values(5,'鍵盤',14.5,2)");;由於只有一筆資料被新增,所以執行後,aStatement.executeUpdate() 會回傳 1。
      2. 如果 SQL 指令執行"查詢",執行的方式為 aStatement.executeQuery(SQL指令);,該方法回傳查詢的結果;由於 SQL 查詢的結果也是一個表格,該表格由 Java 的 ResultSet 物件所代表。以查詢整個 Product 的資料為例,我們的程式碼即為 ResultSet rs = aStatement.executeQuery("select * from Product");
    5. 如果 SQL 指令是查詢,程式大多會進一步處理該查詢結果,也就是 ResultSet 物件。一個 ResultSet 物件包含兩種資料,一種是該回傳表格的 Metadata(由 ResultSetMetaData 物件所代表;該物件包含總共有幾個欄位、欄位的名稱、 欄位的資料型態等資料),另一種是表格的資料。
      1. 我們可以經由 ResultSetMetaData rsmeta = rs.getMetaData(); 來 取得 ResultSetMetaData 物件;然後經由 int cols = rsmeta.getColumnCount(); 來取得回傳表格的總欄位數;在得到總欄位數 cols 之後,我們就可以經由一個 簡單的 for 迴圈,將每一個欄位的名稱以及資料型態,經由 rsmeta.getColumnLabel(i) 以及 rsmeta.getColumnType(i)
      2. 經由 ResultSet 物件 rs 可以取得實際的查詢資料。在預設的情形下,一開始 rs 指向資料的第 0 筆,我們可以經由 rs.next() 來依序取得下一筆的資料, 一旦下一筆資料不存在,rs.next() 會回傳 false。當 rs.next() 指向某一筆資料的 時候,我們可以利用之前 rsmeta 來取得總欄位數,然後利用 rs.getString(i) 將一個一個欄位的資料以字串的方式取出。除了 getString(i) 的方式之外, 我們也可以利用 getDate(i)getTime(i)getDouble(i)getInt(i) 等方法分別取出資料型態為 Date、Time、double、int 的資料。
    6. 最後,但也是很重要的:在程式結束以前,我們必須將相關的資料庫資源釋放 出來。資源釋放的方式是經由呼叫該物件的 close() 方法達成;若釋放 Statement 物件,則與該 Statement 物件相關的 ResultSet 物件也會被釋放。 另外,在程式的最後,我們也必須經由 conn.close(); 將連線釋放。




    完成了以上的安裝以及設定之後,我們就可以開發 Java 程式來 存取資料庫的資料。我們在下列程式中為 Product 新增一筆編號 5 的產品, 新增之後,我們接著修改它、並檢查該資料是否被正確的修改;最後,再把 編號 5 的產品資料刪除掉。 這個範例程式還蠻簡單的,比較不一樣的地方,我們都加上註解了。

    import java.sql.*;
    
    public class NewODBC {
      // 試著將以下的設定以 properties 的檔案讀進來
    
      // 設定 ODBC-JDBC 驅動程式的名稱
      static String classname = "sun.jdbc.odbc.JdbcOdbcDriver";
    
      // ODBC's DNS 設為 csie,你也可以設成其他名稱
      static String jdbcURL = "jdbc:odbc:csie";
      static String UID = "jlu";
      static String PWD = "newpasswd";
      static Connection conn = null;
    
      public static void main( String argv[] ) {
        // 說明這個程式的使用方式為執行:java NewODBC Product
        if(argv.length != 1) {
          System.out.println("Usage: java NewODBC Product");
          System.exit(2);
        }
    
        // 查詢用的 SQL 字串
        String aQuery = "select * from " + argv[0];
    
        // 新增資料的 SQL 字串
        String iSQL = "insert into " + argv[0] + " values(5,'鍵盤',14.5,2)";
    
        // 修改資料的 SQL 字串
        String uSQL = "update " + argv[0] + " set Name='無線鍵盤' where ID=5";
    
        // 刪除資料的 SQL 字串
        String dSQL = "delete from " + argv[0] + " where ID=5";
    
        try {
          // 載入 JDBC-ODBC 驅動程式
          Class.forName(classname);
    
          // 連結資料庫(藉由 dns、帳號、密碼)
          conn = DriverManager.getConnection(jdbcURL,UID,PWD);
    
          // 顯示目前表格的內容
          System.out.println("Display current content");
          ShowResults(aQuery);
    
          // 新增資料並顯示目前表格的內容
          System.out.println("\nInserting a new record .....");
          InsertNew(iSQL);
          ShowResults(aQuery);
    
          // 修改資料並顯示目前表格的內容
          System.out.println("\nUpdateing a record .....");
          UpdateNew(uSQL);
          ShowResults(aQuery);
    
          // 刪除資料並顯示目前表格的內容
          System.out.println("\nDeleting a record .....");
          DeleteNew(dSQL);
          ShowResults(aQuery);
    
          conn.close();
        } catch (Exception sqle) {
          System.out.println(sqle);
          System.exit(1);
        }
      }
    
      private static void DeleteNew(String dSQL) {
        try {
          Statement aStatement = conn.createStatement();
          aStatement.executeUpdate(dSQL);
        } catch (Exception e) {
          System.out.println("Delete Error: " + e);
          System.exit(1);
        }
      }
    
      private static void UpdateNew(String uSQL) {
        try {
          Statement aStatement = conn.createStatement();
          aStatement.executeUpdate(uSQL);
        } catch (Exception e) {
          System.out.println("Update Error: " + e);
          System.exit(1);
        }
      }
    
      private static void InsertNew(String iSQL) {
        try {
          Statement aStatement = conn.createStatement();
    
          // 新增、修改、刪除都跟查詢一樣,在執行 SQL 字串前,必須先
          // 產生 statement 物件。但是跟查詢不一樣的地方在於,真正
          // 執行的方法用的是 executeUpdate(),而不是 executeQuery()
          aStatement.executeUpdate(iSQL);
        } catch (Exception e) {
          System.out.println("Insert Error: " + e);
          System.exit(1);
        }
      }
    
    
      private static void ShowResults(String aQuery) {
        try {
          // 產生一個 SQL statement 物件並利用之前建立的連線 conn
          // 送給 MySQL,然後在伺服器端執行該 statement 物件中的字串
          Statement aStatement = conn.createStatement();
          ResultSet rs = aStatement.executeQuery(aQuery);
    
          // 執行 SQL 字串後的結果被置放於一個 ResultSet 的物件
          // 依據 SQL 的原理,回傳的結果其實也是一個表格(table)
          // 因此 ResultSet 的物件包含該表格的 Metadata 以及資料
    
          // 表格的 Medata 可以經由 ResultSet 物件的 getMetaData 的
          // 方法取得,而取得的 Metadata 由一個 ResultSetMetaData 物件代表
          ResultSetMetaData rsmeta = rs.getMetaData();
    
          // 由 getColumnCount() 可以知道表格共有幾個欄位
          int cols = rsmeta.getColumnCount();
    
          // 經由 for 迴圈可以得出每一個欄位的名稱
          for(int i=1; i<=cols; i++)
          {
            if(i > 1) System.out.print("\t");
    
            // 由 getColumnLabel(i) 可以取得第 i 個欄位名稱
            System.out.print(rsmeta.getColumnLabel(i));
          }
          System.out.print("\n");
    
          // 經由 while 迴圈可以將資料一筆一筆取出來
          // rs.next() 是查詢是否還有下一筆;若有,為 true;反之,為 false
          while(rs.next()) {
            for(int i=1; i<=cols; i++)
            {
              if (i > 1) System.out.print("\t");
    
              // rs.getString(i) 取得第 i 欄的資料並以字串的方式回傳
              System.out.print(rs.getString(i));
            }
            System.out.print("\n");
          }
    
          // 將 rs 以及 statement 清掉。這個動作很重要,如果不清除的話,
          // 在資料量很大的情形下,Java 垃圾處理器來不及處理的話,就會出現異常
          aStatement.close();
        }
        // a better exception handling can be used here.
        catch (Exception e) {
          System.out.println("Exception Occurs.");
        }
      }
    }
    
















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


MySQL 準備工作

MySQL Server 簡介

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

本文假設你已經安裝了 MySQL Server 5.1.x 或者 5.5.x 版。由於 root 擁有最高權限, 因此練習中非常容易造成嚴重的錯誤,為了方便,大多數都會產生一個擁有 一般權限的使用者,並為其指定一個專用的資料庫,以便於測試。 以下我們假設要產生一個使用者 jlu 並使他成為名為 eric 資料庫的擁有者,而且 允許這個使用者能從任何電腦連到這個資料庫。這個步驟主要是給資料庫 管理員的,而你需要使用 mysql 這個執行檔。
  1. 啟動 MySQL 資料庫:如果你依據之前的建議安裝方式,請在 e:\mysql 目錄下,執行
      .\startup.bat
      
    如果你的 MySQL 是安裝在 Unix/Linux 環境下,請在提示下輸入(註:早期的文件中, 我假設建置的環境是 Unix/Linux 環境;這些年由於教學環境的限制,大多轉成了 Windows 的環境,所以畫面大多以 Windows 為主)
      mysqld_safe --user=mysql
      
    如果你依照之前的安裝方式進行,你應該可以看到如下的畫面:
  2. 進入 mysql:剛安裝好的時候,MySQL 為你設定兩個使用者,一個是 root, 另一個是 anonymous,而這兩個帳號的密碼一開始的時候是空的, 所以第一次 login 是不需要密碼的。
    // 請在命令提示字元視窗內,進入 e:\mysql\bin,語法是在
    // 視窗內,分別輸入
    // e:
    // cd \mysql\bin
    // 這兩個指令。然後,輸入
    mysql -u root
      
    輸入之後,你應該可以看到如下的畫面:
    在畫面的底下,有一個 mysql> 的提示,我們稱它為 MySQL 提示; 之後文章內的指令,都是輸入在 mysql> 之後,然後 Enter
    // 想看看目前有幾個 databases,所有系統設定都在 mysql 這個資料庫
    show databases;
    
    // 使用 mysql 這個資料庫
    use mysql;
    
    // 想看看目前使用的 database 有幾個 tables
    show tables;
    
    // 看看有哪些使用者
    select host, user from user;
    
    // 讓我們為 root 設定密碼
    // 更改 user table 中的 password 欄位的值
    // 下列指令中的 newpasswd 請把它改成你希望的密碼
    update user set password = password('newpasswd') where user='root';
    
    // 讓我們把 anonymous 帳號刪除
    delete from user where user='';
    
    // 讓修改馬上生效
    flush privileges;
    
    // logout
    quit
    
    // 重新 login, 這次就需要密碼了
    // 以 -p 來指定在 enter 後輸入密碼
    // 依照 MySQL 的官方文件,從 MySQL 4.1.1 版之後,你所輸入的密碼
    // 並不會是以明文的方市在網路上傳送,因此他們認為非常安全
    // 但是所有傳送的資料卻都是明文的。建議使用 ssh
    mysql -u root -p
      
  3. 產生新的使用者 jlu
    // 兩個 some-password 可以不同,但是這會造成再 localhost 登入時
    // 所用的密碼和遠端登入時不一樣
    //
    // 授與使用者 jlu 在資料庫  eric 中所有的權限
    grant all privileges on eric.* to 'jlu'@'localhost'
    identified by 'some-password' with grant option;
    
    // 你可以檢查一下使用者是否已經產生
    select host, user from mysql.user;
    
    // % 代表所有遠端電腦都可以登入,如果你的安全需求比較高,建議不要
    grant all privileges on eric.* to 'jlu'@'%' 
    identified by 'some-password' with grant option;
      
  4. 產生資料庫 eric 並將 eric 的權限給使用者 jlu。以下的語法,資料庫 eric 以綠色表示。
    // 產生資料庫 eric
    create database eric;
    
    // 將 eric 的權限給使用者 jlu
    grant all on eric.* to jlu@'localhost';
    
    // 檢查資料庫 eric 是否已經產生
    show databases;
      
  5. 由於資料庫系統安裝於遠端的 Unix 電腦上,而我們一般都是使用 Microsoft Windows 的系統,因此使用一個 GUI 介面的程式將會非常方便,我們建議安裝 MySQL 下載頁 中的 MySQL Workbench。






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


在 Windows 安裝 Noinstall Zip Archive 版的 MySQL 5.1.x

在 Windows 安裝 Noinstall Zip Archive 版的 MySQL 5.1.x

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

首先,請到 http://dev.mysql.com/downloads/ 下載 MySQL。本文的說明,以 MySQL Server 5.1.x 的 Noinstall Zip Archive 版為例,因此下載的檔案名稱類似 mysql-noinstall-5.1.x-win32.zip; 如果下載的版本是 5.11.14 版,則 x 的值就是 14。
  1. 解壓縮:我們建議將下載的檔案安裝於電腦分割區的根目錄,以 E 槽為例, 請將檔案解壓縮在 E:\,並將目錄名稱更改為 mysql
  2. 建立設定檔:請在 e:\mysql 的目錄下建立設定檔 my.ini, 檔案內容如下:
    [mysqld]
    # set basedir to your installation path
    basedir=E:/mysql
    # set datadir to the location of your data directory
    datadir=E:/mysql/data
    # set default character set
    default-character-set=utf8
    
    [client]
    default-character-set=big5
    
    在這個設定檔中,總共有四項設定:第一個是 basedir,該設定用來 告訴 MySQL Server 其安裝路徑;第二個是 datadir,該項設定用來 告訴 MySQL Server 資料庫存放資料的位置;第三以及第四個設定是為了中文(Big5) 資料能夠正常顯示的設定,其參考資料為23.11.8: What problems should I be aware of when working with the Big5 Chinese character set?;除了 23.11.8 之外, 有興趣進一步了解的讀者,請閱讀 23.11.9 或者 MySQL Server 與中文
  3. 建立啟動檔:請在 e:\mysql 的目錄下建立 startup.bat, 檔案內容如下:
    start e:\mysql\bin\mysqld --defaults-file=my.ini --console
    
  4. 建立停止檔:請在 e:\mysql 的目錄下建立 shutdown.bat, 檔案內容如下:
    e:\mysql\bin\mysqladmin -u root shutdown
    
    如果之後,你為 MySQL 的 root 帳號設定了密碼之後(剛安裝好的時候,root 是 沒有密碼的),你必須將 root 改成 root -p;這項 改變會讓你有機會輸入密碼。




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



在 Windows 安裝 Noinstall Zip Archive 版的 MySQL 5.5.x

在 Windows 安裝 Noinstall Zip Archive 版的 MySQL 5.5.x

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材


首先,請到 http://dev.mysql.com/downloads/ 下載 MySQL 5.5.x。本文的說明,以 MySQL Server 5.5.x 的 Noinstall Zip Archive 版為例,因此下載的檔案名稱類似 mysql-5.5.x-win32.zip; 如果下載的版本是 5.5.9 版,則 x 的值就是 9。
  1. 解壓縮:我們建議將下載的檔案安裝於電腦分割區的根目錄,以 E 槽為例, 請將檔案解壓縮在 E:\,並將目錄名稱更改為 mysql
  2. 建立設定檔:在 e:\mysql 的目錄下有幾個 MySQL 提供的預設設定檔, 分別是 my-innodb-heavy-4G.ini, my-huge.ini, my-large.ini, my-medium.ini, 以及 my-smalll.ini(依據所使用的記憶體大小,由大而小排列)。這些設定檔主要是依據 有多少記憶體給 MySQL 用而定;例如,若你願意將電腦的 512MB 記憶體全部給 MySQL 使用的話,你就使用 my-large.ini。由於現今的電腦大多有 1GB 以上的記憶體, 而這份教材只是當作教學用,所以我們採用 my-medium.ini 為例;其實不論你使用 哪一個設定檔,作法都大同小異。 請將 my-medium.ini 複製為 my.ini,然後利用編輯器(例如 記事本)開啟檔案;搜尋 [mysqld],並在其下加入以下設定:
    # 設定 MySQL 安裝的位置
    basedir=E:/mysql
    # 設定 MySQL 的資料庫檔所存放的位置
    datadir=E:/mysql/data
    # 設定 MySQL 伺服器端的預設字元集,這裡設的是 utf8
    character-set-server=utf8
    
    然後,搜尋 [client],並在其下加入以下設定:
    # 設定 MySQL 客戶器端的預設字元集,這裡設的是 big5;
    # 這樣的設定是為了 Java/servlets/JSP 程式開發的方便性
    default-character-set=big5
    
    在這個設定檔中,總共有四項設定:第一個是 basedir,該設定用來 告訴 MySQL Server 其安裝路徑;第二個是 datadir,該項設定用來 告訴 MySQL Server 資料庫存放資料的位置;第三以及第四個設定是為了中文(Big5) 資料能夠正常顯示的設定,其參考資料為23.11.8: What problems should I be aware of when working with the Big5 Chinese character set?;除了 23.11.8 之外, 有興趣進一步了解的讀者,請閱讀 23.11.9 或者 MySQL Server 與中文。 以 my-medium.ini 修改成 my.ini 後的部分重要畫面如下:
  3. 建立啟動檔:請在 e:\mysql 的目錄下建立 startup.bat, 檔案內容如下:
    start e:\mysql\bin\mysqld --defaults-file=my.ini --console
    
  4. 建立停止檔:請在 e:\mysql 的目錄下建立 shutdown.bat, 檔案內容如下:
    e:\mysql\bin\mysqladmin -u root shutdown
    
    如果之後,你為 MySQL 的 root 帳號設定了密碼之後(剛安裝好的時候,root 是 沒有密碼的),你必須將 root 改成 root -p;這項 改變會讓你有機會輸入密碼。






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



Microsoft SQL Server 2000 簡介

Microsoft SQL Server 2000 簡介

本教材主要參考由施威銘研究室編著、旗標出版的Microsoft SQL Server 2000 設計實務與管理實務 以及由曾守正與周韻寰編著、儒林出版的資料庫系統應用實務所編寫 而成。
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

目錄

  1. 組成單元
  2. 新增功能
  3. 安裝
  4. 建立磁碟鏡像
  5. 建立資料庫
  6. 建立表格
  7. 表格查詢
  8. 索引
  9. 預儲程序
  10. 觸發程序
  11. 交易與鎖定
  12. 使用者權限管理

組成單元

SQL Server 的主要組成單元有:
  1. 資料庫: SQL Server 資料庫的副檔名為 .MDF,而存放紀錄(log)的 副檔名為 .LDF(可參考 data 路徑內的檔案)。 SQL Server 有一些特定的系統資料庫
    1. master 資料庫:這個資料庫記錄有關 SQL Server 的資訊,包括所有的 登入帳號、系統的組態、各資料的初始資訊等重要資料。若 master 資料庫毀損了,可 使用位於 80\tools\binn 目錄下之 rebuildm.exe 來重建。
    2. msdb 資料庫:它是提供 SQL Server Agent 作各類排程作業所用的資料庫。 另外,有關 backup/restore 、複寫等資訊也放在這裡。
    3. model 資料庫:基本上,它是一個樣板資料庫,所有新增的資料庫的內容都 由這個資料庫複製過去。
    4. tempdb 資料庫:它是用來存放所有作業過程中所產生的資料用的。
  2. Transact-SQL:除了標準的 ANSI SQL 之外,還包含
    1. 流程控制的指令: 如 if、while 等。
    2. 使用者自訂的資料型態
    3. 預儲程序(Stored Procedures)與觸發程序(Triggers)
  3. 命令列應用程式: ISQL
  4. 視窗應用程式:
    1. ISQL/w:即 Query Analyzer
    2. SQL 用戶端設定程式
    3. SQL Service Manager 可以讓你啟動及結束服務程式
    4. MMC (Microsoft Management Console;即 SQL Enterprise Manager) 程式 用來管理伺服器
    5. SQL Server 的效能監視器,除了可用來監視效能外,也可用來 作最佳化的調整。

新增功能

Microsoft SQL 2000 比舊版的 SQL 7.0 和 SQL 6.5 多出許多新的功能, 我們在此只對我們常見的部分做簡單的說明:
  1. 支援 XML:關聯式資料庫引擎已可使用「延伸式標記語言」(XML) 的文件格式 傳回資料。不僅如此,XML 還可以用來插入、更新和刪除資料庫中的值。
  2. 可以有多個執行個體:SQL Server 2000 支援在同一部電腦上執行多個關聯式資料庫 引擎的執行個體 (Instance)。每個執行個體都有自己專屬的一套系統和使用者資料庫。 執行個體主要是套用到資料庫引擎及其支援的元件,而不是套用到用戶端工具。對於預設 執行個體而言,服務的名稱仍然是 MSSQLServer 與 SQLServerAgent。對於具名執行個體 而言,服務的名稱則變成 MSSQL$instancename 與 SQLAgent$instancename,讓執行個體 的啟動及停止與伺服器上的其他執行個體無關。
  3. 新的資料類型:SQL Server 2000 引進了三種新的資料型別。bigint 為一種 8 位元 組的整數型別。sql_variant 為一種可以儲存多種資料型別資料值的型別。table 則為允 許應用程式儲存暫存結果以供稍後使用的型別,這些資料型別可以適用於變數,並可以作 為使用者自訂函數的傳回型別。

安裝

  1. 註冊 SQL Server 伺服器:你可以在 Enterprise Manager 註冊 local 的或者 遠端的 SQL Servers。
  2. 更改資料庫管理員(sa)的密碼:
    「SQL Server 群組 / <Your Server Name> / 安全性 / 登入」,並選擇 「sa」的帳號後,按右鍵來選取「內容」。如果你對資料的使用者想多了解一些, 你可以現在先到「使用者權限管理」把"使用者基本觀念"看一看。
  3. 開啟 Enterprise Manager 並在本地的 SQL Server 圖示上按右鍵來選取「內容」 來更改 SQL Server 的特性。
  4. 利用預儲程序瞧一瞧有哪些 metadata?
    1. 利用 Enterprise Manager 看看有哪些系統表格(system tables)。
    2. 找出有多少資料庫?
                 sp_databases
             
    3. 找出有哪些表格?
                 (1) sp_tables
                 (2) sp_tables @TABLE_TYPE = "'TABLE'"
             
    4. Demo 連結至遠端 SQL Server 的方法
      1. IP is 163.17.9.7, port is 1433.
      2. 利用 Query Analyzer:使用前請先使用用戶端網路公用程式 來設定。
      3. 利用 Enterprise Manager


建立磁碟鏡像

參考由陳玄玲與許皓翔編著、松崗出版的精通 NT Server 4.0

建立資料庫

  1. 建議之管理方式:只有資料庫管理員(Database Administrator; DBA) 擁有「Database Creators」的權限, 其他資料庫使用者只給予表格的產生、刪除、修改等的權限。至於查詢者, 只給予讀取之權限。
  2. 利用指令建立資料庫的範例: 要特別注意,在下面的範例中 路徑 c:\mssql\data 必需已經存在,否則無法正確的產生資料庫。
      CREATE DATABASE Bob
      ON
      ( NAME = 'Bob_dat',
        FILENAME = 'c:\mssql\data\bob_dat.mdf',
        SIZE = 4MB,
        FILEGROWTH = 1MB )
      LOG ON
      (
        NAME = 'Bob_log',
        FILENAME = 'c:\mssql\data\bob_log.ldf',
        SIZE = 2MB,
        FILEGROWTH = 1MB )
      
  3. 利用指令刪除資料庫的範例:要注意目前正在使用 (被使用者開啟來讀取或寫入) 的資料庫不能卸除。
      DROP DATABASE Bob         # 刪除資料庫 bob
      DROP DATABASE Bob, Fish   # 同時刪除資料庫 bob 和 fish
      
  4. 以上的指令可由 Query Analyzer 或 ISQL 輸入,利用 ISQL 輸入指令的 方式如下:
    1. 開啟 MS-DOS Prompt
    2. 輸入 isql -Usa -Sec1
      -S 表示後面的名稱 ec1 為在 Alias Manager 所設定的資料庫名稱; 而 -U 表示後面的名稱為你的資料庫帳號。
    3. 結束 ISQL,請輸入 quit
  5. 若事先指定的硬碟已經滿了,而我仍需要加大我的資料庫,怎麼辦? 「SQL Server 群組 / <Your Server Name> / 資料庫」,選擇你的資料庫, 並在資料庫上按右鍵然後選擇「內容 / 資料檔案」。

建立表格

(曾3-13)
  1. 從其他資料來源(如 Microsoft Access)匯入資料 (import data)。可下載 Bob.mdb, 一個 Microsoft Access 資料庫作範例。這個資料庫包含三個表格各為 books、bookstores、以及 orders。
    1. 點選資料庫(如 dbms
    2. 按右鍵並選「所有工作 / 匯入資料」。
  2. 你也可以 利用匯出資料(export data)將資料儲存於 Microsoft Access 然後帶回家使用。試試看?
  3. 在匯入/匯出的過程中,有很多的設定會不見,那怎麼辦? 甚至,當你完成程式開發而必須在另一部機器上安裝,你需要一步一步的 重新設定嗎?要是設定的過程中出錯怎麼辦?這時你需要使用 script。
    1. Script 是有如 autoexe.bat 的文字檔,資料庫管理系統可以載入 該 script 檔並執行它的內容。
    2. 將下列範例存入一文字檔(如 1.sql)
             select db_name()
      
             use dbms
      
             select db_name()
             
    3. 利用 Query Analyzer 開啟 1.sql,並執行
    4. 你可以利用「SQL Server 群組 / <Your Server Name> / 資料庫」,選擇你的 資料庫,並在資料庫上按右鍵然後選擇「所有工作 / 產生 SQL 指令碼」 來產生所有跟這個資料庫相關的 script,如此一來,你便可以利用這個產生的 script 檔到另一部電腦上快速的建立起你的資料庫!
  4. 你可以使用 Enterprise Manager 來產生新的資料表,也可以利用 指令來產生資料表,如(曾3-13)
      create table bookstores
      ( no int not null primary key,
        name varchar(10),
        rank int,
        city varchar(8))
      
  5. 資料型態:
    資料型態說明範例
    INT長度為 4 個 bytes;介於 -2,147,483,648 與 2,147,483,647 間的整數
    SMALLINT長度為 2 個 bytes;介於 -32,768 與 32,767 間的整數
    TINYINT長度為 1 個 bytes;介於 0 與 255 間的整數
    BIGINT長度為 8 個 bytes;介於 -2^63 與 2^63-1 間的整數
    FLOAT[(n)]n 是儲存 float 數字的小數位數,介於 1 與 53 間; 若 n 介於 1 與 24 間,儲存大小為 4 個 bytes,有效位數為 7 位數;若 n 介於 25 與 53 間,儲存大小為 8 個 bytes,有效位數為 15 位數
    create table p_ex (num1 real, num2 float)
    insert into p_ex values(4000000.123456789, 
                      4000000.123456789012345)
    insert into p_ex values(400.123456789, 
                      400.1234567890123456789)
    select * from p_ex
    // real 共顯示八位數,float 共顯示 17 位數
    
    REAL長度為 4 個 bytes;介於 -3.4E-38 與 3.4E+38 間的浮點數;與 FLOAT(24) 相同
    DECIMAL[(p[,s])]使用 2 到 17 個 bytes 來儲存資料,可儲存的值介於 -1038-1 與 1038-1 之間;p 用來定義小數點兩邊可以被儲存的位數總數目,而 s 代表小數點右邊的有效位數(s < p);p的預設值為 18 而 s 的預設值為0
    create table p_ex (num1 numeric(19,9), 
                       num2 decimal)
    insert into p_ex values(4000000.123456789, 
                      4000000.123456789012345)
    select * from p_ex
    
    NUMERIC[(p[,s])]與 DECIMAL[(p[,s])] 同
    CHAR[(n)]固定長度為 n 的字元型態,n 必須介於 1 與 8000 之間
    VARCHAR[(n)]與 CHAR 相同,只是若輸入的資料小於 n,資料庫不會自動補空格,因此為變動長度之字串
    NCHAR 與 NVARCHAR與 CHAR 以及 VARCHAR 相同,只是每一個 字元為兩個 bytes 的 unicode,且 n 最大為 4000
    DATETIME長度為 8 個 bytes;介於 1/1/1753 與 12/31/9999 間的日期時間
    SMALLDATETIME長度為 4 個 bytes;介於 1/1/1900 與 6/6/2079間的日期時間
    create table d_ex (d1 datetime, 
                       d2 smalldatetime)
    insert into d_ex values ('1/1/1753', 
                           'Jan 2 1900')
    select * from d_ex
    
    MONEY長度為 8 個 bytes 的整數,小數點的精確度取四位
    SMALLMONEY長度為 4 個 bytes 的整數,小數點的精確度取四位
    BIT只佔用一個位元,且不允許存放 NULL 值
    BINARY[(n)]固定長度為 n+4 個 bytes; n 為 1 到 8000 的值,輸入的 值必需符合兩個條件: (1) 每一個值皆為 0-9、a-f 的值;(2)每一個值的前面必須有 0X
    create table b_ex (x binary(1), y binary(2))
    insert into b_ex values(0x0, 0x0)
    insert into b_ex values(0x1, 0x1)
    insert into b_ex values(0xff, 0xff)
    insert into b_ex values(0xfff, 0xfff)
    insert into b_ex values(0xff, 0xfff)
    insert into b_ex values(0xffff, 0xffff)
    insert into b_ex values(0xff, 0xffff)
    select * from b_ex
    
    VARBINARY[(n)]與 BINARY 相同,只是若輸入的資料小於 n,資料庫 不會自動補 0,因此長度為變動的
    TEXT用來儲存大量的(可高達兩億個位元組)字元資料,儲存空間 以 8k 為單位動態增加
    NTEXT和 TEXT 雷同,只是儲存的是 Unicode 資料
    IMAGE和 TEXT 雷同,只是儲存的是影像資料
    SQL_VARIANT此資料型別可儲存 text、ntext、timestamp 與 sql_variant以外的各種 SQL Server 支援的資料型別。 a sql_variant column can contain smallint values for some rows, float values for other rows, and char/nchar values in the remainder.
    create table  b_ex (x int, y sql_variant)
    insert into b_ex values(5, '五十')
    insert into b_ex values(6, 60)
    select * from b_ex
    
  6. 了解 NULL:
    表格欄位的 NULL 的特性可以讓你省略掉一欄位中的欄位值,其值被資料庫管理系統 解讀為「未定義」或「不存在」,這與空白、零、或 ASCII 的 null 值不同。 若一個欄位被定義為 NOT NULL,則表示該欄位不得為空的。
    1. 輸入 NULL 值: NULL 與 'NULL' 不同
          create table ntable (x int null, y char(10) null)
          insert into ntable values(NULL, null)
          select * from ntable
          
    2. 輸入部分值
          insert into ntable (x) values(5)
          insert into ntable (y) values('5')
          insert into ntable (y) values('null')
          select * from ntable
          
    3. 可以利用 is null 或者 is not null 來檢查,例如請查出所有 y 非 null 的資料。
          SELECT * FROM ntable WHERE y IS NOT NULL
          
    4. NULL 的處理: 在 ORDER BY 、 GROUP BY 、 與 DISTINCT 中,所有 NULL 的值被視為相同。
          select * from ntable order by y
          
    5. 設定欄位為 NOT NULL
          create table ntable (x int null, y char(10) not null)
          
      因此我們無法執行 insert into ntable (x) values(5)
  7. 了解 identity: 欄位的特性除了 「NULL/NOT NULL」 之外,還可以利用 identity 來定義。 當一個欄位被定義為 identity 後,你可以省略該欄位的輸入,資料庫系統會自動 依照你所定義的遞增值來遞增。identity(1,2) 指定該欄位的值從 1 開始,每一次加 2,identity 的預設方式為 identity(1,1)。 要注意的是:你只能把 identity 的特性指定 給資料型態為 BIGINT、INT、SMALLINT、TINYINT、DECIMAL(p,0)、NUMERIC(p,0) 的欄位。
      create table itable (name char(15), row int identity(1,2))
      insert into itable (name) values ('Bob Smith')
      insert into itable (name) values ('Mary Jones')
      select * from itable
      
  8. 使用者自訂之資料型態: 當一組專案人員在進行資料庫的設計時,對於某些欄位的設計可能有所 不同,如對於姓名的設定,有些開發人員定義它為 char(15) 而另外 一些開發人員可能定義它為 char(20),這除了造成定義的不統一外, 更可能造成資料在不同的系統轉換成不同的結果。我們可以利用 使用者自訂之資料型態並宣告所有的專案設計都遵循此一規格,以降低 前述之問題。
    1. 利用 Enterprise Manager:展開資料庫(如 Bob),並在展開項目中, 選擇「使用者自訂資料型別」來新增與刪除。
    2. 新增的語法: sp_addtype typename, system_datatype, null_type
    3. 檢查新增的使用者自訂型態: sp_help typename
    4. 新增的範例:
          sp_addtype TP_names, 'char(15)', 'not null'
          
    5. 刪除的語法: sp_droptype typename
    6. 刪除的範例: sp_droptype TP_names
    7. 使用者自訂型態的使用:
          create table itable (name TP_names, row int)
          insert into itable values('Bob Smith', 1)
          select * from itable
          
  9. 主鍵(primary key)的建立
    1. single-attribute PK.
    2. multiple-attribute PK.
        CREATE TABLE ntable
        (num1 int,
         num2 money,
         PRIMARY KEY (num1, num2))
        
  10. 外來鍵(Foreign Key)的建立:
    1. 指令:
              ALTER TABLE orders ADD CONSTRAINT FK_orders_no
                    FOREIGN KEY (no) REFERENCES bookstores (no)
        
    2. 經由 Enterprise Manager 的「設計資料表」視窗中,點選 「資料表與索引屬性」圖示,並選擇「關聯性」標籤。
    3. 請將 books 中的 id 也加入為外來鍵。
    4. 使用指令來變更屬性後,請檢查表格內 「設計資料表 / 資料表和索引屬性 / 關聯性」的變化。
    5. 外來鍵的建立可以維護部分資料的一致性,例如,
      1. 若 orders 中存在有 books.id 為 1 的資料時,你無法將 id 為 1 的資料 從 books 中刪除。若
          delete from books where id = 1
          
        你會收到錯誤訊息。
      2. 若 bookstores 中不存在 no 為 6 的資料時, 你無法將 no 為 6 的訂單資料加入 orders 中.
  11. 外來鍵的刪除
            ALTER TABLE orders DROP CONSTRAINT FK_orders_no
      
  12. 資料的輸入:
    1. 在輸入資料的表格,按右鍵並選擇 「開啟資料表 / 傳回所有資料列」, 再出現的對話窗中開始輸入資料。
    2. SQL 語法:
              insert into books values(4,'西遊記','吳承恩',140,'聊齋出版社')
          
  13. 怎麼做才能 insert 如 'a' (包含單引號)的資料?輸入兩個單引號代表一個單引號,例如
            insert into books values(7,'''a''','''b''',140,'''c''')
        
  14. 外來鍵參考圖:
    1. 展開資料庫(如 Bob),並選擇「圖表」
    2. 按右鍵並選擇「新增資料庫圖表」
    3. 利用資料庫圖表你可以很容易的看出資料表之間的關聯性,其實 如果你在之前並未設定任何關聯性,你也可以利用這個圖表所提供的 工具來設定資料表之間的關聯。
  15. 唯一性的限制(Unique Constraints)是讓我們強制某個欄位 中所含的值一定不能有重複的資料,(可以用來設定候選鍵(candidate key))。 例如,我們假設在 books 中的 bookname 為一候選鍵,其設定方式如下:
       ALTER TABLE books ADD CONSTRAINT UQ_books_name
       UNIQUE NONCLUSTERED (bookname)
       
    完成上述操作後,試試看能不能再加入一本由另一個出版社出版的 「紅樓夢」。
  16. 檢查的限制(Check Constraint)能夠用來限制可以被輸入到 一資料庫表格中一個或多個欄位中的值。
    1. 表格 books 中的單價必須介於 100 與 500 之間:選擇 books 並按右鍵後執行「設計資料表 / 資料表和索引屬性 / 檢查條件約束」;在 「條件約束運算式」中輸入 price > 100 and price < 500 並於「條件約束名稱」內輸入 CK_books_1
    2. 表格 bookstores 中的 rank 只可能有 10、20、30、或 40; 其他數字皆不可能。選擇 books 並按右鍵後執行「設計資料表 / 資料表和索引屬性 / 檢查條件約束」;在 「條件約束運算式」中輸入 rank in (10, 20, 30, 40) 並於「條件約束名稱」內輸入 CK_bookstores_1
  17. 規則物件 (Rule)
    規則物件的功能與檢查的限制(check)非常類似,不過規則物件是獨立儲存的物件,它必須 與資料表 bind 之後,才能發揮查核的功能。規則物件的優點是,同一個規則物件 可以提供不同資料表的不同欄位使用,但每個欄位最多只能和一個規則物件 結合。
    1. 建立規則指令
          CREATE RULE price_rule AS @price >= 1 AND @price <= 50000
          CREATE RULE gender_rule AS @sex in ('男', '女', NULL)
          
    2. 利用 enterprise manager,「SQL Server 群組 / <Your Server Name> / 資料庫 /規則」
    3. 將規則 bind 到資料表的欄位, sp_bindrule rule_name, 'table_name.column_name'
    4. 解除 bind 的關係, sp_unbindrule 'table_name.column_name' 或者 利用 enterprise manager,「SQL Server 群組 / <Your Server Name> / 資料庫 /規則」,於選取規則後,按右鍵並點選『內容 / 繫結資料行』,將資料行 由繫結中移除。
    5. 刪除規則,drop rule rule_name [, ... n]。要特別注意的是在刪除規則 以前,使用者必須先解除這項規則與所有資料表的關係。











表格查詢

  1. OUTER JOIN:
    • 列出所有書局的訂單資料(包含未下任何訂單的書局)
        select name, id, quantity
        from bookstores LEFT OUTER JOIN orders
        on bookstores.no = orders.no
        
    • 列出所有書名的訂購資料(可以將 no 為 1 且 id 為 6 的資料 從 orders 中刪除,比較容易看出效果)
        select bookname, orders.no, quantity
        from orders RIGHT OUTER JOIN books
        on books.id = orders.id
        
    • 以下的範例若改為 RIGHT OUTER JOIN 或者 FULL OUTER JOIN 會產生什麼結果?
        select name, bookname, quantity
        from (bookstores LEFT OUTER JOIN orders
        on bookstores.no = orders.no)
        left outer join books
        on books.id = orders.id
        

索引

索引(Index)是資料庫管理系統內部將表格裡的資料組織成 可以更快速的進行資料存取的一種方式。索引是一個表格,它包含 某一表格裡屬性值(可為單一屬性或組合屬性),以及 其所對應指向這些值存放在表格中的資料分頁的指標。
  1. 建立索引的通則:一般來說,這會因為你的經驗、使用時效能的考量等因素 來考量,並沒有一個百戰百勝的方程式。但是,你還是可以從你的 表格的存取方式來決定;例如,雖然說學號是學生資料表的主鍵, 但是若查詢的大部分時間是利用姓名和科系來決定,那麼你大概就可以 新增一個以姓名和科系為主的索引。另外,索引的其他候選者也包含 外來鍵以及 ORDER BY 與 GROUP BY 裡面所含的欄位。如果欄位的內容 同質性很高,例如性別欄只有男跟女兩種,那麼這個欄位就不適合做索引。
  2. 多少個索引才不會造成效能的明顯下降? 雖然索引的確能增快查詢的速度,但是卻會導致新、修改的速度下降, 究竟可以建立多少個索引才不會造成效能的明顯下降? 依據 Stephen Wynkoop 的說法,SQL Server 的 magic number 是 5 個。
  3. 索引的使用:關鍵在於 SQL Server 考慮一索引時,SELECT 敘述的其中一個欄位名稱必須是在索引中的第一欄位,否則不會 使用這個索引。在上述的學生例子中,若 SELECT 敘述只用到 科系,則該索引不會被使用到,反之,查詢中若包含「姓名」或 「姓名與科系」,則該索引會被使用。
  4. 欄位的資料型態為 TEXT、NTEXT、IMAGE 者不能成為索引的欄位。每一個 索引所使用的欄位最多只能包含 16 個欄位。
  5. 索引的建立:
    1. Enterprise Manager
      1. 選定表格後(如 books),按右鍵並選「所有工作 / 管理索引」。
      2. 「叢聚於磁碟上」的意思是盡量將資料在邏輯上的順序與實際在 磁碟上的順序保持一致;也就是在磁碟中盡量將有某種相關順序的資料擺放 在一起,以減少存取資料時在磁碟機中所花的搜尋時間,增進效率。因此, 依據某屬性所建立的索引,如果被設定為叢聚時,則稱為「叢聚索引」 (Clustering Index)。注意,一個表格只能建立一個叢聚索引。 請為 bookstores 之 no 、 books 之 id 、 與 orders 之 no 和 id 建立叢聚索引。
      3. 忽略重複值:若你的 index 設定為 unique,可是又不希望 已經存在的資料有重複時會造成錯誤,可以設定此選項來忽略。
      4. 填滿因數:在一索引上指定的 FILLFACTOR 值,可以用來告訴 SQL Server 如何將資料打包(pack)到索引資料分頁中。依據 Stephen Wynkoop 的說法,填滿因數應該少用。
      5. 卸除現存的: 用來指定索引應被刪除後重建。
    2. 當資料表中有設為 UNIQUE 的欄位時,則 SQL Server 會用此欄位自動建立一個 非叢集索引的唯一索引。
    3. SQL
      1. 語法:
              CREATE [UNIQUE] [CLUSTERED | NONCLUSTERED]
              INDEX index_name ON table (column [, ... n])
              
      2. 範例:
              create nonclustered index IDX_bookname on books(bookname)
              
  6. 顯示索引資訊:
    1. 語法:sp_helpindex table_name
    2. 範例:sp_helpindex books
  7. 刪除索引:
    1. 語法:DROP INDEX table_name.index_name
    2. 範例:DROP INDEX books.IDX_bookname



預儲程序(Stored Procedures)

預儲程序是一種可以寫在資料庫伺服器端的 SQL 程序,可以在伺服端 呼叫,也可以在客戶端呼叫。 (曾 7-99)
  1. 流程控制敘述:
    1. if ... else:
          if not exists (select * from bookstores where rank = 35)
            print '目前無 rank 為 35 的書局'
          
    2. begin ... end:
          if exists (select * from bookstores where rank=30)
          begin
            print 'record found'
            select no, name, city from bookstores where rank=30
          end
          
    3. while:
          /* 變數的宣告是將變數名稱前加上 @ */
          /* 宣告 x 為 int, x 為區域變數 */
          /* 若 x 前加入兩個 @,則 x 為全域變數 */
          declare @x int
      
          /* 設定 x 的初始值為 1 */
          select @x = 1
      
          while @x < 6
          begin
            /* + 號為將字串聯起來 */
            /* str() 是將整數轉換為字串 */
            print '書局編號為 ' + str(@x,3) + ' 的訂單資料有'
            select * from orders where no = @x
      
            /* 故意空出兩行 */
            print ' '
            print ' '
      
            /* 將 x 加 1 */
            select @x=@x+1
          end
          
    4. break:
          declare @x int
          select @x = 1
      
          while @x < 6
          begin
            select * from orders where no = @x
            print ' '
            print ' '
      
            /* 當 x 為 3 時,跳出迴圈 */
            if @x = 3
              break
      
            select @x=@x+1
          end
          
    5. continue:
          declare @x int
          select @x = 1
      
          while @x < 6
          begin
      
            /* 當 x 為 1 時,直接進入下一個迴圈 */
            if @x = 1
            begin
              select @x = @x + 1
              continue
            end
      
            select * from orders where no = @x
            print ' '
            print ' '
            select @x=@x+1
          end
          
    6. goto:
          declare @x int
          select @x = 1
          select @x = @x + 1
          goto finish
          select @x = @x * 2
        
          finish:
          print @x
          
    7. case:
          /* 值為相同時 */
          select '名稱'  = name, '排名' =
            case rank
              when 10 then '劣'
              when 20 then '良'
              when 30 then '優'
              else '特優'
            end
          from bookstores
          
          /* 值為某一區域時 */
          select '名稱'  = name, '排名' =
            case 
              when rank < 20 then '劣'
              when rank = 20 then '良'
              when rank > 20 then '優'
            end
          from bookstores
          
  2. 全域變數:請參考(林 50--51)
    全域變數說明
    @@connections以登入或試著登入系統的總數
    @@error系統最近的錯誤代號,0 為成功(比較重要的錯誤 訊息可參見 林 53)
    @@servername伺服器的名稱
    @@versionSQL Server 的時間與版本

  3. 新增基本預儲程序:
    1. 語法:
          CREATE PROCEDURE procedure_name 
          AS sql_statements
          
    2. 範例:
          create procedure all_orders
          as select * from orders
      
          all_orders
          
  4. 程序的參數:
        create procedure all_orders_1 (@n1 int, @n2 int) as
        select * from orders where no=@n1 and id=@n2
    
        all_orders_1 1, 1
        
  5. 回傳整數結果: RETURN 僅能回傳整數結果。
        create procedure all_orders_2 (@n1 int, @n2 int) as
        declare @c int
        select @c = count(*) from orders where no=@n1 and id=@n2
        return @c
    
        declare @count int
        exec @count = all_orders_2 1, 1
        print @count
        
  6. 回傳字串結果:
        create procedure all_orders_3 (@n1 int, @n varchar(20) output) as
        select @n = name from bookstores where no=@n1
        return
    
        declare @n varchar(20)
        exec all_orders_3 1, @n output
        print @n
        
  7. 回傳多個結果:
        create procedure all_orders_4 (@n1 int, @n varchar(20) output, @c varchar(20) output) as
        select @n = name, @c = city from bookstores where no=@n1
        return 
    
        declare @n varchar(20)
        declare @c varchar(20)
        exec all_orders_4 1, @n output, @c output
        print @n
        print @c
        

觸發程序(Triggers)

觸發程序可以看成是一種特殊的預儲程序,不過與預儲程序不同的地方是: 預儲程序是被動的,它需要被呼叫時才會執行,而觸發程序卻是 主動的,當某些預定的條件為真時(即事件產生時),它會主動 起來執行。 (曾 7-104)
  1. 觸發程序與規則以及預設的不同?
    SQL Server 會在將資訊寫入資料庫以前,套用系統中所定義的規則和預設。 但是觸發程序卻是在資料更動之後才去觸發 Triggers,因此 觸發程序可視為維持資料一致性的最後一道防線。
  2. 建立觸發程序: 當你建立一觸發時,你必須是資料庫的擁有人。
    1. 語法:
          CREATE TRIGGER trigger_name
          ON table_name
          FOR {INSERT | UPDATE | DELETE}
          [WITH ENCRYPTION]
          AS sql_statements
          
    2. 範例:廠商所下的訂單數量不得高於 50
          /* 建立觸發程序 orders_insert_note */
          create trigger orders_insert_note
          on orders
          for insert, update
          as
          declare @q int
          select @q = quantity from orders where quantity > 50
          if @q > 50
          begin
            rollback tran
      
            /* 產生錯誤訊息:
               第一個參數是訊息,第二個參數是嚴重等級,一般會設在 11 至 16 間,
               第三個參數是狀態(state) */
            /* 訊息錯誤碼以及其敘述可以從 sysmessages 中得知(可以執行
               select * from master.dbo.sysmessages) */
            raiserror('The quantity of orders must be less than 50 units',
                       16, 10)
          end
          
          /* 嘗試執行下列指令,剛剛建立的觸發程序會被自動執行 */
          insert into orders values(3,3,55)
          
    3. 範例: 若訂單數量更改,將發 email 通知
          create trigger update_quantity_note
          on orders
          for update
          as
      
          /* 當欄位 quantity 有任何更動 */
          if update(quantity) and (@@rowcount = 1)
          begin
            declare @mesg varchar(50)
            select @mesg = 'You have updated your orders'
      
            /* 經由 email 將通知發出去 */
            exec master.dbo.xp_sendmail @recipients = 'jllu@nchu.edu.tw',
                                        @message = @mesg
          end
          
    4. 啟動 SQL Mail: 首先請你在你的電腦上安裝 MAPI Enabled 的 電子郵件用戶端程式 (如 Exchange Client, Mail Client, or Outlook 等, 不過要注意的是 Outlook Express 不是 MAPI Enabled 的用戶端程式)
      1. 展開伺服器
      2. 展開「支援服務」並於「SQL Mail」上按右鍵
      3. 於「內容」內給予一 Profile 名稱(如 ec1)
      4. 在「SQL Mail」上按右鍵並選擇「Start」
    5. 範例: 通知取消的訂單
          create trigger delet_quantity_note
          on orders
          for delete
          as
            declare @mesg varchar(50)
            declare @q int
      
            /* DELETED 內包含剛剛被刪除和更改的資料 */
            /* INSERTED 內含剛剛新增的資料 */
            select @q = D.quantity from DELETED D
            select @mesg = 'You have deleted your orders of ' + str(@q)
                           + ' units'
      
            exec master.dbo.xp_sendmail @recipients = 'jllu@nchu.edu.tw',
                                        @message = @mesg
          
    6. 練習:請更改以上的範例,使得使用者於更改訂單數量後, 會收到更改(含更新前、後的數量)的通知。
  3. 顯示觸發資訊:
    1. sp_help trigger_name
    2. Enterprise Manager
      1. 選取你要處理的表格
      2. 按右鍵並選取「所有工作 / 管理觸發程序」

交易與鎖定

交易的特性在於交易只能全部完成或全部沒完成。在一般資訊系統, 需要確定是否交易完成的往往是涉及到兩個以上的 更動。例如,如果我們要將 bookstores 中 no 為 1 的資料去除, 我們一定要確認 orders 內的所有 no 為 1 的資料全部被刪除, 而且 bookstores 中 no 為 1 的資料被刪除。兩者缺一不可。 為達成以上的目標,SQL Server 提供了一些指令。
BEGIN TRAN;
DELETE FROM orders WHERE no = 1;
DELETE FROM bookstores WHERE no = 1;
SELECT * FROM orders;
SELECT * FROM bookstores;
由以上的範例中,我們可以了解 no 為 1 的資料已經刪除了, 可是交易尚未完成。上述交易不會完成直到「COMMIT TRAN」執行完畢。 這個時候我們如果後悔,我們可以使用「ROLLBACK TRAN」來恢復成 尚未執行交易前的狀態。 另外,我們也可以利用 TRAN 加上 T-SQL 的語法來完成平行調撥的手續。 在這個範例中,我們假設 ORDERS 的 QUANTITY 不得小於零並且已經在資料庫 設定該限制。假設我們將書店標號 1 對第五本書的訂單數量改給編號為 4 的 書店。這個 TRAN 會被設計成如下的指令檔
BEGIN TRAN;
UPDATE orders
SET quantity = quantity + 10
WHERE no = 4 AND id = 5;

IF @@ERROR > 0 AND @@ROWCOUNT <> 1
  GOTO NeedRollBack;

UPDATE orders
SET quantity = quantity - 10
WHERE no = 1 AND id = 5;

NeedRollBack:
IF @@ERROR > 0 AND @@ROWCOUNT <> 1
  ROLLBACK TRAN
ELSE
  COMMIT TRAN;

SELECT * FROM orders WHERE id = 5;
由於書店 1 的訂購數量為 10,因此更改完了以後將會使數量等於零,這並不符合 原先訂定的限制。所以,第二個 UPDATE 將出現 ERROR,整個 TRAN 也會被 ROLLBACK。 由上述的兩個範例當中,我們知道在交易進行當中,常常會發生兩個以上的使用者 需要更改同一筆記錄(如飛機訂位系統),這不但會造成資料的不一致,也會造成 商業糾紛。為解決此一問題,資料庫管理系統都會提供鎖定(locking) 的功能。一般來說,一個典型的更新交易過程是先從資料庫讀取資料, 再對這些資料進行『分享鎖定』,然後進入『排它鎖定』狀態來進行資料的更動。 SQL Server 提供各式的鎖訂方式 --- 從 row level 的鎖定、page level 的鎖定、到整個表格的鎖定。若資料庫管理者或系統開發人員 不自行設定鎖定的等級,SQL Server 會自動幫你決定較佳的鎖定方式。

  1. 隔離等級(isolation level)
    隔離等級主要是用來設定交易在『讀取』資料時的隔離狀態(在修改資料時則一定要 做完整鎖定,因此不必設等級)。SQL Server 提供的主要隔離等級有:
    1. READ UNCOMMITTED 等級:這是最低的隔離等級,它只能確保實際上 已經毀損的資料(physically corrupt data)不會被讀取。不建議使用! 執行兩個 sessions,由於在第一個 session 中,表格 orders 已經含有 未確認的資料,而且第二個 session 的隔離等級設定為 READ UNCOMMITTED, 所以已經更改但是還沒有 commit 的資料也可以讀的到。
      1. session 1:
                begin tran
                update orders set quantity=30 where no=4 and id=5
                
      2. session 2:
                set transaction isolation level read uncommitted
        
                /* 你可以看到沒 commit 但是已經 update 的資料 */
                select * from orders
                
    2. READ COMMITTED 等級: 這是 SQL Server 預設的等級, 在 READ COMMITTED 等級,SQL Server 不允許你存取 「dirty」 或未 確認的 (uncommitted)資料。你可以更進一步了解 READ COMMITTED 的運作方式:
      1. 執行兩個 sessions,由於在第一個 session 中,表格 orders 已經含有 未確認的資料,因此第二個 session 無法查詢而停在那裡。直到第一個 session rollback 或 commit 後,第二個 session 才會開始執行。
        1. session 1:
                  begin tran
                  update orders set quantity=30 where no=4 and id=5
                  
        2. session 2:
                  select * from orders
                  
      2. 執行兩個 sessions,第一個 session 對 orders 進行 row 鎖定且 更動資料,第二個 session 若希望對未被鎖定的資料進行查詢,它必須下達 readpast 的 locking hint。
        1. session 1:
                  begin tran
                  update orders with (rowlock) set quantity=30 where no=4 and id=5
                  
        2. session 2:
                  /* 只有非 dirty 的資料顯示出來 */
                  /* readpast: 略過鎖定的資料列。readpast 提示僅套用於在 READ COMMITTED 
                               隔離等級下運作的交易。僅套用於 SELECT 陳述式。 */
                  select * from orders with (readpast)
          
                  /* 依舊無法查詢 */
                  select * from orders
                  
    3. REPEATABLE READ 等級: SQL Server 允許你重複的存取同一筆資料, 並保證每一次所讀到的內容都相同。在這個設定下,其他交易不可以對正被讀取的 資料進行更改或刪除,但是別人仍然可以新增記錄。
      1. session 1:
              /* 保證已經讀取的資料不被更改 */
              set transaction isolation level repeatable read
              begin tran
              select * from orders
              
      2. session 2:
              /* 預設的 isolation level 為 read committed */
              begin tran
        
              /* 無法 update */
              update orders set quantity=quantity+5 where no=4 and id=5
        
              /* 但是可以 insert */
              insert into orders values(5,5,20)
              
    4. SERIALIZABLE 等級: 這是 SQL Server 提供的最高 隔離等級,所有的交易全部互相隔離。
      1. 你可以重複之前的範例,只是在 session 1 設定的等級為 serializable
      2. 在 session 2,現在便無法 update and insert。
    注意,隔離等級設的越高,對於同時性(concurrency)的負面影響越大。 至於需不需要每一筆交易都設定為 SERIALIZABLE 等級,這必須考量系統 的需求。例如,一本書的 editor 為書本的每一章邀請作者寫作,每一個 作者對這本書的更動僅及於他負責的那一章,因此對這本書的所有 交易不必設過高的隔離等級。
    (從 online books 的內容) Transactions must be run at an isolation level of repeatable read or higher to prevent lost updates that can occur when two transactions each retrieve the same row, and then later update the row based on the originally retrieved values. If the two transactions update rows using a single UPDATE statement and do not base the update on the previously retrieved values, lost updates cannot occur at the default isolation level of read committed.
  2. 設定隔離等級: 這個設定會對這一次的連線(current connection) 產生效力。設定後,並不會對其他的連線造成影響。
      set transaction isolation level (read uncommitted |
                                       read committed   |
                                       repeatable read  |
                                       serializable)
      
  3. 雖然 SQL Server 會動態的幫你選擇較佳的鎖定方式,不過瞭解鎖定的內涵 也是有幫助的。鎖定的意義在於使用者在更改某一筆記錄前先進行 鎖定,那麼其他使用者便無法對同一筆記錄作更改,不過需要 注意可能會形成死結(deadlock)。 SQL Server 鎖定的主要種類:
    1. 共享鎖定(shared lock): 共享鎖定允許多個交易同時 讀取(select)某一物件。當某一物件被一個或一個以上的交易 以共享鎖定的方式鎖定時,其他的交易無法對這一個物件進行修改。 當一個交易讀取完某一物件,其共享鎖定的狀態會被立即釋放 除非交易的隔離等級被設定為 REPEATABLE READ 或更高、或者 該交易所使用的 locking hint 要求要保留該共享鎖定。 當某資料被分享鎖定後,其他交易不得對他做排他鎖定。
    2. 排它鎖定(exclusive lock): 排它鎖定不允許其他交易對 被鎖定的物件進行任何活動(含 select、update)。
    3. 更新鎖定(update lock): 更新鎖定是介於共享鎖定與 排它鎖定之間的鎖定等級。當一個交易設定某一物件為更新鎖定時, 其他交易無法取得這一物件的更新鎖定;而當這個交易對這個物件進行 更動時,這物件的鎖定會自動變成排它鎖定;至於其他操作,則為 共享鎖定,這樣可以避免死結。因此,某資料有可能同時被更新鎖定 以及共享鎖定,只是在這資料被更新的時候,不能有其他共享鎖定加諸於他。
      1. session 1:
                 /* REPEATABLEREAD 類似於共享鎖定 */
                 begin tran
                 select * from orders with (REPEATABLEREAD)
                 
      2. session 2:
                 begin tran
                 select * from orders with (REPEATABLEREAD)
                 
      3. 在 session 2 先 ROLLBACK,然後
                 /* XLOCK 為除他鎖定,結果將是無法鎖定而等待 */
                 begin tran
                 select * from orders with (XLOCK)
                 
      4. 在 session 2 先停止 XLOCK,然後
                 begin tran
                 select * from orders with (UPDLOCK)
                 
  4. 明顯的(explicit)取得一鎖定
    利用定義在 SELECT、INSERT、 UPDATE、DELETE 的 locking hints。locking hints 的設定會 override 目前的隔離等級設定。注意,依照微軟的文件說明 SQL Server 查詢最佳化器 (Query Optimizer) 會自動做出決定。建議您只有在必要時才使用資料表層級的鎖定 提示來變更預設的鎖定行為。不允許某些鎖定層級可能會嚴重影響 concurrency。 SELECT ...... WITH (locking hints)
    1. NOLOCK:不發出分享鎖定的要求,也不承諾(honor)排它鎖定(exclusive lock)
    2. HOLDLOCK:保有一個共享鎖定(??好像文件有錯,請看範例)直到一個交易 完成,而非在查詢完後便立即釋放鎖定。請試一試這個範例:
      1. session 1:
                 begin tran
                 select * from orders with (HOLDLOCK)
                 
      2. session 2:
                 /* 猜一下,sql 2000 對 select 所下的 implicit lock 為何? */
                 select * from orders
        
                 /* 這個的 implicit lock 又為何? */
                 insert into orders values(2,3,40)
                 
        可以讀取 orders 的內容,但是由於資料已經被鎖定,因此無法進行新增, 必須等到交易結束後,新增的指令才會被執行。你可以執行 ROLLBACK TRANCOMMIT TRAN 來結束交易。因為無法新增, 可以推測 isolation level 為 serializable。
    3. REPEATBLEREAD:保證相當於 REPEATBLE READ 的隔離等級直到一個交易完成。
      1. session 1:
                 begin tran
                 select * from orders with (REPEATABLEREAD)
                 
      2. session 2:
                 /* select OK */
                 select * from orders
        
                 /* insert OK */
                 insert into orders values(2,3,40)
        
                 /* cannot update */
                 update orders set quantity=20 where no=2 and id=1
                 
    4. ROWLOCK:鎖定一個 row 而非整個表格或分頁。
      1. session 1:
                 begin tran
        
                 select * from orders with (ROWLOCK,REPEATABLEREAD)
                          where no=1 and id=1
                 
      2. session 2:
                 update orders set quantity=40 where no=1 and id=1
                 
        由於該 row 已經被鎖定,因此無法進行更新,必須等到交易結束後, 更新的指令才會被執行。但是由於其他 row 的資料並未被鎖定,因此下列 指令可以照常進行
                 update orders set quantity=40 where no=1 and id=2
                 insert into orders values(2,3,40)
                 delete from orders where no=2 and id=3
                 
    5. 有關其他 lockinig hints;如 PAGLOCK(分頁鎖定)、TABLOCK(表格鎖定) 等,請參考 online books。

  5. 練習題:請寫出一個 stored procedure 並用這個 stored procedure 來訂機票。假設機票只剩下一張,而由兩個同學同時執行這個 stored procedure, 成功的人,顯示取得機票,否則就是失敗。答案可以從之前的範例做出來, 或者可以看.....
  6. 顯示鎖定資訊
    1. sp_lock
    2. Enterprise Manager
      1. 展開資料庫伺服器
      2. 展開「管理 / 目前活動」
  7. 結束鎖定:
    1. 於顯示的鎖定程序(Process Info 內)上按右鍵,並選擇 「Kill Process」 來結束鎖定。
    2. KILL spid
        /* 先利用之前所說明的鎖定進行一 select 鎖定 */
        /* 找出有哪些鎖定 */
        sp_lock
      
        /* 看一下心目中的程序是哪一個? */
        /* 本例題中的 549576996 是由 sp_lock 找出的 */
        select object_name(549576996)
      
        /* 由於 549576996 的 spid 是 8 */
        kill 8
        








使用者權限管理

  1. 使用者的基本觀念:
    SQL Server 上的使用者分為兩類:第一種使用者是登入(Login),登入是 具有連上 SQL Server 的能力;至於登入後,可以存取哪些資料庫、 哪些資料庫表格等,則由使用者(User)的權限(permission)來規範。 由於一個使用者可能可以存取多項資料庫的資源,而且可能有多個使用者具有 相同的使用權限,所以 SQL Server 提供了角色(role)的做法,使得 具有相同的權限的使用者可以擁有相同的角色(類似群組 -- group -- 的觀念), 以便於管理。角色又分為伺服器角色(server role)與資料庫角色(database role); 資料庫角色又分為資料庫角色(database role)與應用程式資料庫角色 (application database role)。
    1. 伺服器角色:是一群由 SQL Server 已經定義好的角色,供資料庫管理員使用。 資料庫管理員不能刪除或更改伺服器角色。想了解有哪些伺服器角色, 展開資料庫伺服器(如 imsql),然後展開「安全性 / 伺服器角色」。
    2. 資料庫角色
      1. 資料庫角色: 最常見的角色就是 PUBLIC 這個角色,它是由 SQL Server 自動產生的,而且每一個新增的使用者都屬於這個角色。你也可以自己 新增一個角色經由展開資料庫(如 Bob),並選擇「角色」來新增。你可以於 新增完該角色後,進行權限的設定。設定的方式是選擇該角色,並按右鍵選取 「內容 / 權限」來進行。
      2. 應用程式資料庫角色:它是由應用程式所啟動的。在預設的情形下, 應用程式資料庫角色不會被啟動也不會有任何的使用者。當一程式使用 某一應用程式資料庫角色登入時,所有身分認證工作都會被忽略掉,而改用 應用程式資料庫角色的密碼來確認身分。
  2. 範例: 新增兩個使用者,一個為 bob,其權限為可以在資料庫 Bob 內產生、修改表格等動作,而另一個使用者為 guest,guest 只能在 資料庫 Bob 中瀏覽。
  3. 新增登入:產生 bob 與 guest
    1. 利用 Enterprise Manager
      1. 展開資料庫伺服器
      2. 展開「安全性」,並選擇「登入」
      3. 按右鍵並選擇「新增登入」
    2. 利用 Stored Procedure
          sp_addlogin login_id [, password [, defaultdb]]
          
  4. 新增資料庫角色:產生 developer 與 browse,並使得 bob 與其他 開發人員具有 developer 角色,且使得 guest 具有 browse 的角色。
    1. 利用 Enterprise Manager
      1. 展開資料庫(如 Bob)
      2. 選擇「角色」並按右鍵來選擇「新增資料庫角色」來產生新角色。
      3. 於新增完 developer 與 browse 後,在其上分別按右鍵 並選擇「內容」以進行權限的設定。在「資料庫角色屬性」 的對話視窗內,選擇「權限」來進行設定。
      4. 將 browse 設定為可以 select 後,利用 isql 登入,並進行 查詢。並試著 insert 一筆記錄到 orders 內,你應該會得到錯誤訊息。
      5. 將 developer 設定為可以 select、insert、update、delete, 利用 isql 登入,並進行查詢。並試著 insert 與 delete 一筆記錄到 orders 內。
    2. 利用 Stored Procedure:先 use Bob
      語法範例
      sp_adduser login_idsp_adduser 'bob'
      sp_addrole rolesp_addrole 'developer'
      sp_addrolemember role, accountsp_addrolemember 'developer','bob'

    3. 除了 select、insert、update、delete 外,還有哪些權限?
      1. EXEC: 可以讓你執行預儲程序
      2. DRI/REFERENCES (Declared Referential Integrity): 可以讓你為一表格加上外來鍵的限制
      3. ALL:代表所有權限,需要 sysadmin 或者 db_owner 的成員才能設定。
      4. SQL Server 2000 也支援 data-item security,也就是說你可以選擇 「資料行」來對某些特定的欄位做 select 以及 update 的權限設定。

  5. 權限的授與與取消
    可以使用 Enterprise Manager 來授與與取消某些角色或 使用者的權限,也可以利用 GRANT 與 REVOKE 的 Transact-SQL 的指令來達成同樣的目標。
    語法範例
    GRANT permission_list
    [fieldname1, fieldname2, ...] 
    ON object_name
    TO name_list
    
    // 將資料表 orders 內欄位 id 的 select 權力授權給 guest
    GRANT select
    (id) ON orders
    TO guest
    
    GRANT select,delete,update,insert
    ON orders
    TO bob
    
    REVOKE permission_list
    ON object_name
    FROM name_list
    
    REVOKE select
    ON orders
    FROM guest
    
    REVOKE update,delete
    ON orders
    FROM bob
    




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





MySQL Server 簡介

MySQL Server 簡介

The materials presented in this web page is provided as is and is used solely for educational purpose. Use at your own risks.
Written by: 國立中興大學資管系呂瑞麟 Eric Jui-Lin Lu

請勿轉貼
看其他教材

本文假設你已經安裝了 MySQL Server 5.1.x 或者 5.5.x 版,其下載的地點為 http://dev.mysql.com/downloads/。 MySQL Server 也可以安裝於 Win32 的環境,如果讀者想在 Windows 的環境下 安裝 "Noinstall Zip Archive" 版的 MySQL,可以參考 在 Windows 安裝 "Noinstall Zip Archive" 版的 MySQL 5.1.x 或者 5.5.x 版的安裝說明
  1. 準備工作:產生一個新的使用者使其能夠成為某個資料庫的擁有者,以下 我們假設要產生一個使用者 jlu 並使他成為名為 eric 資料庫的擁有者,而且 允許這個使用者能從任何電腦連到這個資料庫。這個步驟主要是給資料庫 管理員的,而你需要使用 mysql 這個執行檔。
  2. 在資料庫中建立表格 (tables): 首先我們以之前建立的使用者 jlu 來登入 MySQL,並在資料庫 eric 中 建立表格;例如 Product。
  3. 利用 JDBC-ODBC 驅動程式來存取資料庫: 我們分別使用 JDBC-ODBC 以及 純 JDBC 驅動程式來存取之前之前建立的 資料庫以及其表格。
  4. 利用純 JDBC 驅動程式來存取資料庫
  5. MySQL Server 與中文:這篇文章說明了 MySQL 的預設編碼方式,並說明如何將資料適當的轉碼以便於處理中文,而不會 造成亂碼的方式以及概念。





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



存貨管理系統 - MySQL (Part VI):刪除

第二個範例 (Part VI):刪除

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

請勿轉貼
看其他教材

刪除存貨資料

刪除存貨資料的工作也是分成兩個主要的步驟,而且其步驟跟之前的 add() 和 update() 的程式碼類似:若使用者點選某一項工作,被選擇的工作項目以及 其相關值,會被複製到相對應的輸入欄位(這步驟與 update() 的第一個部分 相同);然後,等到使用者點選"刪除"按鈕,程式需要把資料從資料庫中刪除, 然後把輸入欄位內的資料清除(清除資料的部分跟 add() 的最後工作相同)。 首先,因為第一個步驟的工作與 update() 相同,因此程式碼不需要做任何 更動。然後,我們需要註冊以及定義使用者點選"刪除"按鈕時的事件處理機制。 在本範例中,該事件會觸發 delete() 方法,所以 我們必須更改刪除的按鈕標籤如下:
<button label="刪除" width="46px" height="24px" onClick="delete()"/>
跟其他 Java 程式相同,請在 <zscript> 標籤內加入 delete() 方法如下:
  void delete() {
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
      stmt = conn.createStatement();
      String dSQL = "delete from Product where id=" + num.value;
      if (stmt.executeUpdate(dSQL) &lt;= 0)
        throw new SQLException("資料刪除失敗");
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    // 下列兩個敘述的順序不可以對調
    // 確保刪除的資料也從 box 和 allItems 內消失
    allItems.remove(box.getSelectedIndex());
    box.removeItemAt(box.getSelectedIndex());
    clearData();
  }

  void clearData(){
    // 清除輸入欄位
    num.value = null;
    name.value = null;
    price.value = null;
    qty.value = null;
  }
從程式碼中,我們可以清楚的看出,資料刪除分成三個動作:第一個動作 就是把使用者選擇的 Product 物件從資料庫中刪除(其中包含資料庫的連結 以及 SQL 的 delete 指令);第二個動作就是把使用者選擇的 Product 物件從 allItems 以及 box 中刪除;刪除的方式是利用 Listbox 物件的 getSelectedIndex() 來決定該物件的索引位置,然後分別利用 remove(索引位置) 和 removeItemAt(索引位置) 把該物件從 allItems 和 box 刪除;第三個動作就是把輸入欄位的值清除, 由於這一段程式碼跟 add() 重複,為了降低維護成本,我們將這個動作的 四個敘述包成一個 clearData() 方法,然後在 delete() 以及 add() 的最後 呼叫該方法。(請記得將 add() 方法做適當的修改) 以下的執行畫面為將之前 ccc 的資料刪除後的結果:
為了方便起見,我們把整個存貨管理的程式碼列示如下:
<?xml version="1.0" encoding="Big5"?>
<window title="存貨管理系統" width="640px" border="normal" mode="highlighted">
<zscript>
  // 除了方法的部分,其他的部分只會執行一次
  import java.sql.*;
  import java.util.*;


  Statement stmt = null;
  Connection conn = null;
  List allItems = new ArrayList();
  try {
    Class.forName("com.mysql.jdbc.Driver");
    conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
    stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("select * from Product");

    Product p;
    while(rs.next()) {
      p = new Product();
      p.setId(rs.getInt(1));
      p.setName(rs.getString(2));
      p.setPrice(rs.getDouble(3));
      p.setQty(rs.getInt(4));
      allItems.add(p);
    }
  } catch (SQLException e) {
    e.printStackTrace();
  } finally {
    try {
      stmt.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
    try {
      conn.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }

  // 資料新增 ==========================================================
  void add() {
    // 確保新增的資料也在 allItems 內
    Product newp = new Product(num.value.intValue(), name.value, 
                               price.value.doubleValue(), qty.value.intValue());
    allItems.add(newp);

    // 經資料新增至資料庫
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
      stmt = conn.createStatement();
      String iSQL = "insert into Product values(" + num.value + ",'" +                           
                     name.value + "'," + price.value + "," + qty.value + ")";
      if (stmt.executeUpdate(iSQL) &lt;= 0)
        throw new SQLException("資料新增失敗");
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    // 將新增資料形成一個 Listitem 物件(或者節點) 
    Listitem li = new Listitem(); 
    li.setValue(newp);  // 在之後的 update, delete 會用到
    li.appendChild(new Listcell(num.value.toString())); 
    li.appendChild(new Listcell(name.value)); 
    li.appendChild(new Listcell(price.value.toString())); 
    li.appendChild(new Listcell(qty.value.toString())); 

    // 將 Listitem 物件變成 box 的子節點
    // box 是 listbox 的 id 值
    box.appendChild(li);

    // 清除輸入欄位
    clearData();
  }

  // 資料修改 ==========================================================
  void move() {
    // 確保修改的資料也在 allItems 內
    Product p = (Product) box.selectedItem.value;
    num.value = p.getId();
    name.value = p.getName();
    price.value = new java.math.BigDecimal(p.getPrice());
    qty.value = p.getQty(); 
  }

  // update()
  void update(){
    // 修改 box 和 allItems 物件的內容
    Product upp = (Product) box.selectedItem.value;
    upp.setId(num.value.intValue());
    upp.setName(name.value);
    upp.setPrice(price.value.doubleValue());
    upp.setQty(qty.value.intValue());
    allItems.set(box.getSelectedIndex(), upp);

    // 修改資料庫的內容
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
      stmt = conn.createStatement();
      String uSQL = "update Product set name='" + name.value + "', price=" + price.value + 
                    ", qty=" + qty.value + " where id=" + num.value;
      if (stmt.executeUpdate(uSQL) &lt;= 0)
        throw new SQLException("資料新增失敗");
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    // 修改 listbox 的內容
    List children = box.selectedItem.children;
    ((Listcell)children.get(0)).label = num.value.toString();
    ((Listcell)children.get(1)).label = name.value;
    ((Listcell)children.get(2)).label = price.doubleValue().toString();
    ((Listcell)children.get(3)).label = qty.value.toString(); 
  }

  // 資料刪除 =========================================================
  void delete() {
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpassw");
      stmt = conn.createStatement();
      String dSQL = "delete from Product where id=" + num.value;
      if (stmt.executeUpdate(dSQL) &lt;= 0)
        throw new SQLException("資料刪除失敗");
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    // 下列兩個敘述的順序不可以對調
    // 確保刪除的資料也從 allItems 內消失
    allItems.remove(box.getSelectedIndex());
    box.removeItemAt(box.getSelectedIndex());
    clearData();
  }

  // ==============================================================
  void clearData(){
    // 清除輸入欄位
    num.value = null;
    name.value = null;
    price.setText("");
    qty.value = null;
  }

</zscript>
  <listbox id="box" multiple="true" rows="4" onSelect="move()">
    <listhead>
      <listheader label="料號" width="50px" />
      <listheader label="品名" />
      <listheader align="right" label="價格" width="60px" />
      <listheader align="right" label="數量" width="60px" />
    </listhead>
    <listitem forEach="${allItems}" value="${each}">
      <listcell label="${each.id}"/>
      <listcell label="${each.name}"/>
      <listcell label="${each.price}"/>
      <listcell label="${each.qty}"/>
    </listitem>
  </listbox>
  <groupbox>
    <caption label="存貨管理"/>
    料號: <intbox id="num" cols="5" />
    品名: <textbox id="name" cols="25" />
    價格: <decimalbox id="price" cols="8" />
    數量: <intbox id="qty" cols="8" />
    <div>
    <button label="新增" width="46px" height="24px" onClick="add()"/>
    <button label="修改" width="46px" height="24px" onClick="update()"/>
    <button label="刪除" width="46px" height="24px" onClick="delete()"/>
    </div>
  </groupbox>
</window>
我們的範例程式到此已經介紹完畢,可是我們要提醒讀者:這個系統只是 設計給單一使用者的;也就是說,這個系統的設計是假設一次只有一個使用者 在使用。試試看:打開兩個瀏覽器的視窗,然後分別開啟我們剛剛設計好的系統, 並分別在兩個視窗中新增資料,請觀察新增的資料是否立即反應再另一個視窗中。 練習題: 請新增一個"清除"的按鈕,使用者點選它之後,會把所有欄位的輸入 或者選擇的資料清除。

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





存貨管理系統 - MySQL (Part V):修改

第二個範例 (Part V):修改

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

請勿轉貼
看其他教材

修改存貨資料

修改存貨資料分成兩個主要的步驟:若使用者點選某一項工作,被選擇的工作項目以及 其相關值,會被複製到相對應的輸入欄位;然後,等到使用者修改完資料後,使用者 需要按"修改"按鈕來完成修改工作。 首先,我們要為 <listbox> 註冊一個 onSelect 的事件處理方法 move(); 該註冊的事件處理過程為:一旦使用者選擇了(也就是觸動了 onSelect 事件) <listbox> 中的某一個存貨資料,ZK 開始執行 move() 方法,而該方法所做的 事情就是把該存貨資料的值分別複製到相對應的輸入欄位。註冊的原始碼如下:
<listbox id="box" multiple="true" rows="4" onSelect="move()">
而 move() 的程式碼定義如下:
  void move() {
    Product p = (Product) box.selectedItem.value;
    num.value = p.getId();
    name.value = p.getName();
    price.value = new java.math.BigDecimal(p.getPrice());
    qty.value = p.getQty();
  }
這一段程式碼中,兩個綠色的部分需要特別說明一下:在之前介紹 forEach 和 add() 方法中,我們曾經說過,有一行程式碼(li.setValue(newp);) 是在 add() 中沒有特別用途,但是卻對 update 或者 delete 有幫助的; 這一行程式碼基本上是為 Listitem 物件的 value 屬性設定一個屬性值,而該值 是該 Listitem 所代表的存貨資料物件(即 Product)。經過這樣的設定,在 move() 方法中,我們就可以經由 box.selectedItem.value 的方式取得被使用者 點選的 Listitem 物件中的 Product 物件;由於 .value 傳回的是 Object 物件, 因此需要將它強迫轉型為 Product。有了 Product 物件之後,我們就可以經由其相關的 get 方法(getId()、getName()、getPrice()、getQty())取得值,並將其指定給 輸入欄位 num、name、price、和 qty。程式碼中的第二個綠色部分也需要說明一下: 由於 price.value 的資料型態是 BigDecimal,但是 p.getPrice() 回傳的卻是 double,所以我們必須經由 new java.math.BigDecimal(p.getPrice()) 將 double 轉換成 BigDecimal。 在這裡,我們先測試一下目前的工作是否 正確,以下的執行畫面是在點選料號為 4 的存貨之後的結果,請留意輸入欄位內的值:

在確定上一個步驟的正確性之後,使用者可以在輸入欄位內做必要的修改。 使用者在修正完必要的資料後,按下"修改"按鈕,這會觸發一個事件, 因此我們需要定義一個該事件的處理方法,在本範例中稱之為 update()。 我們必須更改修改的按鈕標籤如下:
<button label="修改" width="46px" height="24px" onClick="update()"/>
跟其他 Java 程式相同,請在 <zscript> 標籤內加入 update() 方法如下:
  void update(){
    // 修改 box 和 allItems 物件的內容
    Product upp = (Product) box.selectedItem.value;
    upp.setId(num.value.intValue());
    upp.setName(name.value);
    upp.setPrice(price.value.doubleValue());
    upp.setQty(qty.value.intValue());
    allItems.set(box.getSelectedIndex(), upp);

    // 修改資料庫的內容
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
      stmt = conn.createStatement();
      String uSQL = "update Product set name='" + name.value + "', price=" + price.value + 
                    ", qty=" + qty.value + " where id=" + num.value;
      if (stmt.executeUpdate(uSQL) &lt;= 0)
        throw new SQLException("資料修改失敗");
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    // 修改 listbox 的內容
    List children = box.selectedItem.children;
    ((Listcell)children.get(0)).label = num.value.toString();
    ((Listcell)children.get(1)).label = name.value;
    ((Listcell)children.get(2)).label = price.doubleValue().toString();
    ((Listcell)children.get(3)).label = qty.value.toString(); 
  }
跟 add() 類似,update() 方法中的第一件事情就是從輸入欄位取得資料;這個可以 從程式碼中的 num.value.intValue()、name.value、price.value.doubleValue()、 和 qty.value.intValue() 可以看的出來。 取出的資料要用來修改被使用者選取的 Product 物件,也就是 box.selectedItem.value 的值;再經由該物件的 set 方法(setId()、setName()、setPrice()、和 setQty())來更新該物件的內容。跟 add() 類似,我們需要更新 box、allItems、 以及資料庫的資料。 最後,輸入欄位內的資料必須覆蓋到存貨管理系統中之前被使用者選擇的存貨資料。 從上列的"修改 listbox 的內容"的那一段程式碼,我們可以看到程式利用 box.selectedItem.children 取得使用者選擇的待辦事項的所有子節點; 在本範例中,總共會有四個 Listcell 的子節點。然後,程式利用 children.get(0) 取得第一個子節點,然後轉型為 Listcell 物件,最後利用 .label 取得該物件 的 label 屬性,並以輸入欄位的值來取代 label 的屬性值。 以下的執行畫面為將之前 aaa 的資料改成 bbb 的結果:



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








存貨管理系統 - MySQL (Part IV):新增


第二個範例 (Part IV):新增

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

請勿轉貼
看其他教材

新增存貨資料

顯示完所有的待辦事項後,需要完成的就剩下視窗下半部的新增、修改、以及 刪除。一般來說,這三個步驟習慣上會先實作新增;新增的資料顯示後,我們 緊接著實作修改;等到修改後的資料也正確了,最後就實作刪除,把剛剛新增的 資料除去;這樣的順序可以確保所有的功能都被完成,而且不會留下不必要的 測試資料。 資料的新增、修改、以及刪除在程式設計上需要考量的部分比較多,讓我們拿 之前 Part II 的顯示來說明。由於存貨資料不但儲存在資料庫中,為了能夠讓資料能夠 在畫面上正確的處理,程式裡面還有兩個地方包含有存貨資料,一個是 allItems,另一個是在 id 為 box 的 <listbox> 內(嚴格來說,是在其子元素 <listitem> 內);也就是說,每一次資料庫內的資料改變,其相對應的 allItems 以及 box 都需要跟著改變。
有了以上的概念,我們先說明資料的新增處理;新增存貨資料的步驟如下:使用者 在輸入欄位內輸入資料之後,點選"新增"按鈕;在按下"新增"按鈕之後, 資料庫、allItems、以及 box 的資料隨之增加,最後,將輸入欄位內的資料 清除。
點選"新增"按鈕的處理機制: 在使用者按下"新增"按鈕之後,這會觸發一個事件,因此我們需要定義一個 該事件的處理方法,在本範例中稱之為 add()。add() 方法需要完成的事情包含 從輸入欄位取得資料,然後把資料新增到資料庫,最後重新更新畫面。首先, 我們先註冊事件,必須更改新增的按鈕標籤如下:
<button label="新增" width="36px" height="24px" onClick="add()"/>
其中,綠色的部分就是告訴 ZK:如果使用者點選了"新增"按鈕,請執行 add() 方法。跟其他 Java 程式相同,請在 <zscript> 標籤內加入 add() 方法如下:
  void add() {
  }
在 add() 方法中的第一件事情就是從輸入欄位取得資料。請參考 Part I 中的 四個輸入欄位,分別是 num、name、price、和 qty;ZK 提供了一個 非常方便的方式(非常類似 Javascript)來取得這些欄位的值,那就是在這些 名稱的後面直接加上 .value;取得了輸入欄位的值之後,我們就可以初始化 一個新的 Product 物件如下,並將該物件加到 allItems 內:
    Product newp = new Product(num.value.intValue(), name.value, 
                               price.value.doubleValue(), qty.value.intValue());
    allItems.add(newp);
由於 Product 物件的第一個資料成員是 id,其資料型態為 int,可是在利用 .value 的方式取得該輸入欄位的值,其資料型態會依據該輸入欄位的 型態而回傳適當的資料型態;由於料號的輸入欄位型態為 intbox,因此 num.value 的資料型態為 java.lang.Integer,所以我們需要藉由 intValue() 的用法,把它改成 int;同樣的,第四個資料成員 qty, 也需要同樣的轉換; Product 物件的第三個資料成員是 double,而其相對應的輸入欄位型態為 decimalbox,利用 price.value 會回傳 java.Math.BigDecimal 資料型態,因此我們需要使用 doubleValue() 將資料 轉換成 double;至於 name,由於其資料型態為字串,且其輸入欄位型態為 textbox(回傳 java.lang.String),因此不需要轉換。 (請注意,intValue() 和 doubleValue() 是 ZK 提供的功能)。 將新增的 Product 物件加到 allItems 內之後,我們可以將輸入資料加到資料庫中, 其方式如下:
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
      stmt = conn.createStatement();
      String iSQL = "insert into Product values(" + num.value + ",'" +                           
                     name.value + "'," + price.value + "," + qty.value + ")";
      if (stmt.executeUpdate(iSQL) &lt;= 0)
        throw new SQLException("資料新增失敗");
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
新增資料的程式碼跟以前介紹的 Java 程式碼幾乎相同,因此我們不多做介紹。 但是這一類的程式如果在 ZK 的環境下執行,有一個地方要特別注意:所有在 Java 程式中,如果出現 <(小於)或者 >(大於)的符號,是不能直接 使用 < 或者 >,而必須分別使用 &lt; 或者 &gt; 來替代;以範例中的程式碼為例,我們使用一個 if 敘述來判斷 stmt.executeUpdate(iSQL) 是否成功;也就是說資料是否成功的新增 到資料庫中,stmt.executeUpdate(iSQL) 會回傳 1,如果有 1 筆資料被影響; 因此,我們判斷 stmt.executeUpdate(iSQL) 小於等於零時,就代表新增失敗; 所以,在程式中的 if 敘述,我們寫的是 stmt.executeUpdate(iSQL) &lt;=0,而不是 stmt.executeUpdate(iSQL) <=0。 到這個步驟,新增加的代辦事項已經加到資料庫以及 allItems,剩下的工作就是 畫面的更新(也就是把資料加到 box 內)。 畫面的更新分成兩個部分:一個是將新增的資料顯示在清單中;另一個就是 把輸入欄位裡面的資料清除掉。新增資料到清單的工作非常類似 DOM 的新增 節點,其程式碼如下:
01      Listitem li = new Listitem(); 
02      li.setValue(newp);  // 在之後的 update, delete 會用到
03      li.appendChild(new Listcell(num.value.toString())); 
04      li.appendChild(new Listcell(name.value)); 
05      li.appendChild(new Listcell(price.value.toString()));
06      li.appendChild(new Listcell(qty.value.toString()));
07      box.appendChild(li);
在第 01 行中,我們新增了一個 Listitem 的物件;Listitem 物件代表 <listbox> 標籤中的一個 <lisitem> 標籤。在第 03 到 06 行,我們需要為 Listitem 物件新增四個子節點,而每一個節點都是一個 Listcell 物件;也就是說, new Listcell(name.value) 會產生 <listcell>name.value</listcell> 標籤,而 name.value 代表 name 欄位內的值。除了 name 之外,其他三個欄位 的回傳資料型態都不是字串,因此其他的回傳值都需要再經過 toString() 將資料轉換成字串。最後,我們在第 07 行把 Listitem 物件(內含四個 Listcell 子節點)加到 box(box 是 <listbox> 的 id 值)下,並成為它的子節點。新增完了之後,ZK 會自動 refresh 畫面。 在上述程式碼中,如果目的只有呈現新的資料,那麼第 02 行的程式碼是不需要的。 第 02 行的目的在於為 Listitem 物件的 value 屬性(還記得之前介紹的 forEach 嗎?)加上一個 newp 的屬性值,這個 屬性值對目前的工作是沒有必要的,而它加入的目的在於讓之後的 update 和 delete 用的。
最後,就是把欄位資料清空,清空的方式非常簡單,直接把欄位值設定為 null 即可;例如,把 name 欄位的資料清空的方式就是 name.value = null;; 其他三個欄位也比照辦理即可。執行整個程式碼,並新增資料後的畫面如下:

add() 的完整程式碼如下:
  void add() {
    // 確保新增的資料也在 allItems 內
    Product newp = new Product(num.value.intValue(), name.value, 
                               price.value.doubleValue(), qty.value.intValue());
    allItems.add(newp);

    // 經資料新增至資料庫
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
      stmt = conn.createStatement();
      String iSQL = "insert into Product values(" + num.value + ",'" +                           
                     name.value + "'," + price.value + "," + qty.value + ")";
      if (stmt.executeUpdate(iSQL) &lt;= 0)
        throw new SQLException("資料新增失敗");
    } catch (SQLException e) {
      e.printStackTrace();
    } finally {
      try {
        stmt.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
      try {
        conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    // 將新增資料形成一個 Listitem 物件(或者節點) 
    Listitem li = new Listitem(); 
    li.setValue(newp);  // 在之後的 update, delete 會用到
    li.appendChild(new Listcell(num.value.toString())); 
    li.appendChild(new Listcell(name.value)); 
    li.appendChild(new Listcell(price.value.toString())); 
    li.appendChild(new Listcell(qty.value.toString())); 

    // 將 Listitem 物件變成 box 的子節點
    // box 是 listbox 的 id 值
    box.appendChild(li);

    // 清除輸入欄位
    num.value = null;
    name.value = null;

    // 嗯,在 XP + Tomcat 6.x + ZK 5.0.x 的組合下,price.value 會出現
    // NullPointerException;需要改成 price.setText("");
    // 但是,Win7 x64 + Tomcat 5.5.x + ZK 5.0.x 的組合下,
    // price.null 卻 OK。
    //price.value = null;
    price.setText("");
    qty.value = null;
  }
請注意:雖然這個程式到目前看起來一切 OK,實際上它的設計是有瑕疵的, 例如,請問如果 allItems 新增了之後,但是資料庫的新增卻由於網路斷線 而無法新增,請問這該怎麼辦? 練習題: 請重新設計範例程式,使得只有在資料庫正確的更新的之後, allItems 和 box 才能更新。

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











存貨管理系統 - MySQL (Part III)

第二個範例 (Part III)

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

請勿轉貼
看其他教材

ZK 與資料庫的結合

在建立完 ZK 的畫面,並且安裝完資料庫之後,剩下的就是如何讓 ZK 與資料庫 來互動;基本上,這就是開始使用 ZK 的事件處理機制。在之前的 Ajax 的範例, 我們使用 Javascript 來完成這個部分,可是由於 ZK 可以與多種語言結合,而且 其預設的語言是 Java,所以我們也以 Java 來完成這項工作。 ZK 和 Java 連結使用的方式有以下三種:
  1. 直接將 Java 程式碼寫在 zul 檔案內,而這些程式碼只需要放在 <zscript> 標籤內即可,它就可以像 JSP 一樣,在載入 zul 時就可以 執行。
  2. 把 Java 的原始碼放置於另一個檔案內,然後讓 zul 檔把它引入。
  3. zul 檔可以使用已經編譯好的 Java 類別檔(即 .class 檔)。
在本範例中,我們使用了第一種和第三種方式。第三種的使用方式就像 在 Part II 中說明的 Product 類別;至於第一種的使用方式 就是 Part III 的重點。在載入 inv.zul 網頁的時候,我們希望能夠 先到資料庫中把已經存在的存貨資料載入,並放到視窗內。為了 達成這個目標,我們首先把資料載入,載入的方式如下所示, 而 <zscript> 需要放在 <window> 內。
<zscript>
  // 除了方法,其他的部分只會執行一次
  import java.sql.*;
  import java.util.*;


  Statement stmt = null;
  Connection conn = null;
  List allItems = new ArrayList();
  try {
    Class.forName("com.mysql.jdbc.Driver");
    conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
    stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("select * from Product");

    Product p;
    while(rs.next()) {
      p = new Product();
      p.setId(rs.getInt(1));
      p.setName(rs.getString(2));
      p.setPrice(rs.getDouble(3));
      p.setQty(rs.getInt(4));
      allItems.add(p);
    }
  } catch (SQLException e) {
    e.printStackTrace();
  } finally {
    try {
      stmt.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
    try {
      conn.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }
</zscript>
這個程式跟 MySQL Server 簡介 中介紹的 Java 程式非常類似,因此我們只針對比較不同的地方做說明。 第一個是例外處理的部分,我們將 stmt.close();conn.close();、 以及其他資料庫連接的部分分開,如此一來可以比較容易的找出錯誤;我們建議 讀者甚至可以把"連接資料庫"所可能產生的例外分開處理,這個部分就當作 練習題了。第二個不一樣的地方在於綠色的部分,我們宣告了一個 List allItems = new ArrayList();;ArrayList 可以把它看成一個大小可以變動 的陣列,因此我們可以將任意數量的資料加入 allItems 中;除了這項方便性 之外,宣告成 List 的主要原因是要配合使用 ZK 的 forEach 指令。 經過上述程式的執行,它會將資料庫中的資料一筆一筆的放到 allItems 中, 而 allItems 中的每一筆資料都是一個 Product 的物件。 有了資料,我們需要一個機制把資料放到 <listitem> 內。為了能夠 簡易的做到這一點,ZK 提供了一個非常強的功能,也就是 <listitem> 的 forEach 屬性,其原始碼如下:
    <listitem forEach="${allItems}" value="${each}">
      <listcell label="${each.id}"/>
      <listcell label="${each.name}"/>
      <listcell label="${each.price}"/>
      <listcell label="${each.qty}"/>
    </listitem>
forEach 的用法需要特別仔細的說一下:forEach 的參數值是一個集合物件, 如 List,在本範例中就是 allItems;請注意,如果程式碼中 List 物件 的名稱改成 xxx,那麼 forEach 的屬性值也必須改成 xxx。如果直接把 forEach 定義成 forEach="allItems",ZK 處理器無法判斷屬性值可以直接 使用,還是一個變數的名稱,因此 ZK 採用跟 JSP 一樣的 EL-expression, 如果看到 ${allItems},則 ZK 會從變數 allItems 中取得其值, 並指定給 forEach。在 forEach 的每一個迴圈中,forEach 會從 List 物件 (也就是 allItems)一次取得一個物件,而在本範例中,該物件的資料型態 為 Product,而且該物件的名稱被定義為 each;因此,在每一個 <listcell> 中,我們就可以 each 的資料成員名稱 來取得 Product 物件中的資料成員。以上述程式碼為例,${each.id} 會取得 Product 物件的 id 成員;同樣的道理, ${each.name}、${each.price} 和 ${each.qty} 分別取得該物件的 name、price 和 qty 資料成員。 最後,原始碼的綠色部分在目前是可以不必定義的,該定義是指定 each 物件(也就是 Product 物件)當作是 Listitem 物件的 value 屬性值。value 屬性在之後的修改以及刪除的時候才會用到。
執行該段程式碼後,執行的畫面如下:

我想這個時候,把整個完整的 zul 碼列示出來應該有幫助,這裡也先當一個 檢查點,以便確定從安裝到現在,一切都正常的運行:
<?xml version="1.0" encoding="Big5"?>
<window title="存貨管理系統" width="640px" border="normal" mode="highlighted">
<zscript>
  // 除了方法的部分,其他的部分只會執行一次
  import java.sql.*;
  import java.util.*;


  Statement stmt = null;
  Connection conn = null;
  List allItems = new ArrayList();
  try {
    Class.forName("com.mysql.jdbc.Driver");
    conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1/eric", "jlu", "newpasswd");
    stmt = conn.createStatement();
    ResultSet rs = stmt.executeQuery("select * from Product");

    Product p;
    while(rs.next()) {
      p = new Product();
      p.setId(rs.getInt(1));
      p.setName(rs.getString(2));
      p.setPrice(rs.getDouble(3));
      p.setQty(rs.getInt(4));
      allItems.add(p);
    }
  } catch (SQLException e) {
    e.printStackTrace();
  } finally {
    try {
      stmt.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
    try {
      conn.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }

</zscript>
  <listbox id="box" multiple="true" rows="4">
    <listhead>
      <listheader label="料號" width="50px" />
      <listheader label="品名" />
      <listheader align="right" label="價格" width="60px" />
      <listheader align="right" label="數量" width="60px" />
    </listhead>
    <listitem forEach="${allItems}" value="${each}">
      <listcell label="${each.id}"/>
      <listcell label="${each.name}"/>
      <listcell label="${each.price}"/>
      <listcell label="${each.qty}"/>
    </listitem>
  </listbox>
  <groupbox>
    <caption label="存貨管理"/>
    料號: <intbox id="num" cols="5" />
    品名: <textbox id="name" cols="25" />
    價格: <decimalbox id="price" cols="8" />
    數量: <intbox id="qty" cols="8" />
    <div>
    <button label="新增" width="46px" height="24px"/>
    <button label="修改" width="46px" height="24px"/>
    <button label="刪除" width="46px" height="24px"/>
    </div>
  </groupbox>
</window>

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