在谈论技术细节前首先说说FIBPlus.这是Delphi,BCB和Kylix的原生组件库,帮助开发者通过InterBase API与InterBase高效率的交互,其同样适用于FireBird和Yaffil.这意味着使用FIBPlus开发者可以使用所有Interbase的功能:完全的事务控制,最高的速度,特殊的InterBase特性(如数组字段类型)等等.除了上面提到的,FIBPlus控件绝对与数据感知控件兼容.后面将通过范例阐述.
在关系数据库中使用主细表连接是应用开发者常遇到的任务.事实上数据库会优化这种主从关系从而建立一种更深层的主从连接.因此依赖于数据库的优化,开发者应该尽量简化用户界面,将更多的主从关系处理交给数据库.
我们将使用InterBase开发包的EMPLOYEE.GDB数据库中的DEPARTMENT 和EMPLOYEE表建立一个主从关系范例.
CREATE TABLE DEPARTMENT (
DEPT_NO DEPTNO NOT NULL,
DEPARTMENT VARCHAR (25) NOT NULL,
HEAD_DEPT DEPTNO,
MNGR_NO EMPNO,
BUDGET BUDGET,
LOCATION VARCHAR (15),
PHONE_NO PHONENUMBER DEFAULT '555-1234',
DEPT_NO1 CUSTNO);
ALTER TABLE DEPARTMENT ADD PRIMARY KEY (DEPT_NO);
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));
ALTER TABLE EMPLOYEE ADD PRIMARY KEY (EMP_NO);
ALTER TABLE EMPLOYEE ADD FOREIGN KEY (DEPT_NO) REFERENCES DEPARTMENT (DEPT_NO);
DEPARTMENT是主表,EMPLOYEE是从表.主从关系为:
DEPARTMENT.DEPT_NO <- EMPLOYEE.DEPT_NO
对每个部门都可以存储相对应的多个雇员.
步骤1.从数据表中获取数据并显示在TDBGrid中.
我们从头开始分析这个应用程序的每个过程.这可以帮助我们实现部门和雇员数据的编辑.首先向窗体拖放一个主要控件--连接到InterBase数据库的TpFIBDatabase,并编辑组件.
图2.
为连接到数据库必须设置一个路径(本例的本地路径),用户名和密码.设置好参数后点击”Test”按钮检查设置是否正确.也可以在运行期设置数据库连接参数.
图3.
procedure TForm1.Button1Click(Sender: TObject);
begin
pFIBDatabase1.DBName := DBNameE.Text;
pFIBDatabase1.ConnectParams.UserName := UserNameE.Text;
pFIBDatabase1.ConnectParams.Password := PasswordE.Text;
pFIBDatabase1.Open;
end;
下一步从数据库获取数据.需要在窗体上放置两个组件: TpFIBDataSet和TpFIBTransaction. TpFIBDataSet从TDataSet继承并可以与所有标准可视组件兼容(DataSource1可以连接到pFIBDataSet1). TpFIBTransaction用来控制事务.
图4.
在TPBMode属性中设置事务隔离级别,并将pFIBTransaction1连接到pFIBDatabase1.
图5.
同样设置pFIBDataSet1的Database和Transaction属性.
图6-7.
现在可以设置必要的数据库查询语句获取数据,并可修改数据表.使用SelectSQL工具写查询数据的SQL命令.本例查询很简单:
SELECT * FROM DEPARTMENT
现在做少量的代码修改:
procedure TForm1.Button1Click(Sender: TObject);
begin
pFIBDatabase1.DBName := DBNameE.Text;
pFIBDatabase1.ConnectParams.UserName := UserNameE.Text;
pFIBDatabase1.ConnectParams.Password := PasswordE.Text;
pFIBDatabase1.Open;
pFIBDataSet1.Open;
end;
在打开数据集前不必显示的启动事务因为pFIBDataSet1的poStartTransaction属性默认值指定数据集将自动开启事务.启动程序打开数据库可见如下界面:
图8.
下载代码example.
步骤2.创建实时查询.使用生成器获取主键值.
如果尝试在DBGrid1中修改值将会发现数据是只读的.为了使我们的查询可以编辑(有活力的)还需要写其他一些SQL命令,在编辑DBGrid1中数据是可由pFIBDataSet1自动调用.需要设置如下属性:
图9.
FIBPlus包括特殊的设计器编辑器用来编辑和生成修改命令.可右击pFIBDataSet1组件在弹出菜单中点击” SQL Generator”项.
图10.
生成修改命令前首先需要在列表中选择要修改的表.如果查询中只有一个表,将会自动选择.点击“Get Table Fields”按钮填充“Key Fields”和“Update Fields”列表.在第一个列表中选择要生成修改命令的WHERE子句的列.在“Update Fields”列表中选择需要修改的列.默认DEPARTMENT表的所有列都被选中.现在点击“Generate SQLs”按钮将自动生成所有修改命令,可从SQLs属性中查看.本例我们得到如下InsertSQL属性:
INSERT INTO DEPARTMENT(
DEPT_NO, DEPARTMENT, HEAD_DEPT, MNGR_NO, BUDGET, LOCATION,
PHONE_NO
)
VALUES(
?DEPT_NO, ?DEPARTMENT, ?HEAD_DEPT, ?MNGR_NO, ?BUDGET, ?LOCATION,
?PHONE_NO
)
注意RefreshSQL查询命令.这也是一个查询语句,但其必须只返回当前记录.创建了所有SQL命令后启动查询,在DBGrid1中新建一个记录,pFIBDataSet1将自动使用用户设置的相应字段值填充?DEPT_NO, ?DEPARTMENT等参数的值.注意DEPT_NO字段值.这个整数字段是表的主键必须填充一个唯一值. InterBase 提供了一个叫“generators”的特殊特性来生成这样的值,并可确保值的唯一性.在执行插入命令前我们使用这个特性生成一个新的生成器值.为什么需要这样做?当执行任意修改操作后(出删除外), pFIBDataSet1都自动执行RefreshSQL,在子句中设置当前参数值.如果我们不提前获取主键值而是使用触发器生成,将无法在RefreshSQL查询中设置DEPT_NO的值,也就无法重读被修改的记录.因此如果一下记录字段在触发器中被修改,则在完全重新打开数据集前看不到这些修改的值.但如果首先获取到生成器的值,然后随其他参数插入到数据库,即可使用这个值来更新当前记录,获取到所有当前记录字段值,而不必重新打开数据集!
TpFIBDataSet允许使用生成器自动获取主键值.需要设置AutoUpdateOptions属性:
图11.
首先设置DEPARTMENT表名,DEPT_NO主键字段和DEPT_NO_GEN生成器. WhenGetGenID属性可设置如下值:
wgOnNewRecord –缓冲区准备创建新纪录后获取生成器的值.
wgBeforePost – 向服务器发送新记录前获取生成器的值.
wgNever – 不使用生成主键值的机制.
本例设置为wgOnNewRecord,查看DBGrid1的DEPT_NO字段值,都是冲服务器上获取的.启动程序,编辑记录并插入新记录.
图12.
上图可见自动获取了DEPT_NO的值为22.注意还会自动获取到BUDGET和PHONE_NO字段的默认值.
范例代码 example
步骤3. AutoCommit模式.启用两个事务上下文防止死锁
Step 3. AutoCommit Mode. Work in the context of two transactions for avoiding DEADLOCK.
如果设置了pFIBDataSet的AutoCommit属性为True则会自动提交数据的修改.调用Post方法后, pFIBDataSet1将自动调用pFIBTransaction1.CommitRetaining,提交修改但不会关闭数据集.这样可减少死锁的可能,死锁很容易在长事务中修改数据时出现.
FIBPlus提供了另一个能力,将死锁的可能性几乎降低为0. TpFIBDataSet可以工作在两个事务上下文中,一个长事务负责读取数据,一个短事务用来提交数据的修改.
将pFIBTransaction1重命名为ReadTransaction,添加一个新的事务组件WriteTransaction. 设置pFIBDataSet1的UpdateTransaction属性为WriteTransaction.这样pFIBDataSet1同时连接到了两个事务组件:
图13.
现在调用pFIBDataSet1的Post方法后将执行WriteTransaction的提交方法.但是考虑到读取数据的是不同的事务(ReadTransaction),将不会关闭pFIBDataSet1.因此使用FIBPluswomen有了一个真正的AutoCommit模式,减少死锁可能性而不会阻碍查看实际记录值.而在BDE中使用AutoCommit模式则不能获取到实时的记录值除非重新打开数据集.
查看范例代码example
步骤4.主从连接.使用特殊的参数名称前缀"MAS_"
现在可以创建主从连接了.向窗体拖放如下组件:
DBGrid2: TDBGrid;
DataSource2: TDataSource;
pFIBDataSet2: TpFIBDataSet;
ReadTransaction2: TpFIBTransaction;
WriteTransaction2: TpFIBTransaction;
将其按第一组组件同样方式进行关联.设置pFIBDataSet2的SelectSQL属性:
SELECT * FROM EMPLOYEE
WHERE DEPT_NO = ?DEPT_NO
很明显细表查询只想获取工作在当前部门的雇员.参数值?DEPT_NO必须从DEPARTMENT表的DEPT_NO字段中获取.为自动实现这个效果,设置pFIBDataSet2.DataSource= DataSource1.现在采用与pFIBDataSet1同样方式生成修改SQL命令.
自动生成命令后需要稍作修改.特别在InsertSQL中:
INSERT INTO EMPLOYEE(
EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, HIRE_DATE,
DEPT_NO,
JOB_CODE, JOB_GRADE, JOB_COUNTRY, SALARY
)
VALUES(
?EMP_NO, ?FIRST_NAME, ?LAST_NAME, ?PHONE_EXT, ?HIRE_DATE,
?DEPT_NO,
?JOB_CODE, ?JOB_GRADE, ?JOB_COUNTRY, ?SALARY
)
很明显增加一个新雇员后需要将其?DEPT_NO参数这只为DEPARTMENT表当前记录DEPT_NO字段值. FIBPlus允许通过添加前缀MAS_来自动实现.:
INSERT INTO EMPLOYEE(
EMP_NO, FIRST_NAME, LAST_NAME, PHONE_EXT, HIRE_DATE,
DEPT_NO,
JOB_CODE, JOB_GRADE, JOB_COUNTRY, SALARY
)
VALUES(
?EMP_NO, ?FIRST_NAME, ?LAST_NAME, ?PHONE_EXT, ?HIRE_DATE,
?MAS_DEPT_NO,
?JOB_CODE, ?JOB_GRADE, ?JOB_COUNTRY, ?SALARY
)
现在可以明确的看到EMPLOYEE.DEPT_NO的值是取自DEPARMENT表(EMPLOYEE对应的主表)当前记录的DEPT_NO字段值.同样修改UpdateSQL:
UPDATE EMPLOYEE SET
FIRST_NAME = ?FIRST_NAME,
LAST_NAME = ?LAST_NAME,
PHONE_EXT = ?PHONE_EXT,
HIRE_DATE = ?HIRE_DATE,
DEPT_NO = ?MAS_DEPT_NO,
JOB_CODE = ?JOB_CODE,
JOB_GRADE = ?JOB_GRADE,
JOB_COUNTRY = ?JOB_COUNTRY,
SALARY = ?SALARY
WHERE
EMP_NO = ?OLD_EMP_NO
在代码中稍作修改:
procedure TForm1.Button1Click(Sender: TObject);
begin
pFIBDatabase1.DBName := DBNameE.Text;
pFIBDatabase1.ConnectParams.UserName := UserNameE.Text;
pFIBDatabase1.ConnectParams.Password := PasswordE.Text;
pFIBDatabase1.Open;
pFIBDataSet1.Open;
pFIBDataSet2.Open;
end;
启动应用程序.在DBGrid1中选择记录,DBGrid2中的内容自动变化.注册表功能完成:
图14.
下载范例代码example
步骤5.制定主从机制
FIBPlus也允许设置一些独特的主从机制.还记得上面范例需要手动打开细表(pFIBDataSet2)吗.对于简单连接很简单,但对多个主从关系或多层主从关系(master-detail-subdetail),手动操作打开所有数据集会引起错误.必须在主表打开时启动从表.为避免不必要的代码TpFIBDataSet包括一个特殊的DetailOptions属性:
图15.
如果设置dcForceOpen可以完全确保在主表打开时从表自动打开.现在可以删除pFIBDataSet2.Open这句代码.无论多复杂的主从连接,所有的查询都会按顺序开启.
第二个重要的选项使dcWaitEndMasterScroll.假设用户在滚动DBGrid1试图查找一个部门.每一次DBGrid1的当前记录变化, pFIBDataSet2都自动重新开启查询.明显滚动主表记录时会引起一系列的复杂查询,增加了网络流量.事实上只需要用户最后在DBGrid1上选择了一个部门后才需要重新打开细表查询. FIBPlus帮助避免这些冗余的查询. 将dcWaitEndMasterScroll键值加入到DetailConditions,细表只会在指定间隔后才重新打开(间隔时间由WaitEndMasterInterval设置).
因此当用户简单的切换DBGrid1当前记录,当前记录变化将激活一个时间触发器重新打开细表重新.如果指定时间内又切换到其他记录,时间触发器将重新开始计时.直到用户找到了必要的记录位置,细表查询才会重新查询,这意味着pFIBDataSet2只执行了一次而不是很多次.
结论
现在已经知道如何使用FIBPlus的主从连接,如果连接到数据库,执行查询,生成修改命令,使用两个事务上下文,及连接主从数据集.但描述的很简单建议彻底的学习一下范例代码.