从来没有过这么一种数据库,能够像InterBase/FireBird一样富有激情。这是一种完全为程序员准备的数据库,就像瑞士军刀一样小巧、方便、实用。以往的数据库,不是太大太笨重(例如,Oracle、MS SQL、DB2),就是太简陋,功能不足(例如My SQL)。而InterBase/FireBird则是在两者之间找到了一个很好的平衡点,笔者不妨称之为“中型数据库”。随着硬件环境的不断发展,普通的个人电脑的计算能力越来越逼近并不太久以前的大型计算机的能力,这种趋势同时也大大推动了与此相适应的中型数据库的应用。中型数据库逐渐蚕食大型数据库的市场,这几乎是一个明显的趋势。随着软硬件条件的不断发展,很多大型数据库的很多极其复杂的特性,今天看来逐渐成为了不必要。今天的软件用户更加渴求“简单、实用、绿色”。InterBase/FireBird数据库几乎就是为这个宗旨而量身定制的。
和InterBase/FireBird相当的数据库引擎还有MySQL、PostGre SQL两种数据库。和后两种数据库相比,InterBase/FireBird数据库有着最为充沛而友好的开发工具,市面上专门用于这两种数据库的建库工具,就不下十来种,几乎每个资深的Delphi/IB/FB开发者都恨不得自己也做一个管理工具。最为流行的管理开发工具,例如IBExpert,在不断的发展完善下,其功能甚至于早已超过了其他商用大型数据库的企业管理器。InterBase/FireBird和Delphi、C++Builder两种工具结合非常紧密,因而,在C/S应用开发方面,InterBase/FireBird占有上风,能够开发出最为细腻友好的客户端UI。InterBase/FireBird数据库目前正在迅速发展,它们所需要的是更多实战应用的考验,其中特别包括了大型Web应用的考验。在这方面,MySQL相对而言更成熟一些。但是,随着InterBase/FireBird用户的不断增多,笔者相信,这个只是个时间的问题。中型数据库中,没有一种数据库提供了InterBase/FireBird所带来的如此完备的内在构架,如此丰富强大的SQL支持,如此简洁的使用、维护方式,以及精华所在的存储过程语言。相信这些优异的特性总有一天会在业界放出应有的光芒。
InterBase/FireBird数据库是两个分支的合称。InterBase是Borland/CodeGear公司的数据库产品,而FireBird则是开源组织持续开发的免费开源版本的InterBase。由于这两种数据库的核心特性几乎完全一样,所以,笔者书中阐述的特性绝大多数都同时适用于两种数据库。读者可以根据自己的情况需要,在这两种数据库之间进行选择。在InterBase/FireBird支持者的持续推动下CodeGear和FireBird开发组织都在持续的改进着这两种数据库,所以,在未来还会出现更多的新特性,这些也许会在本书的后续版本中涵盖。相信本书的读者,也许会经常访问这两种数据库的官方网站,关注它们的发展,甚至于一定程度的参与到数据库引擎的改进中。
目前市面上关于InterBase/FireBird数据库的书籍很早就已经有了,但是这些书籍都是关于这种数据库本身的功能阐述,相当于数据库的中文手册,这些书籍对该数据库的应用开发却论述甚少。本书的重点则在于针对这种数据库的应用开发上,面向的是实战性,包括和开发工具的结合以及系统的构架,一些应用开发层面的高级技术将被展开论述,这部分也是本书的精华所在,因而,本书的读者应该是具备一定编程经验的开发者。好,我在这里感谢各方面人士的支持,我们这就开启InterBase/FireBird数据库激情之旅,执行一句:
select ‘我们开始使用InterBase/FireBird数据库了!’
as result from RDB$DATABASE
构建高度智能化的关系数据库系统
现代的应用软件系统的用户,越来越希望软件系统更加的易于维护,使用过程透明化,希望软件本身对人的要求更低,尽量少的涉及专业的计算机知识,使计算机操作者能够更加专注于业务本身。这些,或许就是所说的“软件系统智能化”。感谢InterBase/FireBird,使这一高度挑战性的目标实现起来并不困难。由于特定的历史背景,InterBase/FireBird的开发接口比其他数据库公开的更加彻底,再加之InterBase/FireBird数据库的体系结构也非常的简洁透明,所以用InterBase/FireBird构架智能化的系统就来的比较顺利。
构架纯绿色客户端软件
所谓的“绿色软件”,就是指,程序文件拷贝到空白电脑上面,就能直接运行,不必注册ocx文件、不必读写注册表、不必在系统文件夹下面拷贝文件、以及不必做其他特殊的操作。随着软件风格潮流的返璞归真的趋势,市面上很多的软件产品都以标榜自己为“绿色软件”为荣。“绿色软件”的标准是有一定道理的,一方面,它不会给操作系统环境带来污染,另一方面,更重要的一点,绿色软件有更好的健壮性和便携性。例如,如果系统需要在系统文件夹下面放置若干动态库,那么这些动态库就有可能和其他系统的同名动态库产生冲突,或产生不同版本的混淆,因而增加了故障的概率。Client/Server数据库的绿色化并不是一件容易的事情,因为很多的大型数据库的客户端的安装必须依赖数据库开发商提供的安装程序,完全的不透明。因此,开发出小巧、绿色的客户端软件一直是商业开发者的一个话题
值得一提的是,在开发语言方面,目前最适合制作绿色软件的,仍然是老牌子的Delphi/C++Builder for Win32。我们这里以经典的Delphi 7为例来考察绿色数据库软件的制作。有如往常一样,我们在Delphi中创建好一个应用程序,养成良好的习惯就是,空白程序一上来,先创建后目录结构,把可执行文件输出路径和源代码路径分离开来,同时创建好数据库存放路径,路径之间的相对关系尽量和用户现场一致。如下图,可执行文件输出路径是Bin目录,而数据库文件存放在DB目录下。
作为最简单的程序,我们直接在主窗体上放置了数据库连接控件,并且在数据库中创建了表,使控件在设计时连向了该数据库,几乎与以前没有什么差别:
编译之后,我们发现Bin目录中多了一个999k的可执行文件,执行起来,和往常一样顺利:
运行这个程序,我们用一个动态库分析工具看看它调用了哪些动态库:
在笔者的Windows XP中,Delphi7是经过一定改造的绿色版Delphi7,因此笔者的Windows的System32中可以说只有WinXP自己原先的动态库。从图中可以看到,这个Project1.exe所用到的动态库,除了Windows的系统动态库之外,只有gds32.dll和midas.dll两个dll。由此看来,Win32的Delphi开发出的程序相当的干净绿色。在InterBase/FireBird数据库中,其客户端就是一个小小的动态库(gds32.dll或者FBClient.dll),这样,只要把客户端动态库和应用程序文件放到一起即可完成部署。好的,我们把动态库两个动态库拷贝到Bin目录下,这样不就可以了?
确实,如果按照最低的绿色软件标准来衡量,这样确实已经可以了,至少你的软件拷贝到空白环境中,真的可以运行起来。然而,作为专业级别的软件开发者,我们要快速的制作脍炙人口的软件作品,那么在知识上的要求就不能仅于此了。我们要结合Delphi和InterBase/FireBird数据库的特点,总结一套专门开发绿色软件系统的思路来。幸亏Delphi是开发绿色软件的能手,这使思路总结出来后,相当的清晰。
如果用更加严格的眼光来看,这个小小的范例程序立即就需要一个重要的改进,那就是在单元的Uses中加入一个单元:MidasLib。好,笔者做了这件事情,从新编译,看看目标文件,发现竟然多了200K。这究竟发生了什么?这是因为MidasLib是Midas.dll的静态库。把它囊括到可执行文件之中,可执行文件就不必再部署Midas.dll动态库了。从新分析一下这个可执行文件的动态库调用,发现果然没有了Midas.dll。Midas.dll对于Delphi程序来说,是一个特殊的动态库,它本身是一个COM库,需要注册。系统在寻找这样的动态库时,并非完全遵循Path路径,而是先看看注册表记录的路径,如果不存在,则会自动注册Path内能够找到的Midas.dll。然而,Delphi的每个不同的版本,都会带有不同版本的Midas.dll,这样,机器上所有程序,都引用最后一次安装的Midas.dll,这样,必然造成混乱。所以,在单元的Uses中加入MidasLib是非常必要的举动,直接让程序免去依赖MidasLib。可执行文件+gds32.dll才是比较到位的绿色软件。对于一个有经验的软件设计人员而言,他的头脑中应该很清醒设计中存在哪些影响软件绿色化的因素,以及有一套评测软件绿色程度的标准和流程。笔者在这里试图做一个详尽的总结:
1、 总结系统所用到的VCL控件集列表,列出每个控件的外部依赖性。一般来说,通常所说的“纯VCL”控件都是彻底绿色的组件,也就是说不必附带任何文件。但是也有很多VCL控件包需要外带动态库、ocx、甚至驱动程序。这些都应该在列表中体现。
2、 除了VCL对象之外,系统是否用到了DLL或COM库?这需要对程序本身所用到的东西做一个统计。若系统需要部署带有ocx后缀的文件,很明显,系统中用了ActiveX。但是,很多的COM对象库可能是以其他的后缀结尾,例如,dll。这需要开发者自己分析和整理。
3、 明确程序中用到了哪几种数据库,以及针对每种数据库的访问方式。不同的访问方式有着不同的外部依赖性。如下表:
数据库引擎 | 绿色程度 | |
通用引擎 | BDE/SQL Links | 红色 |
通用引擎 | DBX | 绿色,但需要部署动态库 |
InterBase/FireBird | IBX | 纯绿色 |
FIBPlus | 纯绿色 | |
通用引擎 | ADO | 对于MS数据库平台:绿色,但依赖OS的MDAC的版本 对于其他数据库平台需要部署OLE DB Provider |
文件数据库 | EasyTable | 纯绿色 |
AbsoluteDB | 纯绿色 | |
Halcyon | 纯绿色 | |
内存表 | dxMemData | 纯绿色 |
KbmMemTable | 纯绿色 | |
ClientDataSet | 纯绿色(需要引用MidasLib) | |
Oracle | ODAC | 纯绿色 |
4、 明确所访问的数据库的客户端部署需要哪些文件。最容易处理的,就是客户端只要稍许动态库即可的数据库平台,这样的环境可以算成绿色客户端环境。但很多著名的数据库并不是如此,很可能需要注册COM对象,甚至于必需运行数据库厂商提供的安装盘。
5、 软件进入测试环节后,应该用动态库依赖测试工具来分析程序在运行期间加载了哪些动态库。这一点仍然是非常重要的。这是对程序运行时依赖性的一种验证。如果程序“一不小心”调用了某些“不干净”的代码,那么在这个测试环节中即可直接体现出来。如果没有测试工具,则也可以直接在开发工具的调试状态下,查看程序进程的Modules列表。
6、 软件在测试的后期,可以直接在干净的OS上试运转。目前诸如VMWare之类的虚拟软件非常成熟了,可以借助这种工具来方便软件的测试。
在InterBase/FireBird这个专题开发领域,“绿色”的概念还有着更加深入的内涵,我们下面进行深入一些的讨论。
1、 如果我们的系统是面向单机用户,那么我们不妨考虑一下Embeded FireBird。这是把整个FireBird数据库合并到客户端动态库中的一个版本。也就是说,整个Server的功能都已经包含在了gds32.dll或者fbclient.dll中,那么程序的运行也就不再依赖于必需安装FireBird Server或者InterBase Server。这样一来,一个可执行文件加上几个动态库,干净利索的构成了一个复杂的单机数据库系统,直接就能运转起来,这是所有单机数据库系统都希望达到的效果。它完全免去了数据库引擎的安装,真正实现了“便携”的效果。
2、 把数据库服务器的安装包含到我们自己的程序中。这个特性是FireBird数据库所独有的。FireBird的部署完全免费,并且提供了相应的服务安装的支持文件,这使得数据库的安装可以完全合并到程序本身之中,使数据库服务成为系统的一部分。可喜的是,FireBird Server引擎本身也是纯绿色的,这使得全自动安装服务器变得非常轻松,只要运行以下命令即可:
instreg install –z
instsvc install -auto -superserver -guardian –z
instsvc start
3、 gds32.dll可以编译到可执行文件的资源之中,运行时,可以从资源中调用该动态库。对于IBX而言,这恐怕需要修改IBIntf.pas单元,以及使用第三方的动态库资源加载代码来达到这个效果。这些笔者已经为大家做好了,相关文件可以在本书的附带光盘中找到。这个技术给人的第一印象就是比较“酷”,它能够真正的做到,让客户端程序是一个完全独立的可执行文件,客户端除了这个可执行文件以外,不必部署任何其他文件,包括数据库的客户端。不过,在实际应用中,我们发现很多情况下这未必是一个最好的做法,因为FireBird/InterBase是一个快速发展的数据库平台,如果把客户端动态库合并入可执行文件,那么要升级新版本的引擎出现的时候,恐怕就必需得从新编译程序了。特别是FireBird,几乎每年都会有数个新的引擎版本,那么对于一些追求完美的人来说,不停的编译程序将是一件痛苦的事情。但是对于某些场合而言,这个技巧仍是一个非常有用的东西,例如,我们想把数据库的客户端编写在一个ActiveForm中,这样就省去了附带dll的麻烦。
FireBird/InterBase数据库引擎本身就倾向于绿色和免维护,那么我们在开发系统的时候,不妨就朝着这个方向努力,使我们的系统也变得绿色起来。除了数据库方面的东西以外,第三方控件的选择也是另一方向。一般而言,选择控件一定要尽量选择“纯VCL”,它不仅代表着绿色,也往往代表着最好的性能和效率。几乎所有的ActiveX,都能找到相应的VCL替代品。例如,微软的串口访问控件MS COMM,可以用免费开源的CPort代替。笔者曾经开发的基于FireBird的考勤系统就是采用了Cport。
数据库管理维护功能(Administration)模块的开发
对于FireBird/InterBase数据库系统而言,数据库的维护工作是一个非常重要的内容。它包含了以下几部分:
1、 数据库帐号的维护
2、 数据库文件的拷贝、复制
3、 数据库的备份、压缩
4、 数据库的检查诊断
5、 创建镜像
6、 设定数据库的运行模式、属性参数
对于很多数据库而言,上述任务的完成往往需要数据库管理员在数据库的管理控制台工具中完成,甚至于需要在命令行状态下敲入特定的命令。对于智能化的系统来说,这些,都应该通过程序的图形化界面来体现。要达到这个效果,需要我们的程序本身就包含数据库管理模块。FireBird/InterBase提供了开放的Administration开发接口,使得这一切变得很轻松。
数据库帐号的维护
我们要更改数据库的帐号,需要使用IBX的IBSecurityService控件。它的使用方法如下:
IBSecurityService1.Params.Values['user_name'] := ‘sysdba’;
IBSecurityService1.Params.Values['password'] := ‘masterkey’;//写入管理员密码
IBSecurityService1.ServerName := 'LocalHost';//写入服务器名称
IBSecurityService1.Active := True;
IBSecurityService1.UserName := ‘ANewName’;//写入被更改或添加的用户名称
IBSecurityService1.Password := ‘NewPass’;//写入新的密码
IBSecurityService1.ModifyUser;//更改已有帐号
//IBSecurityService1.AddUser ;//添加新的帐号
//IBSecurityService1.DeleteUser ;//删除帐号
我们可以看到用IBSecurityService控件来增、删、改数据库帐号非常的方便简捷。上述的代码可以作为一个函数提供在程序的主服务模块中。但是需要指出的是,一个用户创建好了之后,并不意味着可以直接以该用户登录来使用数据库中的所有对象。相反,此时的新用户,恐怕无法使用数据库中的任何对象。这是因为,新创建的用户,必需通过Grant命令来赋予相应的权限。所以,如果系统是动态创建用户,则往往在上述代码执行后,用IBScript控件来执行一系列的Grant命令。在很多时候,我们构建系统并不用如此紧密的依赖数据库平台本身的权限机制,毕竟这样的做法在开发上非常繁琐。我们更加常用的做法则是在固定的系统管理员帐号的基础上,我们自己构架一套帐号机制,把我们的操作员保存在我们的一张表中,通过我们自己的代码来进行权限认证。这种情况,我们仅需要使用IBSecurityService1.ModifyUser来改变sysdba的密码就够用了。sysdba密码连同服务器连接串在客户端程序中被加密的保存在一个本地的文件中,这样每次登录的时候,用户只需要输入操作员的帐号,即可登录。在某些特定的场合,程序可能需要多种不同角色的帐号内部使用,这种情况,便可以充分的运用IBSecurityService空间和grant命令来实现相应的功能模块。在简单的关系数据库系统中,系统管理员的密码修改功能,可以直接合并到登录框窗体中,这对于很多场合,不失为是一种既方便用户,又方便开发者的一种做法:
数据库文件的拷贝、复制
InterBase/FireBird数据库有着和Access一样的一个特点,那就是数据库是一个单独的文件。这一点非常有利于制作高度智能化的数据库系统。因为对某个数据库的拷贝,简单到了只需要拷贝一个文件的地步。尽管直接复制数据库文件比Gbak工具生成的gbk文件来说体积要大一些,但是它是一种最为直接的备份方式。对于硬盘空间充沛的系统,这种方式来实现自动备份最为灵活简单。拷贝硬盘文件的方法,可以是调用API函数(例如CopyFile),也可以直接调用Shell命令。由于Shell命令有着更好的灵活性和更丰富的功能,所以是一种更加常用的自动维护功能的实现方法。我们先给出一个函数:
procedure DoCMD(CMD: string);
var
Buf: array[0..511] of char;
AFileName: string;
begin
GetSystemDirectory(Buf, SizeOf(Buf));
AFileName := Buf;
AFileName := AFileName + '\CMD.exe';
ShellExecute(0, '', PChar(AFileName), PChar('/C ' + CMD), '', SW_HIDE);
end;
在这个函数的帮助下,我们可以执行任意的DOS命令,包括拷贝文件:
DoCMD('copy c:\test\db1.gdb d:\bak\' + DateToStr(Now) + '.gdb');
如果我们希望做更加复杂一些的操作,比如,将数据库文件拷贝到特定目录下压缩,或者启动、终止某些服务程序,我们可以根据特定的命令格式编写批处理文件,然后用DoCMD函数来执行该批处理。本书列出3个非常有用的命令:
a) iisreset /stop和iisreset /start:这是启动或停止IIS服务的命令。如果读者的系统参与了IIS中的Web功能,那么就有可能需要让IIS服务停止或启动。
b) sc stop和sc start:启动或停止某服务程序的命令,需要添加一个参数。例如,对于停止FireBird服务来说,是sc stop FirebirdServerDefaultInstance。如果我们需要一个服务名称的列表,那么我们可以在命令行中执行以下命令:
sc query type= service>C:\log.txt
这样在C:\就会产生一个log.txt文件,里面列举着每个命令的名称和状态。
c) 压缩命令。目前WinRAR是最为流行的压缩工具,很多开发者希望用这种方式来自动备份数据库文件,那么相应的命令范例如下:
”C:\Program Files\WinRAR\RAR.exe” a DB.rar “c:\test\db1.gdb”
不过,WinRAR工具包毕竟不是免费的,这使得系统对这个工具的依赖比较不利。我们可以推荐更加紧缩的第三方免费开源的7Zip工具,其官方网址为:
http://www.7-zip.org/zh-cn/
上述命令对于7zip而言,它和WinRAR的几乎完全一样:
” C:\Program Files\7-Zip\7z.exe” a DB.rar “c:\test\db1.gdb”
7Zip无与伦比的压缩比率,全开放的接口,使它能更好的胜任数据库压缩备份的任务,因而能够很好的被数据库备份工具运用。(当然,还可以避开Shell的方式,在程序中直接使用7Zip的开发包来做压缩模块,但这种方式和7Zip结合太紧密,缺乏灵活性)
通过上述的方法,我们能够非常方便的实现各种文件复制、移动、删除等功能,实现数据库文件的备份。
在InterBase/FireBird为基础的系统中,数据库复制模块往往会是下面的形态
1、 在数据库所在的计算机上,开启一个服务程序,该程序按照特定的时间频率,将数据库文件作复制。这就是定期自动直接备份。备份的文件,按照时间的次序,循环滚动存放在特定命名规则的目录下,过期的文件将被覆盖。当然做到按照时间周期来运行某个功能,有两种做法,第一就是在程序中通过Timer来实现;第二种就是把功能做到程序中之后,通过Windows的计划任务来按照特定周期来调用。
2、 在应用服务器或WebServer上,包含一个数据库复制模块。用户在客户端可以根据自己的要求来让系统做出复制动作。
3、 在服务期端有一个图形化的管理工具,打开工具可以进行库文件的复制,甚至于管理工具带有光盘刻录的功能,能够把文件直接刻录到光盘上。
数据库的备份、压缩
FireBird/InterBase提供了专门的备份、恢复功能。和上述直接备份数据库文件不同的是,真正的数据库的备份文件其实是一个“可便携的”(Portable)紧缩的数据文件。FireBird/InterBase的这个功能和它们提供的gbak命令是相关的。在比较早期的时候,InterBase比较偏重于Unix操作系统平台,那时候备份InterBase数据库都是用gbak命令:
备份: gbak.exe -USER "sysdba" -PAS "masterkey" -B DATA.GDB DATA.fbk
恢复: gbak.exe -USER "sysdba" -PAS "masterkey" -REP DATA.fbk DATA.GDB
现在这一传统功能仍然被保留。不过我们却并不希望用户到黑黑的命令行中敲入如此晦涩难记的命令,而是我们的程序代替用户做了这件事情。我们让我们的程序执行上述shell命令,即可达到备份恢复的目地。FireBird/InterBase数据库的备份恢复有着重要的意义。这是因为FireBird/InterBase数据库运行一段时间之后,数据库中会残留大量的垃圾页面,数据页的排列也不连贯,这样,不仅数据库的体积膨胀,而且数据库的性能也受到很大的影响。当我们使用gbak把数据库做出备份,然后由该备份文件恢复出一个全新的数据库后,这个全新的数据库就会从新变得很紧缩,性能会恢复如初。所以智能化的关系数据库系统应该能做到定期的备份恢复数据库。更高标准的对数据的备份的要求,是用gbak备份出数据之后,然后用7zip来压缩,这样,备份的数据非常的紧缩,占据很小的存储空间。数据库的真备份实现方式,也和上述的shell命令实现文件的拷贝相类似,通过DoCMD来调用gbak命令,并通过其它的命令、批处理来实现服务的起停、文件的压缩。这个功能可以做到数据库服务器上的一个周期运行的程序中,也可以做到应用服务器上供客户端的用户手动调用。上面提到的很多方法在这里都可以使用。不过,在Delphi的IBX控件中,还提供了两个控件,支持用户在客户端进行备份、恢复:IBBackupService、IBRestoreService
以IBBackupService为例,典型范例代码如下:
IBBackupService1.Params.Values['user_name'] := 'sysdba';
IBBackupService1.Params.Values['password'] := 'masterkey';
IBBackupService1.ServerName := 'LocalHost';
IBBackupService1.DatabaseName := IBDB.DatabaseName;
IBBackupService1.BackupFile.Clear;
IBBackupService1.BackupFile.Add('C:\test.gbk');
IBBackupService1.Attach;
IBBackupService1.ServiceStart;
IBBackupService1.Detach;
这两个控件比较的简单易用,使用代码几乎相同,不必赘述。
数据库的检查诊断
对于专业级别的数据库系统来说,应该在系统中提供特定的数据库状态诊断工具。在特定的情况下,数据库的运行中会在数据库内部引入数据的瑕疵页面。定期对数据库进行诊断,能够及早的发现数据库瑕疵,尽早的采取有效的措施,消除瑕疵,避免数据库的崩溃。在FireBird/InterBase数据库中,同样提供了两种开发接口来实现数据库的诊断。第一,就是gfix命令行工具,第二就是ibx中提供的IBValidationService控件。
对于gfix来说,我们可以像使用gbak那样来通过命令行来调用。参数列表如下:
-activate activate shadow file for database usage
-attach shutdown new database attachments
-buffers set page buffers <n>
-commit commit transaction <tr / all>
-cache shutdown cache manager
-full validate record fragments (-v)
-force force database shutdown
-housekeeping set sweep interval <n>
-ignore ignore checksum errors
-kill kill all unavailable shadow files
-list show limbo transactions
-mend prepare corrupt database for backup
-mode read_only or read_write
-no_update read-only validation (-v)
-online database online <single / multi / normal>
-prompt prompt for commit/rollback (-l)
-password default password
-rollback rollback transaction <tr / all>
-sql_dialect set database dialect n
-sweep force garbage collection
-shut shutdown <full / single / multi>
-two_phase perform automated two-phase recovery
-tran shutdown transaction startup
-use use full or reserve space for versions
-user default user name
-validate validate database structure
-write write synchronously or asynchronously
-z print software version number
典型的用法是:
gfix.exe -USER "sysdba" -PAS "masterkey" -force -validate MyServer:C:\test\DATA.GDB
不过,和gbak不同的是,gfix将返回一系列的信息,我们必须截获这些信息来判断数据库是否存在问题。如果用Shell的方式调用的话,就会涉及到从Shell中截获控制台程序的输出流,那么在开发上有些麻烦(尽管完全可以实现)。我们更推荐第二种方式,那就是使用IBX的控件:IBValidationService
用法如下:
IBValidationService1.Params.Values['user_name'] := 'sysdba';
IBValidationService1.Params.Values['password'] := 'masterkey';
IBValidationService1.ServerName := 'LocalHost';
IBValidationService1.DatabaseName := IBDB.DatabaseName;
IBValidationService1.Attach;
IBValidationService1.ServiceStart;
while IBValidationService1.IsServiceRunning do
Application.ProcessMessages;
IBValidationService1.Detach;
在这个控件中,蓝色部分是它的最重要属性。包括了检查、修复、
忽略坏的记录等等多种功能。通过这个控件,我们能够很好的对
数据库进行检查、乃至修复。
创建镜像
镜像功能(Shadow)是InterBase /FireBird特有的特性,它是在数据库运行中,由系统自动维护的该数据库的一模一样的另一个或多个库文件(也就是镜像库,或称之Shadow)。也就是说,当主数据库发生任何数据更改动作的时候,InterBase /FireBird Server将同时把发生的任何数据变化同步的更新到镜像库中。毫无疑问,这种机制的目地是为了获取更大的可靠性、提高InterBase /FireBird数据库的健壮性。当主数据库产生瑕疵甚至于发生崩溃的时候,其Shadow很有可能是一个完好的数据库,在这种灾难性的场景下,人们可以将镜像库恢复成为主数据库,进而可以避免系统崩溃带来的巨大损失。镜像功能可以说是一种类似于硬盘并联阵列那样的机制,用数据的副本来换取可靠性、坚固性,只不过镜像功能是通过软件来实现的。在一些恶劣的环境下,镜像功能会显得尤为重要。因为用户的业务现场千差万别,可能有各种难以预料的情况,特别是InterBase /FireBird由于其便捷性,它们经常被安装在普通的个人电脑上充当服务器,这样的话,电脑的掉电、病毒导致OS崩溃、打开程序过多导致内存用尽、硬盘质量低下等等因素都会增加数据库崩溃的概率。经历过客户方的数据库崩溃的开发者对此必然印象深刻,特别是,用户现场灾难发生后,我们这些系统的缔造者们不得不亲临现场,伤透脑筋用尽各种办法去抢救那些致命的业务数据,当尝试了各种渠道也无法挽回这些数据时,我们真的有“上天无路、入地无门”的感觉。目前市面上的所有的数据库有发生崩溃的案例,包括最著名的Oracle、MS SQL等等,当然,我们的InterBase /FireBird也有这种事例。因为产生数据崩溃的硬盘瑕疵往往是非常偶然发生的,因此当存在一个或多个镜像库的时候,在灾难的情况下,我们几乎总能从镜像库中找到状态好得多的、或者是毫无问题的数据库副本,正是这些,让现场的我们长吁一口气。由此我们能够感受到Shadow功能对于InterBase /FireBird而言是多么的重要。
讲了很多的理论,回头看InterBase /FireBird的镜像的使用,却十分的简单,它是通过执行下面的SQL命令来进行的:
Create Shadow Set_Num [Auto|Manul] [Conditional] ‘Filespec’ [Length [=] Int [Page[S]]] [Secondary_File];
其中:
<Secondary_File>= File ‘Filespec’ [<Fileinfo>] [Seconary_File] <Fileinfo>={Length[=]Int [Page[S]]| Starting [At [Page]] Int} [ <Fileinfo>]
Set_Num是影像集合标识,它告诉InterBase语句中列出的所有的影像文件均被组织在该标识之下,影像文件的扩展名是.shd。另外,该语句创建的的影像总是你正在连接的当前数据库的影像。影像文件的名字并不能代表它是哪个数据库的影像,但是使用数据库的名字给影像文件命名,便于理解、查看和管理。简而言之,创建单一文件的Shadow,范例命令如下:
Create Shadow 1 ‘Employee.Shd’;
更加复杂的多文件Shadow则参看参数说明即可。
在我们的程序中,这样的命令其实是放在一个ibQuery控件中,该ibQuery也如同其它的查询的执行那样,需要关联一个IBDataBase和IBTransaction,与实际的数据库相连接。值得指出的是,Create Shadow也在Transaction的控制下,当我们Commit Transaction的时候,镜像数据库才确实的创建出来。
在我们的程序中,Shadow维护的功能一般和其它的数据库维护功能放在一起。不过,在系统的自动备份恢复功能中,也往往包含这方面的代码,因为当我们重整数据库后,我们可以藉次删除并从新创建Shadow,保证新的Shadow本身也是健康无碍的。
用InterBase /FireBird构建专业级别的业务系统——高级Delphi篇
在后MIS时代,BS模式的商用软件的嚣尘逐渐降温,传统的GUI形态的软件仍然散发着相当的魅力,不管是Client/Server,还是Multi-tier,还是Smart Client。这样一来,我们亲爱的Delphi和VCL则被证实为仍旧有着宝贵的价值,这种价值,甚至于可以通过某些技术体现到IE中去,这将是后面将要阐述的Rich Client。不过,一切的一切还得从最基础做起,那就是Delphi的数据访问技术和数据库GUI设计,这部分也是本书的精华所在。
基本把式——ClientDataSet+DataSetProvider+ibQuery
任何武功秘籍都必然有一个基本的套路,用Delphi开发InterBase /FireBird数据库系统同样如此。我们假定读者已经是具备相当经验的Delphi开发者了,因此我们一上来就直接把核心套路和盘托出:ClientDataSet+DataSetProvider+ibQuery。在对这一组合进行深入论述之前,我们非常有必要将Delphi的数据库访问方式的演化过程简要的回顾一下。
Borland公司早期的Paradox数据库取得了巨大的成功,后来又并购了dBase,在那时,Borland公司便将两种数据库的核心统一了起来,形成了BDE数据库引擎。在进军Client/Server市场的时候,Borland公司的研发团队以BDE为基础进行了深入的强化,形成了SQL Links。由于BDE/SQL Links最初起源于Paradox,所以这一数据库引擎有着很深的Paradox的烙印,以至于这一数据库访问方式很多内部处理数据方式都是运用了Paradox的底层功能。这个时期Delphi访问大型数据库都是通过TDataBase、TTable、TQuery等控件,这个时期的Delphi数据库访问方式的特点就是——以数据引擎为核心,什么东西都是现在数据库引擎中(主要指BDE),此时,在Delphi的数据访问对象定义中,作为核心概念之一的“数据集”(TDataSet)也是密切与BDE绑定,作为数据集抽象的实现,例如TTable、TQuery,它们的很多功能也是密切绑定BDE,或者说根本就是通过BDE来实现的。
在后续的发展中,Delphi内引入了BDE以外的其它访问方式,包括ADO和IBX等,这时,“数据集”(TDataSet)早已和BDE脱开,使得基于TDataSet的多种数据访问方式的出现成为可能。在这个过程中,发生了一个微妙的需求,原先的BDE中的TDataSet的实现,诸如TTable和TQuery,拥有着丰富的功能,而新的数据库访问控件中的TDataSet的后代,不得不从新实现一遍TDataSet,尽量多的实现TDataSet的功能子集。然而实际上,TDataSet这个抽象对象规定的大部分功能特性,其实与具体的数据访问方式、访问引擎并无直接关系,如果每当新开发一套数据库控件,就得从新实现一遍TDataSet,那么这就成为了无谓的重复了。能不能推出一个最完备、丰富的TDataSet的实现,而这个实现却与具体的数据库访问方式无关呢?事实上Delphi开发团队也正是做了这样的努力。最初仅用于多层系统的ClientDataSet,后来被推广成为了数据库访问的通用对象,并被持续加强,它就是与数据访问方式无关的最完整的TDataSet实现。而ClientDataSet直接配合具体数据访问控件的用法,就是ClientDataSet+DataSetProvider+具体的数据访问控件,在本书中,数据访问控件就是ibQuery。回顾历史,我们发现这种三合一的组合其实就是实现了老的TTable。
Delphi技术支持人员后来便一直推荐使用这种三合一模式来操作数据,在这个时期,Delphi的数据引擎的风格出现了一种风格趋势,那就是让数据库引擎尽量轻便、精炼、简化,而把功能性的东西全都推给ClientDataSet来实现,从而形成了“瘦引擎、胖内存数据集(ClientDataSet)”的局面。其中,逐渐成为新的Delphi数据访问核心的DB Express(DBX)引擎,就是这种思想的集中体现。在DBX中,数据库访问引擎就是一个精炼的动态库,DBX的控件则是一个最为简陋的单方向Query控件,这个单向Query控件,除了提供数据、执行SQL以外,没有任何额外的功能,它的最大用途就是和DataSetProvider相配合,被ClientDataSet连接使用,一切功能的实现都推给了ClientDataSet。对于InterBase/FireBird开发,三合一模式中,单向DBX的Query变成了IBQuery,也体现着相同的思想。
在这里,我们不妨深入对比一下两种基于数据集的数据访问方式:
TTable控件
胖数据库引擎
数据库
传统的方式
传统模式下,DataSet和远端的数据库保持着实时的互动,客户端每做一个数据更改操作,都会立即反应到服务器
ClientDataSet
瘦数据库引擎
数据库
新的方式
单向
Query
Provider
ClientDataSet和数据库服务器的耦合更加松散,数据更改被缓存到了内存中,在提交数据更改的时候,这些更改被批量的执行到服务器端
我们现在深入论述一下两种方式的区别所在。在Client/Server思想初步取代 “小型机—终端”模式的时期,Delphi中的数据集的概念来源于数据库服务器的“游标”(Cursor)概念,在Delphi中执行一条SQL Select语句后,就会在数据库服务器端产生一个游标,而在Delphi中则体现出一个数据集。BDE则是利用Paradox内存表在客户端的引擎中实现了客户端游标,那么在客户段展现或操作这个数据集的数据时,服务器端的游标就会长期存在,实践证明,这种机制要耗费更多的服务器资源,并且使客户端和数据库服务器形成无意义的紧耦合,毕竟,数据一经提供给客户端,那么数据库服务器就已经完成了任务,从而没有必要一直在那里等着客户端关闭数据才释放。ClientDataSet在这方面体现着和老的方式最大的区别:当ClientDataSet被打开的时候,它会向DataSetProvider申请数据;DataSetProvider把关联的DataSet(往往是单向Query)打开,遍历该DataSet,把所有记录生成数据包,关闭后面的DataSet,然后把生成的数据包发送给ClientDataSet;ClientDataSet解析并呈现这些数据。这个过程中,我们尤其要注意到,DataSetProvider提供数据之后,会立即关闭关联的Query控件,这样,当ClientDataSet呈现数据的时候,Query控件早就被关闭了,也就是说,数据库服务器上面的Cursor早就被释放,毫无疑问,这种方式要健康的多、灵活的多。这里需要特别指出的是,DataSetProvider有个属性叫做“ResolveToDataSet”,我们这里假定该属性是默认的关闭状态。
在数据的更改、更新方面,最传统的模式,就是数据集内的数据一旦被更改,便会立即反映回数据库端,而ClientDataSet则是把更改保存在内存中,在提交的时候批量执行更新语句。实际上后来的传统模式也提供了CachedUpdates这种批量更新的功能,也能让数据在用户完成一系列更改之后,批量将更改提交给数据库,特别是,很多DataSet后代控件具备诸如UpdateObject这样的属性,使之能关联一个XXXUpdateSQL,设定Insert、Update、Delete三种动作相应的参数SQL语句,当提交更改的时候,根据更改去执行相应的SQL语句。这种模式其实已经具备了ClientDataSet的主要优点,在一些场合可以替代ClientDataSet。不过它和ClientDataSet相比,仍有3个关键的区别:
其一,带有批量更新功能的Query控件,仍然需要数据库服务器端的Cursor和数据集本身同时存亡,没有和数据库服务器彻底的放松捆绑。
其二,带有批量更新功能的Query控件不是一个完备的TDataSet类的实现,TDataSet抽象类定义的功能集对于Query控件来说很可能仅仅实现了有限的一部分;而ClientDataSet则是TDataSet的完备而丰富的实现,功能上是TDataSet的超集。
其三、批量更新功能在ClientDataSet中被完整的展开实现了,可以被充分的定制化,甚至于可以用程序代码自定义数据更新的动作。
我们充分的明白了ClientDataSet+DataSetProvider+Query模式的来由和背景之后,就可以进入下一个环节:更加深入的在实战中运用这种模式。
背景:一般情况,连接数据感知控件的场合都推荐用ClientDataSet+DataSetProvider+Query模式,让DataSource和ClientDataSet相关联。
能不能更快一些?——InterBase /FireBird面向性能编程
对于批量数据处理而言,“性能”一词几乎是个永恒的话题。无论是客户,还是我们开发人员自身,都在不停的问:这个系统能不能更快一些?很多时候,我们的做好系统在用户现场爆发的问题已经不是“能否快一些”的事情了,而是“慢得根本就不能使用,能否先让我们能用起来”。作为最优秀的构架师来说,他的60%的水平,都要体现为让系统给人感觉“健步如飞”。系统的性能的制约因素分为两个来源:
1、 高效率的实现代码
2、 面向性能的优良的设计艺术
对于本章的内容来说,我们的重点落在了前者上。首先我们要保证我们的基本操作的实现代码是最高效的,这是一切的基础。
编写高效率的批量数据传输代码
开发人员频繁的需要把一大批数据从一个数据库中复制到另一个数据库中,这种特定的功能模式,我们不妨称之为“数据管道”程序,在业界,通常把它们叫做Replicator。这种东西是沟通不同的数据库的最常用的方法,因而,对于InterBase/FireBird而言,必然会涉及这方面的内容。对于数据管道程序而言,它们的原理往往很简单,无非是把数据从一个地方取出,然后插入到另一个地方。然而,由于数据量的巨大,貌似很简单的东西其实也有很关键的注意事项。这里,我们先把正确的典型的做法加以论述。假定xxx.gdb数据库中,有一个表T1,有6个字段:id、A、B、C、D、E,我们需要把某个地方的10万条数据插入到这个表中,那么应该这样做:
首先和往常一样,窗体上放好ibx的连接组件,添加一个ibQuery,里面写:
INSERT INTO T1(ID,A,B,C,D,E) VALUES(:ID,:A,:B,:C,:D,:E)
循环体,可能是for语句,也可能是针对某数据集的While语句
然后,这样编写数据插入函数(不妨叫做数据管道函数):
…
每隔256条记录,Commit一次
参数的赋值
如果不希望程序失去响应,则加入这句话
IBDatabase1.Open;
ibqAddRec.Prepare;
for I := 1 to 100000 do begin
ibqAddRec.Params[0].Value := …;
…
ibqAddRec.Params[1].Value := …;
ibqAddRec.ExecSQL;
if (I and $FF) = 0 then begin
IBTransaction1.CommitRetaining;
Application.ProcessMessages;
end;
end;
IBTransaction1.CommitRetaining;
IBDatabase1.Close;
在上述很简单的代码中
将Midas和InterBase/FireBird结合起来的封装艺术
Delphi的Midas是一个非常关键的数据库开发组件。我们可以充分的把Midas的高级特性和InterBase/FireBird数据库的精化结合起来,达到完美境界的数据逻辑封装效果。我们不妨给出一个极端的例子。
在数据库中有这样一个表:
R
C
DATA
1
1
XXXXX
1
2
XXXX
1
3
XXXX
1
4
XXXX
2
1
XXX
2
2
XXXX
2
3
XXX
2
4
XXXXX
我们可以看到这个表有三个字段,R(INTEGER)、C(INTEGER)、Data(VARCHAR(20))。其中R代表“行”,C代表“列”,这个表中的数据永远是4*N条,而R的值将是从1到4,这些数据来源于下面一张逻辑表:
R
DATA1
DATA2
DATA3
DATA4
1
XXX
XXX
XX
XXXXX
2
XXXXXXX
XXX
XXXX
XXXX
这个例子的含义不难理解,就好比是学校的学生站队做操一样,学生本来是按照一列纵队的形式存储,但是做操的时候,需要让他们站成4列纵队。那么我们这个案例中,数据在数据库中的存储形式是1列纵队,现在需要把这些数据按照4列纵队的方式展现开来,实现数据的存储形式和表现形式的完全分离,让数据在界面上完全像一个5列的数据集那样给最终用户展现,这个极端的例子即可很好的演示Midas和InterBase/FireBird高级特性相结合的艺术。下面我们就详细的看看具体实现技巧。
这个物理表的DDL如下:
CREATE TABLE DEMO1 (
R INTEGER NOT NULL,
C INTEGER NOT NULL,
DATA VARCHAR(20));
ALTER TABLE DEMO1 ADD CONSTRAINT PK_DEMO1 PRIMARY KEY (R, C);
首先,怎样生成目标形态的数据集呢?精通InterBase/FireBird的SQL语句的读者可以想到,用一条复杂一点的嵌套式SQL语句即可很好的生成。我们观赏一下这样的SQL语句怎么写:
SELECT DISTINCT
R AS R_MAIN,
DATA AS DATA1,
我们可以看到,这条SQL语句中运用了嵌套规则,用了三个子select,三个子select中与主select的行相关联。
(SELECT
DEMO1.DATA
FROM
DEMO1
WHERE
(DEMO1.R = DEMO_MAIN.R) AND
(DEMO1.C = 2)
) AS DATA2,
(SELECT
DEMO1.DATA
FROM
DEMO1
WHERE
(DEMO1.R = DEMO_MAIN.R) AND
(DEMO1.C = 3)
) AS DATA3,
(SELECT
DEMO1.DATA
FROM
上述复合SQL语句执行的结果如下。我们发现,这个SQL语句很好的达到了我们的要求
DEMO1
WHERE
(DEMO1.R = DEMO_MAIN.R) AND
(DEMO1.C = 4)
) AS DATA4
FROM
DEMO1 AS DEMO_MAIN
WHERE
(DEMO_MAIN.C = 1)
我们在IBExpert执行一下,看看效果。
原始物理数据形态
我们看到,果真在这个复杂的SQL的执行下,数据按照我们所希望的样子,由1列纵队变成了4列纵队。看到这里,我们非常惊叹于InterBase/FireBird所提供的丰富的SQL功能。然而,这个复杂的SQL语句并非我们在下一步的演示中要用的,接下来,我们要看看比SQL语句更加丰富的FireBird存储过程,实现这一功能。我们编写下面一个存储过程:
CREATE PROCEDURE DEMO
RETURNS (
R INTEGER,
DATA1 VARCHAR(20),
DATA2 VARCHAR(20),
DATA3 VARCHAR(20),
DATA4 VARCHAR(20))
AS
BEGIN
FOR
SELECT DISTINCT
R,
DATA AS DATA1
FROM DEMO1
WHERE (C = 1)
INTO :R,:DATA1
DO
BEGIN
SELECT DEMO1.DATA
FROM DEMO1
WHERE
(DEMO1.R = :R) AND (DEMO1.C = 2)
INTO DATA2;
SELECT DEMO1.DATA
FROM DEMO1
WHERE
(DEMO1.R = :R) AND (DEMO1.C = 3)
INTO DATA3;
SELECT DEMO1.DATA
FROM DEMO1
WHERE
(DEMO1.R = :R) AND (DEMO1.C = 4)
INTO DATA4;
SUSPEND;
END
END
我们在IBExpert中运行一下这个存储过程,发现它和刚才那个复杂的SQL实现了完全相同的效果。不过我们看到存储过程里实现这类的功能模块,几乎是手到擒来。在SQL 语句中是颇有些繁琐的东西,在存储过程中只不过是最基础的功能。在这个存储过程中,我们在一个for循环中,把后三列的值轻而易举的用3个select语句选了出来,然后放到三个列变量中,提交。有了存储过程,我们几乎可以生成任意的数据集,InterBase/FireBird的这个功能既强大,又灵活。OK!我们在Delphi程序中把数据体现出来:
1、 用IBDatabase将数据库连接好
2、 取一个IBQuery控件,里面写下如下SQL:
select * from DEMO
其中,“DEMO”是存储过程的名称。注意,访问返回数据集的存储过程,要用IBQuery,而并非存储过程控件IBStoredProc。
3、 和往常一样,我们用一个ClientDataSet和这个Query相连接,为的是取得完备的内存表特性,同时节约服务器Cursor资源。
4、 连接一个DBGrid显示数据。
设计时的控件连接关系
好了,我们如此顺利的就把4列纵队的数据表格界面实现了。难道我们现在就可以庆祝成功了吗?答案是:非也。很显然,上述实现的仅仅是一个只能看的功能模块。而我们需要的是一个能够增、删、改的完全可维护数据的数据集行为。接下来,就该Midas大展身手了。我们将借用Midas所提供的机制,来从新定义这个虚拟的数据集的增、删、改等三种行为:
放置三个IBQuery控件,分别书写以下语句
INSERT INTO DEMO1( R, C, DATA )
VALUES( :R, :C, :DATA )
UPDATE DEMO1
SET DATA = :DATA
WHERE ( R = :R ) and ( C = :C )
DELETE FROM DEMO1 WHERE ( R = :R )
我们在设计器中选中DataSetProvider控件,创建一个BeforeUpdateRecord事件函数,在函数中,用子函数的形式将上述三个IBQuery控件封装一下:
procedure TForm1.dpDemo1BeforeUpdateRecord(Sender: TObject; SourceDS: TDataSet; DeltaDS: TCustomClientDataSet; UpdateKind: TUpdateKind;
var Applied: boolean);
procedure InsertRec(
R: variant;
C: variant;
Data: variant);
begin
ibqInsertRec.ParamByName('R').Value := R;
ibqInsertRec.ParamByName('C').Value := C;
ibqInsertRec.ParamByName('DATA').Value := Data;
ibqInsertRec.ExecSQL;
end;
procedure UpdateRec(
Data: variant;
R: variant;
C: variant);
begin
ibqUpdateRec.ParamByName('DATA').Value := Data;
ibqUpdateRec.ParamByName('R').Value := R;
ibqUpdateRec.ParamByName('C').Value := C;
ibqUpdateRec.ExecSQL;
end;
procedure DeleteRec(R: variant);
begin
ibqDeleteRec.ParamByName('R').Value := R;
ibqDeleteRec.ExecSQL;
end;
begin
end;
从新定义增、删、改的代码就将填入此处
好了,这个空的事件函数已经预备好了,我们很快将把必要的内容写入到里面。首先,通过UpdateKind参数来区分三种更新动作:
begin
if UpdateKind = ukInsert then begin
//增加记录
end else if UpdateKind = ukModify then begin
//更改记录
end else if UpdateKind = ukDelete then begin
//删除记录
end;
end;
在三种UpdateKind的状态下,我们将分别作出相应的数据操作。这就是在真正的从新定义虚拟的数据集针对增删改动作的后台解释代码了。我们看看完成之后的代码:
if UpdateKind = ukInsert then begin
//增加记录
InsertRec(DeltaDS.FieldByName('R').NewValue, 1,
DeltaDS.FieldByName('DATA1').NewValue);
InsertRec(DeltaDS.FieldByName('R').NewValue, 2,
DeltaDS.FieldByName('DATA2').NewValue);
InsertRec(DeltaDS.FieldByName('R').NewValue, 3,
DeltaDS.FieldByName('DATA3').NewValue);
InsertRec(DeltaDS.FieldByName('R').NewValue, 4,
DeltaDS.FieldByName('DATA4').NewValue);
end else if UpdateKind = ukModify then begin
//更改记录
if not VarIsEmpty(DeltaDS.FieldByName('DATA1').NewValue) then
UpdateRec(DeltaDS.FieldByName('DATA1').NewValue,
DeltaDS.FieldByName('R').OldValue, 1);
if not VarIsEmpty(DeltaDS.FieldByName('DATA2').NewValue) then
UpdateRec(DeltaDS.FieldByName('DATA2').NewValue,
DeltaDS.FieldByName('R').OldValue, 2);
if not VarIsEmpty(DeltaDS.FieldByName('DATA3').NewValue) then
UpdateRec(DeltaDS.FieldByName('DATA3').NewValue,
DeltaDS.FieldByName('R').OldValue, 3);
if not VarIsEmpty(DeltaDS.FieldByName('DATA4').NewValue) then
UpdateRec(DeltaDS.FieldByName('DATA4').NewValue,
DeltaDS.FieldByName('R').OldValue, 4);
end else if UpdateKind = ukDelete then begin //删除记录
DeleteRec(DeltaDS.FieldByName('R').OldValue);
end;
Applied := True;
我们可以看到,上面的代码清晰而整齐,很容易读懂,在这个事件里轻易的实现了虚拟数据集的数据更新动作的从新定义。其中Applied := True是指告诉DataSetProvider,数据更改已经被落实了,不必再做另外的操作了。工作做完了80%,还有一小段代码没有写,那就是存盘事件:
procedure TForm1.btnSaveClick(Sender: TObject);
begin
if cdsDemo1.State in [dsInsert, dsEdit] then
cdsDemo1.Post;
cdsDemo1.ApplyUpdates(0);
IBTransaction1.CommitRetaining;
end;
我们发现整个存盘(数据更新)过程,都是在一个事务中控制的。所以,在存盘后,应提交事务。我们运行一下程序,发现增删改完全和普通的数据集一样,界面上增加了1条记录,存盘后会发现数据库里面多了4条记录,相反,删除一条之后,数据库里也相应的减少了4条记录。在实际的开发中,情形不一定有这么的极端,细节的要却要比这个例子复杂一些。比如,如果在保存过程中有异常,则应捕获异常,根据异常的内容判断是否回滚。
在这个典型案例所揭示的技巧,就是将InterBase/FireBird的精髓功能——返回数据集的存储过程,和Delphi的数据库精髓——Midas相结合的技巧。这样的结合,使我们几乎无所不能的开发任意的数据集形态的功能模块:用存储过程生成数据,用DataSetProvider的BeforeUpdateRecord事件解释数据的更新动作。更好的是,为了开发友好方便的界面,我们的DBGrid一般都换作第三方出品的最为精致的DBGrid控件,比如说DevExpress的cxGrid,这样一来,我们封装的ClientDataSet可以连接各种符合数据感知规范的控件,运用大量优秀的数据感知控件,也是我们把数据封装成为数据集的一个重要意义。在实战中,数据的更新动作被从新定义的程度不一定有这个例子这么彻底,也许仅仅定义其中的部分动作。不同的情况都会有不同注意事项,这些,都将在以后的章节中作详细讨论。