|
18.3.4.1 适化概述
所谓适化就是将桌面应用转化为Client/Server应用。
适化是一个很复杂的主题,这里不详细讲述。本节将介绍适化 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 INTRBASE
PATH
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 SMALLINT
CHECK (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 雇员薪水调整的历史信息
━━━━━━━━━━━━━━━━━━━━━━━━━━━
每个数据库表中都定义了关键字段。关于数据库表中的字段名、类型、大小,这里不再赘述。
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)
相应地,< |