| 後一頁 前一頁 回目錄 回首頁 |
18.3.4.1 適化概述 所謂適化就是將桌面應用轉化為Client/Server應用。適化是一個很複雜的主題,這�堣ㄧ埴蚆翮z。本節將介紹適化 Delphi 應用程式中最重要的方面。適化的主要方面有: ● 將資料庫從桌面基台到伺服器的適化● 將應用程式轉化為Client/Server的適化
適化還需要實現從桌面環境到 Client/Server環境的轉化。桌面資料庫和 SQL伺服器資料庫在許多方面有不同之處。例如: ● 桌面資料庫用於同一時刻單用戶的存取,而伺服器用於多用戶存取 ● 桌面資料庫是面向記錄的,而伺服器是面向集合的● 桌面資料庫將每個表存儲在獨立的文件中, 而伺服器將所有的表存儲在資料庫中Client/Server應用必須解決更新的問題,最複雜的是聯接、網路和事務控制
18.3.4.2 適化資料庫
適化資料庫包含下列步驟: ● 在桌面資料庫結構的基礎上,定義伺服器上的元數據● 將數據從桌面轉化到伺服器中 ● 解決下列問題: ● 數據型式差異 ● 數據安全性和完整性 ● 事務控制 ● 數據存取權 ● 數據合法性 ● 鎖定 Delphi提供了兩種方法適化一個資料庫。 ● 使用Database Desktop工具,選擇選擇表Tools/Utilities/Copy to命令將資料庫表從桌面方式拷貝到SQL格式 ● 建立應用TBatchMove部件的應用程式
這兩種方法都可以將表結構和數據從桌面數據源轉化到伺服器上。依靠這些資料庫,可能需要改變結果表。例如,可能想進行不同數據型式的映射。 也可以將下列特征加入資料庫: ● 完整性約束 ● 索引● 偵測約束 ● 存儲過程和觸發器 ● 其它伺服器特征
如果用 SQL腳本和伺服器數據定義工具定義元數據會更有效。然後用前面介紹的兩種方法轉移數據。因為如果是手工定義資料庫表,Database Desktop和TBatchMove 部件將只拷貝數據。
18.3.4.3 適化應用程式
在理論上,設計用來存取局部數據的 Delphi應用程式做很少的修改就可以存取遠端伺服器上的數據。如果在伺服器上定義適合的數據源,你就能將應用程式指向存取它,這只需簡單地改變應用程式中TTable或TQuery部件的DatabaseName屬性。實際上,在存取局部和過程數據源之間有許多重要的不同之處。 Client/Server應用程式必須解決大量的在桌面應用中所沒有的問題。任何 Delphi應用程式都能用TTable或TQuery部件存取數據。桌面應用程式通常都是使用TTable部件。當適化到SQL伺服器上時,用TQuery會更有效,如果應用程式要檢索大量記錄,則TQuery部件要略勝一籌。如果應用程式使用統計或數學函數,那麼在伺服器上通過存儲過程執行這些函數會更有效。因為存儲過程執行更快,使用存儲過程還可以減少網路負載,特別是大量行數據的函數。 例如,計算大量記錄的標準差: ● 如果該函數在用戶端執行,所有的值從伺服器上檢索出來並送到用戶端,導致網路擁塞● 如果該函數在伺服器端執行,則應用程式只需要伺服器上的答案
18.4 Delphi 用戶/伺服器應用實例分析
本節中採用的實例是 Delphi2.0資料庫的例子CSDEMO。CSDEMO是Delphi用戶/伺服器程式設計的示例程式,它採用的資料庫伺服器是Local InterBase Server。CSDEMO 較好地範例了BDE環境的組態,InterBASE Server進階功能應用,SQL伺服器聯接,觸發器應用、存儲過程程式設計和事務控制技術等,具有較高的參考價值。本節講述下列內容:● 資料庫環境介紹 ● TDatabase的應用 ● 不同資料庫表的切換● 觸發器程式設計 ● 存儲過程程式設計 ● 事務控制應用
18.4.1 資料庫環境介紹
本例中採用的資料庫伺服器是 Local InterBase Server。Local InterBase是InterBase Server的單用戶版32位、相容ANSI SQL。Local InterBase支援用戶/伺服器應用在單機上的開發和測試,並且可以很容易地適化到InterBase Server上。因此,開發用戶/伺服器應用採用Local InterBase作為原型開發環境是很方便的。
18.4.1.1 IBLOCAL 的BDE參數
本例中的 SQL資料庫是IBLOCAL。它是由BDE組態工具(BDECFG32.EXE)設定參數值。它的各項參數值列於下表:表18.13 IBLOCAL的各項參數值 ──────────────────────────────────── 參 數 名 參 數 值 ———————————————————————————————————— TYPE INTRBASEPATH SERVER NAME C:\INTRBASE\EXAMPLES\EMPLOYEE.GDB USER NAME SYSDBA OPEN MODE READ/WRITE SCHEMA CACHE SIZE 8 LANGDRIVER SQLQRYMODE SQLPASSTHRU MODE SHARED AUTOCOMMIT SCHEMA CHCHE TIME -1 MAX ROWS -1 BATCH COUNT 200 ENABLE SCHEMA CACHE FALSE SCHEMA CACHE DIR ────────────────────────────────────
18.4.1.2 資料庫結構介紹IBLOCAL資料庫的結構都是由InterBase伺服器工具互動式SQL工具(ISQL)定義的。 用 ISQL定義資料庫,首先要用Create Database命令建立資料庫,建立的新資料庫一般是以GDB為檔案附加名。建立好後,就可以用SQL語言定義資料庫表,例如建立EMPLOYEE表的SQL語句如下:定義網欄位標簽數據型式:
CREATE DOMAIN FIRSTNAME AS VARCHAR(15); CREATE DOMAIN LASTNAME AS VARCHAR(20); CREATE DOMAIN COUNTRYNAME AS VARCHAR(15); CREATE DOMAIN EMPNO AS SMALLINT; CREATE DOMAIN DEPTNO AS CHAR(3) CHECK (VALUE = '000' OR (VALUE > '0' AND VALUE <= '999') OR VALUE IS NULL); CREATE DOMAIN JOBCODE AS VARCHAR(5) CHECK (VALUE > '99999'); CREATE DOMAIN JOBGRADE AS SMALLINTCHECK (VALUE BETWEEN 0 AND 6); CREATE DOMAIN SALARY AS NUMERIC(15, 2) DEFAULT 0 CHECK (VALUE > 0);
建立EMPLOYEE表:
CREATE TABLE EMPLOYEE (EMP_NO EMPNO NOT NULL, FIRST_NAME FIRSTNAME NOT NULL, LAST_NAME LASTNAME NOT NULL, PHONE_EXT VARCHAR(4), HIRE_DATE DATE DEFAULT 'NOW' NOT NULL, DEPT_NO DEPTNO NOT NULL, JOB_CODE JOBCODE NOT NULL, JOB_GRADE JOBGRADE NOT NULL, JOB_COUNTRY COUNTRYNAME NOT NULL, SALARY SALARY NOT NULL, FULL_NAME COMPUTED BY (last_name || ', ' || first_name), PRIMARY KEY (EMP_NO)); CHECK語句是給資料庫欄位取值範圍加約束條件。PRIMARY_KEY語句是給表建立關鍵字索引。 如法炮制,就可以定義 IBLOCAL中的所有表。IBLOCAL中的表包括:
EMPLOYEE CUSTOMER DEPARTMENT EMPLOYEE_PROJECT PROJECT SALES SALARY_HISCORY 各資料庫表中的內容如下:
表18.14 EmployeeDemoDB中各資料庫表的內容 ─────────────────────────── 資料庫表名 表中內容 ——————————————————————————— EMPLOYEE 雇員資訊 CUSTOMER 用戶資訊 DEPARTMENT 部門資訊 EMPLOYEE_PROJECT 雇員負責的工程 PROJECT 工程資訊 SALES 銷售資訊 SALARY_HISTORY 雇員薪水調整的歷史資訊 ───────────────────────────
每個資料庫表中都定義了關鍵欄位。關於資料庫表中的欄位名、型式、大小,這�堣ㄕA贅述。
18.4.2 應用程式分析
18.4.2.1 TDatabase部件的使用
CSDEMO程式中定義了一個資料庫模群組部件????TDmEmployee,它是繼承於TDataModule。TDataModule是在Delphi2.0中才出現的專門放置數據存取部件(如TDatabase、TTable和TQuery等)的框架。其它涉及資料庫存取的窗體,只要在uses語句中插入資料庫模群組所在的庫單元,該窗體上的資料庫部件就可引用相應的資料庫存取部件。 在TDmEmployee中定義了一個TDatabase型式的部件——EmployeeDatabase。EmployeeDatagase的主要屬性及屬性值如下:
表18.15 EmployeeDatabase部件主要屬性的取值 ─────────────────────── 屬性 屬性值 ——————————————————————— AliasName IBLOCAL DatabaseName EmployeeDemoDB KeepConnection True LoginPrompt False TransIsolation tiReadCommitted Params USERNAME = SYSDBA PASSWORD = masterkey Connected True ───────────────────────
AliasName屬性所指定的IBLOCAL,必須已經在BDE中組態好,DatabaseName屬性指定要使用的資料庫名,該資料庫名是由應用程式自己定義的,因此不反應到BDE中,該屬性值被TTable、TQuery等DataSet部件引用,並且出現在DataSet部件的DatabaseName 下拉式列示方塊中。本例中的“EmployeeDemoDB”,被EmployeeTable,SalesTable等所有DataSet部件引用。 Connected為True表明,應用程式與資料庫將保持聯接。 KeepConnection屬性為True,表明多次打開和關閉EmployeeDemoDB資料庫中的任意表,應用程式將始終與資料庫保持聯接,這省卻了重複註冊的開銷。 LoginPrompt 屬性為False,表明應用程式自動處理與資料庫的聯接註冊,因此,Params屬性中定義了註冊的用戶名和密碼:
USERNAME = SYSDBA PASSWORD = masterkey
TransIsolation 屬性為tiReadCommitted表明,如果存在多個同時事務,則某一事務只允許讀由其它事務提交了的數據。程式中 EmployeeDatabase的應用還與事務控制等有關。下文中會介紹這方面的內容。
18.4.2.2 不同資料庫表的切換
在許多資料庫應用中都要在不同資料庫表之間相互切換,以響應用戶輸入條件或系統狀態的變化。這時,往往需要特別的處理,例如改變遊標形狀或隱藏數據改變等,尤其是在用戶 /伺服器應用程式中。因為是用SQL語句存取遠端資料庫,有時還要在伺服器端執行計算任務,所以用戶端的數據變化會有一定的間隔,因此應該讓用戶明白發生了什麼。下面是CSDEMO在資料庫表切換時的處理辦法:procedure TFrmViewDemo.ShowTable( ATable: string ); begin Screen.Cursor := crHourglass; { 向用戶提示目前操作狀態 }VaryingTable.DisableControls; { 隱藏數據變化 }VaryingTable.Active := FALSE; { 關閉原來的資料庫表 }VaryingTable.TableName := ATable; { 更新資料庫表名 }VaryingTable.Open; { 打開資料庫表 }VaryingTable.EnableControls; { 顯示所作的修改 } Screen.Cursor := crDefault; { 重新設定遊標形狀 } end;
crHourglass型遊標表明正在執行SQL查詢。DisableControls和EnableControls的作用是隱藏和顯示數據變化。
18.4.2.3 InterBase觸發器(Trigger)的應用
在CSDEMO應用程式中,演示觸發器應用的窗體是TFromTriggerDemo; 在該窗體中包含兩個TDBGrid物件。DBGrid1顯示EmployeeTable中的數據,DBGrid2顯示SalaryHistoryTable中的數據。它們的主要屬性及屬性值如下: 表18.16 EmlpoyeeTable部件主要屬性的取值 ───────────────────── 屬 性 屬 性 值 ————————————————————— DatabaseName EmployeeDemoDB IndexFieldName Emp_No TableName EMPLOYEE ─────────────────────表18.17 SalaryHistoryTable部件主要屬性的取值 ───────────────────── 屬 性 屬 性 表 ————————————————————— DatabaseName EmployeeDemoDB IndexFieldName Emp_No MasterFields Emp_No MasterSource EmployeeSource TableName SALARY_HISTORY ─────────────────────
這兩個表之間存在兩種關係: ● 連接關係 EmployeeTable的記錄變化時,SalaryHistoryTable的數據要作相應的變化。這種連接關係是通過索引來實現的。 ● 數據一致性 對EmployeeTable中的Salary欄位的值作修改必須反映到SalaryHistoryTable中,SalaryHistoryTable維護的是Salary變化的歷史資訊。這種數據一致性要求在本程式中是通過觸發器實現的。 觸發器是在SQL伺服器端執行的一段程式,它在伺服器端被觸發執行完成一定的數據計算任務。 下面是InterBase伺服器上與Employee表相關的觸發器程式:
Triggers on Table EMPLOYEE: SAVE_SALARY_CHANGE, Sequence: 0, Type: AFTER UPDATE, Active AS BEGIN IF (old.salary <> new.salary) THEN INSERT INTO salary_history (emp_no, change_date, updater_id, old_salary, percent_change) VALUES ( old.emp_no, 'now', user, old.salary, (new.salary - old.salary) * 100 / old.salary); END
因為觸發器是相應於EMPLOYEE表上的數據修改由伺服器自動觸發執行的,所以在用戶應用程式上沒有顯式的呼叫。在用戶端有打開並顯示資料庫表內容的程式和當SALARY_HISTORY表中數據變化時的更新顯示的操作。
procedure TFrmTriggerDemo.FormShow(Sender: TObject); begin DmEmployee.EmployeeTable.Open; DmEmployee.SalaryHistoryTable.Open; end;
procedure TDmEmployee.EmployeeTableAfterPost(DataSet: TDataSet); begin { 一個雇員的薪水變化將觸發薪水調整歷史記錄的變化, 因此,如果SalaryHistory打開的話,就需要更新顯示 }with SalaryHistoryTable do if Active then Refresh; end;
18.4.2.4 存儲過程程式設計
存儲過程也是 SQL伺服器上的一段程式,它接收輸入參數,在伺服器端執行,並將結果返回用戶端,存儲過程是必須在用戶應用程式中顯式呼叫的。對於資料庫表中大量記錄的統計和函數計算,存儲過程是很有用,這樣可以將重複性計算任務轉換到伺服器,提高資料庫應用的性能。 Delphi中有兩個部件能操作遠端資料庫伺服器上的存儲過程:TQuery和TStoredProc。 1. TQuery 的存儲過程程式設計CSDEMO 中演示用TQuery呼叫存儲過程的窗體是TFrmQueryProc。TFrmQueryProc中有兩個TDBGrid 部件。DBGrid1顯示EmployeeTable中的數據。DBGrid2顯示Project表中的數據。使用存儲過程的TQuery部件名為EmployeeProjectsQuery,它的作用是建立Employee 表和Project 表的連接,以實現當DBGrid1中記錄改變時,DBGrid2中的數據作相應的改變。具體的連接任務是由伺服器上的存儲過程Get_Emp_Proj完成。下面是Get_Emp_Proj的程式: PROCEDURE Get_Emp_Proj BEGIN FOR SELECT proj_id FROM employee_project WHERE emp_no = :emp_no INTO :proj_id DO SUSPEND;END
EMP_NO INPUT SMALLINT PROJ_ID OUTPUT CHAR(5) 該過程帶兩個參數: EMP_NO是輸入參數,型式是SMALLINT. PROJ_ID 是輸出參數,型式是CHAR(5)相應地, EmployeeProjectsQuery的主要屬性如下:表18. 18 EmployeeProjectsQuery部件主要屬性的取值 ────────────────────────── 屬 性 屬 性 值 —————————————————————————— DatabaseName EmployeeDemoDB Params EMP_No( 輸入參數,Smallint型式)SQL Select * from Get_Emp_Proj(:EMP_NO) ──────────────────────────
TQuery 部件是在SQL語句中直接呼叫存儲過程。下面是用戶端的程式: procedure TFrmQueryProc.FormShow(Sender: TObject); begin DmEmployee.EmployeeTable.Open; EmployeeSource.Enabled := True; with EmployeeProjectsQuery do if not Active then Prepare; end; 用 Prepare顯式地準備SQL語句,雖非必須,但可以優化SQL的執行。procedure TFrmQueryProc.EmployeeDataChange(Sender: TObject; Field: TField); begin EmployeeProjectsQuery.Close; EmployeeProjectsQuery.Params[0].AsInteger := DmEmployee.EmployeeTableEmp_No.Value; EmployeeProjectsQuery.Open; WriteMsg('Employee ' + DmEmployee.EmployeeTableEmp_No.AsString + ' is assigned to ' + IntToStr(EmployeeProjectsQuery.RecordCount) + ' project(s).'); end;
該事件處理過程與 EmployeeSource的OnDataChange屬性相聯。用於當EmployeeTable數據記錄變化時,修正存儲過程的輸入參數,並執行SQL語句。2. TStoredProc部件的存儲過程程式設計 TStoredProc Delphi 專門用來使用伺服器存儲過程的部件。CSDEMO 中演示用TStoredProc呼叫存儲過程的窗體是TFrmExecPr 在程式執行中,當按下 ShipOrder按鈕,要求對ORED_STA_TUS等欄位的內容作修改以維護資料庫的一致性。欄位內容的修改任務由伺服器上的存儲過程SHIP_ORDER完成。SHIP_ORDE的程式如下:PROCEDURE SHIP_ORDER DECLARE VARIABLE ord_stat CHAR(7); DECLARE VARIABLE hold_stat CHAR(1); DECLARE VARIABLE cust_no INTEGER; DECLARE VARIABLE any_po CHAR(8); BEGIN SELECT s.order_status, c.on_hold, c.cust_no FROM sales s, customer c WHERE po_number = :po_num AND s.cust_no = c.cust_no INTO :ord_stat, :hold_stat, :cust_no; IF (ord_stat = "shipped") THEN BEGIN EXCEPTION order_already_shipped; SUSPEND; END ELSE IF (hold_stat = "*") THEN BEGIN EXCEPTION customer_on_hold; SUSPEND; END FOR SELECT po_number FROM sales WHERE cust_no = :cust_no AND order_status = "shipped" AND paid = "n" AND ship_date < 'NOW' - 60 INTO :any_po DO BEGIN EXCEPTION customer_check; UPDATE customer SET on_hold = "*" WHERE cust_no = :cust_no;
SUSPEND; END UPDATE sales SET order_status = "shipped", ship_date = 'NOW' WHERE po_number = :po_num; SUSPEND; END Parameters: PO_NUM INPUT CHAR(8) 該過程只帶有一個輸入參數: PO_NUM,型式是CHAR(8)。在用戶端使用該過程的 TStoreProc部件是ShipOrderProc,其主要屬性如下表:表18.19 ShipOrderProc部件主要屬性的取值 ──────────────────────────── 屬性名 屬 性 值 ———————————————————————————— DatabaseName EmployeeDemoDB ParamBindMode pbByName Params PO_NUM( 輸入參數,String型式)StoredProcName SHIP_ORDER ────────────────────────────
用戶端執行 SHIP_ORDER的程式如下:procedure TFrmExecProc.BtnShipOrderClick(Sender: TObject); begin with DmEmployee do begin ShipOrderProc.Params[0].AsString := SalesTable['PO_NUMBER']; ShipOrderProc.ExecProc; SalesTable.Refresh; end; end; 當用戶按 ShipOrder按鈕時,執行這段程式。程式中先準備輸入參數,用ExecProc方法執行存儲過程。呼叫 SalesTable.Refresh方法刷新數據顯示。在 CSDEMO應用程式中另一個使用存儲過程的TStoredProc部件是DeleteEmployeeProc。它完成的任務是解除Employee表中的記錄,並修改所有相關的表, 以維護數據的一致性。其屬性如下:表18.20 DeleteEmployeeProc部件主要屬性的取值 ────────────────────────── 屬性名 屬 性 值 —————————————————————————— DataBaseName EmployeeDemoDB ParamBindMode PbByName Params EMP_NUM( 輸入參數,整型)StoredProcName DELETE_EMPLOYEE ──────────────────────────
存儲過程 DELETE_EMPLOYEE的程式如下:PROCEDURE DELETE_EMPLOYEE DECLARE VARIABLE any_sales INTEGER; BEGIN any_sales = 0; SELECT count(po_number) FROM sales WHERE sales_rep = :emp_num INTO :any_sales; IF (any_sales > 0) THEN BEGIN EXCEPTION reassign_sales; SUSPEND; END UPDATE department SET mngr_no = NULL WHERE mngr_no = :emp_num; UPDATE project SET team_leader = NULL WHERE team_leader = :emp_num; DELETE FROM employee_project WHERE emp_no = :emp_num; DELETE FROM salary_history WHERE emp_no = :emp_num; DELETE FROM employee WHERE emp_no = :emp_num; SUSPEND; END Parameters: EMP_NUM INPUT INTEGER 從上述存儲過程的例子中,我們看到存儲過程在維護伺服器上的數據一致性方面有很強的能力,它節省了系統開銷,提高了用戶端的性能。
18.4.2.5 事務控制程式設計
在用戶 /伺服器應用程式中,事務控制是一項很重要的技術。它對於提高系統的可靠性,維護數據一致性有著重要的意義。Delphi中提供了事務的隱式和顯式兩種控制方法。其中顯式控制的性能較高,下面介紹Delphi事務顯式控制的程式設計方法。 Delphi擔當事務控制任務的部件是TDatabase 。TDatabase 用於事務控制的屬性是TransIsolation,方法有StartTranstion、Commit和Rollback。關於這些屬性和方法作用和使用方法請參閱用戶/伺服器事務管理。 在 CSDEMO中TDatabase 部件為EMployeeDatabase,其TransIsolation屬性值為tiReadCommitted,意為如果存在多個同時事務存取資料庫,則其中任一事務只能讀其它事務提交的了數據。CSDEMO中演示事務控制的窗體是TFrmTransDemo。 DBGrid1中顯示EmployeeTable中的內容。當視窗顯示時,EmployeeDatabase開始一次事務控制並激活EmployeeTable: procedure TFrmTransDemo.FormShow(Sender: TObject); begin DmEmployee.EmployeeDatabase.StartTransaction; DmEmployee.EmployeeTable.Open; end; 當視窗被關閉或隱藏時,EmployeeDatabase提交事務:
procedure TFrmTransDemo.FormHide(Sender: TObject); begin DmEmployee.EmployeeDatabase.Commit; end;
視窗中有兩個按鈕BtnCommitEdits和BtnUndoEdits。按下BtnCommitEdits按鈕將提交目前事務,並開始新的事務控制並刷新數據。
procedure TFrmTransDemo.BtnCommitEditsClick(Sender: TObject); begin if DmEmployee.EmployeeDatabase.InTransaction and (MessageDlg('Are you sure you want to commit your changes?', mtConfirmation, [mbYes, mbNo], 0) = mrYes) then begin DmEmployee.EmployeeDatabase.Commit; DmEmployee.EmployeeDatabase.StartTransaction; DmEmployee.EmployeeTable.Refresh; end else MessageDlg('Can''t Commit Changes: No Transaction Active', mtError, [mbOk], 0); end;
按下BtnUndoEdits按鈕將返轉目前事物,恢復原來的數據,開始新的事務控制,並刷新數據的顯示。
procedure TFrmTransDemo.BtnUndoEditsClick(Sender: TObject); begin if DmEmployee.EmployeeDatabase.InTransaction and (MessageDlg('Are you sure you want to undo all changes made during the ' + 'current transaction?', mtConfirmation, [mbYes, mbNo], 0) = mrYes) then begin DmEmployee.EmployeeDatabase.Rollback; DmEmployee.EmployeeDatabase.StartTransaction; DmEmployee.EmployeeTable.Refresh; end else MessageDlg('Can''t Undo Edits: No Transaction Active', mtError, [mbOk], 0); end; |
後一頁 前一頁 回目錄 回首頁 |