【 tulaoshi.com - 编程语言 】
第二章 单层和两层的应用程序
单层和两层的数据库应用程序相对来说比较简单,应用程序和数据库往往在同一个文件系统中,甚至就在同一个磁盘上。这两种类型的数据库应用程序都不太适合于在多用户的环境下同时访问同一个数据库。
对于单层的应用程序来说,Delphi 4提供了两种获得数据的方式,一种方式是通过BDE,另一种方式是通过文件。两层的应用程序一般要使用BDE。
2.1 基于BDE的应用程序 由于BDE以及数据访问构件处理了诸如读数据、更新数据、记录导航等细节,编写一个两层的应用程序与编写一个单层的基于BDE的应用程序几乎没有什么区别。
发布基于BDE的应用程序时,必须同时发布BDE,这将使应用程序的字节数大大增加,同时也增加了发布、安装的难度。但不管怎么样,BDE的作用还是无法替代的 。
2.1.1 基于BDE的体系结构 一个基于BDE的单层或两层应用程序通常由这么几个部分组成:
.用户界面,其主要部件是数据控件;
.一个或几个数据集构件,用于从数据库引入数据;
.一个或几个TDataSource构件,用于连接数据集与数据控件;
.一个或几个TDatabase构件(可选),用于控制事务。在两层的应用程序中,TDatabase构件还能够管理数据库连接的持续性和要不要登录;
.一个或几个TSession构件(可选),用在多线程的应用程序中管理数据库的连接。
图2.1演示了基于BDE的应用程序的体系结构:图2.1 基于BDE的体系结构 。
2.1.2 理解数据库和数据集 数据库与数据集是既有联系又有区别的两个概念。数据库既包含了表格中的数据,而且还包含了表格本身的属性、索引以及存储过程等。而数据集主要用于引入表格中的数据。一个基于BDE的应用程序至少得有一个数据集构件。
每个基于BDE的数据集构件都有一个公开的属性叫DatabaseName,这个属性用于指定要访问的数据库。
DatabaseName属性可以设为数据库的BDE别名。一个别名不但代表了数据库,也代表了数据库的配置信息。不同类型的数据库如Oracle、Sybase、Interbase、Paradox、dBASE,它们的配置信息也不同。用BDE Administration或SQL Explorer可以创建和管理BDE别名。
对于Paradox或dBASE数据库来说,DatabaseName属性也可以设为表格所在的目录。如果应用程序显式地使用了TDatabase构件来连接数据库,DatabaseName属性可以设为应用程序专用的别名。
2.1.3 使用会话期对象 会话期对象(TSession)用在多线程的数据库应用程序中管理数据库的连接。会话期对象的作用主要体现在四个方面。
管理BDE别名。可以用会话期对象创建新的别名、删除别名、修改别名的 参数。默认情况下,用会话期对象创建和修改的别名只在本会话期是有效的,但是,您可以调用TSession的SaveConfigFile把别名永久地保存到BDE配置文件中,这样,其他会话期和其他应用程序都可以使用这些别名。
在两层的数据库应用程序中控制数据库的连接。如果把TSession的KeepConnections属性设为True,即使当前没有数据集在活动状态,也保持与数据库的连接,这样虽然浪费一点资源,但可以避免下次连接数据库时再次登录。在单层的数据库应用程序中对Paradox和dBASE提供口令保护。既可以显示一个对话框让用户输入口令,也可以在程序中提供口令。如果计划将单层的体系结构平滑地过渡到两层或多层的体系结构,就必须设计一个人机界面,让用户输入用户名和口令,即使目前在单层的数据库应用程序中用不着。
指定BDE网络控制文件PDOXUSRS.NET和存放私有文件的目录。
如果应用程序需要同时在几个地方访问同一个数据库,就要用多个会话期对象来管理数据库的连接。如果没有这样做的话,很可能会造成意想不到的后果。
2.1.4 连接数据库 BDE中包含了不同类型的驱动程序用来连接不同类型的数据库。Delphi4的Standard版本中包含了访问本地数据库的驱动程序如Paradox、dBASE、FoxPro和Access。Delphi 4的Professional版本除了访问本地数据库的驱动程序外,还包含了ODBC驱动程序,通过ODBC驱动程序可以访问更广泛的数据源。Delphi4的Client/Server版本和Enterprise版本还包含了SQL Links,可以访问远程大型数据库,包括Interbase、Oracle、Sybase、Informix、Microsoft SQL Server和DB2。
2.2 事 务 一个事务实际上是一组动作,这些动作必须在表格被提交之前成功地执行。如果有某个动作执行失败,所有的动作都将撤消(Undo),这样能保证数据库中不会有不一致的数据。
默认情况下,BDE提供了隐含的事务处理能力,当数据集的某条记录要写到数据库中时,BDE能保证不会有部分字段被更新而另一个部分字段没有被更新的情况。
在多用户的环境下尤其是访问SQL服务器的情况下,最好显式地使用事务,因为隐含的事务处理能力毕竟是有限的,它会导致网络开销增大、应用程序性能下降。
2.2.1 显式地使用事务 在基于BDE的数据库应用程序中,要显式地使用事务,有两种方式,这两种方式是互斥的,不能同时使用。
通过TDatabase构件来连接数据库,然后通过TDatabase的StartTransaction、Commit、Rollback、InTransaction和TransIsolation等属性和方法来控制事务。这种方式的好处是,不需要依赖于特定的数据库或服务器,程序的移植性较好,没有多余的代码。
使用所谓的“Passthrough SQL”,即通过TQuery构件把SQL语句直接传递给远程的SQL服务器或ODBC服务器。这种方式的好处是,可以直接使用服务器的事务管理能力,不过,这与特定的服务器有关,程序的移植性很难得到保证。
单层的应用程序不能使用“Passthrough SQL”方式,只能用TDatabase构件来控制事务。而两层的应用程序既可以使用TDatabase构件,也可以使用“Passthrough SQL”方式。
2.2.2 TDatabase构件怎样控制事务 首先要调用StartTransaction开始一个事务。一旦启动了一个事务,以后所有的读写操作均与事务有关,除非显式地终止了事务。可以访问TDatabase的InTransaction属性,如果这个属性返回True,表示现在已经开始了一个事务。此时,要检索数据库中的数据,取决于事务隔离级别(TransIsolation属性)。
启动了一个事务后,就可以对数据库进行读写操作,为了使修改后的数据永久地保存到数据库中,应当调用TDatabase的Commit来提交。Commit通常放在Try...Except结构的Try部分调用,这样,如果Commit没有调用成功,还有机会调用Rollback。程序示例如下:
Database1.StartTransaction;
Try
Table1.Edit;
Table1.FieldByName('CustNo').AsInteger := 100;
Table1.Post;Database1.Commit;
Exception
Database1.Rollback;
End;
2.2.3 TransIsolation属性 如果有多个事务同时在处理,并且访问的是同一个表,这些事务彼此之间怎样相互影响,是由TransIsolation属性设置的事务隔离级别决定的。
如果这个属性设为tiDirtyRead,允许读其他事务对数据库尚未提交的修改。未提交的修改随时有可能滚回,因此,这种情况下读出来的数据是不可靠的。
如果这个属性设为tiReadCommitted,允许读其他事务对数据库已经提交的修改。如果这个属性设为tiRepeatableRead,不允许读其他事务对数据库的修改。不同的服务器支持不同的事务隔离级别,如果TransIsolation属性设置的事务隔离级别不被服务器所支持,BDE将自动降低事务隔离级别。
注意:对Paradox、dBASE、Access和FoxPro等本地数据库使用事务时,应当把TransIsolation属性设为tiDirtyRead而不是默认值tiReadCommitted,否则,BDE会报错。
2.2.4 Passthrough SQL 所谓“Passthrough SQL”,就是通过TQuery、TStoredProc或TUpdateSQL构件把SQL语句直接传递给远程服务器,这些SQL语句中包含了对服务器事务处理功能的调用。
要使用“Passthrough SQL”,必须满足几个条件:必须是Client/Server版本,并且已安装了SQL Links驱动程序。正确配置了有关网络协议。具有访问远程服务器的权限。必须用SQL Explorer把“SQLPASSTHRU MODE”参数设置为“NOT SHARED”。
2.2.5 本地事务 对于Paradox、dBASE、Access和FoxPro等本地数据库来说,BDE也提供了事务处理能力,这称为本地事务。
从编程的角度看,本地事务与针对远程数据库的事务没有什么两样。不过,它的功能受到限制:
.没有崩溃自恢复功能
.不支持数据定义语句。
.不能针对临时表进行事务。
.对于Paradox表来说,必须建立了索引,否则,数据没法回滚。
.能够被锁定和修改的记录数是有限的,Paradox限于255条记录,dBASE限于100条记录。
.对于ASCII文本文件不能进行事务。
.事务隔离级别(TransIsolation属性)只能设为tiDirtyRead。
2.2.6 缓存更新 BDE提供了缓存更新技术。缓存更新的过程是这样的:应用程序从数据库检索数据,对数据进行修改,实际上是在本地的缓存中,以后可以申请把缓存中的数据更新数据集。
显然,缓存更新技术能够提高效率,减少网络上的传输量。不过,缓存更新与事务还是有区别的。缓存中的数据只是对本应用程序是可见的,在申请更新之前,其他应用程序无法知道这些数据究竟作了哪些修改。所以,对于那些频繁修改的数据不适用缓存更新技术,因为A用户可能把数据改为10,而B用户又把数据改为20,C用户又把数据改为30,他们完全有可能同时向数据库申请更新,这就会造成冲突。
正是基于上述原因,数据集构件都有一个CachedUpdates属性,让您选择要不要使用缓存更新技术。
2.2.7 创建和重构表格 在基于BDE的应用程序中,可以用TTable构件动态地创建一个表格,或者在一个已有的表格中建立一个索引。
要创建一个表格,首先要建立字段定义(FieldDefs属性),然后要建立索引定义(IndexDefs属性),最后调用CreateTable。也可以在设计期用鼠标右键单击TTable构件,在弹出的菜单中选择“Create Table”命令。
注意:如果要创建Oracle8类型的表格,Delphi 4目前还不能创建ADT字段、数组字段、引用字段和数据集字段。
在运行期也可以改变表的结构(这称为重构),这就需要调用BDE的一个API叫DbiDoRestructure。不过,如果仅仅要建立一个索引,调用AddIndex就可以了。
在设计期,您可以使用Database Desktop创建和重构Paradox和dBASE表格,使用SQL Explorer可以创建和重构SQL表格。
2.3 基于文件的单层数据库应用程序 基于文件的单层数据库应用程序要用到TClientDataSet构件。TClientDataSet构件能够从文件中存取数据,在内存中建立数据的一个副本,这样,对数据的访问和操作非常快,但数据的容量受内存的限制。
2.3.1 TClientDataSet
TClientDataSet不需要依赖于BDE,这意味着应用程序不必为BDE开销内存,但数据本身需要内存。TClientDataSet只需要一个动态链接库DBCLIENT.DLL的支持,因此,发布和安装用TClientDataSet建立的程序是比较简单的,省掉了BDE的配置和维护。
正因为TClientDataSet没有使用BDE,因此,基于文件的单层数据库应用程序不适用于多用户环境下使用。尽管TClientDataSet不支持BDE,但由于TClientDataSet是从TDataSet继承下来的,因此,大多数能对TTable构件进行的操作也能对TClientDataSet构件进行,包括用标准的数据控件显示TClientDataSet引入的数据。不必使用TDatabase构件,因为没有数据库需要管理,也不需要支持事务。也不必使用TSession构件,除非应用程序是多线程的。
基于BDE的数据库应用程序和基于文件的数据库应用程序的区别在于,创建数据集的方式和存取数据的方式不同。
2.3.2 在设计期创建数据集 由于基于文件的单层数据库应用程序使用的不是现成的数据库,因此,它的首要任务就是创建一个数据集。创建了一个数据集后,可以把它保存到文件中,以后就可以从文件中取出来,不必再重建数据集。不过,保存数据集时,索引不会一起保存,因此,每次从文件中读取数据集时,都得重新建立索引。
可以在设计期用字段编辑器创建数据集,其一般步骤是:
把一个TClientDataSet构件放到窗体上,单击鼠标右键,在弹出的菜单中选择“Fields Editor”命令,Delphi 4将打开字段编辑器,如图2.2所示。
图2.2 字段编辑器
在字段编辑器上单击鼠标右键,在弹出的菜单中选择“New Field”命令,Delphi 4将打开“New Field”对话框,如图2.3所示
在“Name”框内键入字段名,在“Type”框内选择字段的数据类型,在“Size”框内设置字段长度,“Component”框将自动生成该字段的对象名。
“Field Type”框用于指定新字段的生成类型(不是指字段的数据类型),可以选“Data”、“Calculated”、“Lookup”、“InternalCalc”和“Aggregate”。
用字段编辑器创建的字段称为永久字段。创建了永久字段后,用鼠标右键单击TClientDataSet构件,在弹出的菜单中选择“Create DataSet”命令,此时,Delphi 4将创建一个没有记录的空数据集。再次用鼠标右键单击TClientDataSet构件,在弹出的菜单中选择“Save To File”命令,把数据集保存到文件中,扩展名是.CDS。
2.3.3 在运行期创建数据集 要在运行期创建基于TClientDataSet的数据集,首先要建立字段定义和索引定义,这一点与创建基于TTable构件的数据集相似,不同的是,TClientDataSet没有DatabaseName、TableName或TableType等属性,因为TClientDataSet并不需要直接访问数据库。
建立索引要用到IndexDefs属性,这个属性是TIndexDef对象,它有两个属性这里详细解释一下:一是DescFields属性,另一个是CaseInsFields属性。
如果TIndexDef的Options属性中包含ixDescending元素,记录按所有字段的降序排列。如果您想使记录按部分字段的升序排列,按另一部分字段的降序排列,就要用到DescFields属性。假设一个数据集有三个字段,分别叫Field1、Field2和Field3,如果把DescFields属性设为“Field1;Field3”,表示按Field2的升序排列,按Field1和Field3的降序排列。
CaseInsFields属性的作用与DescFields属性的作用类似,这里不再赘述。
建立了字段定义和索引定义后,就要调用CreateDataSet,程序示例如下:
Procedure TForm1.FormCreate(Sender: TObject);
Begin
With ClientDataSet1 Do
Begin
{定义一个字段叫Field1}
With FieldDefs.AddFieldDef Do
Begin
DataType := ftInteger;
Name := 'Field1';
End;
{定义一个字段叫Field2}
With FieldDefs.AddFieldDef Do
BeginDataType := ftString;
Size := 10;
Name := 'Field2';
End;
{定义一个索引叫IntIndex}
With IndexDefs.AddIndexDef Do
Begin
Fields := 'Field1';
Name := 'IntIndex';
End;
CreateDataSet;
End;
End;
2.3.4 基于一个已有的表格创建数据集 如果要把一个基于BDE的应用程序转换为单层的基于文件的应用程序,首先要把一个已有的表格转换为TClientDataSet能识别的格式,操作步骤是这样的:
把一个TClientDataSet构件加到窗体上,单击鼠标右键,在弹出的菜单中选择“Assign Local Data”命令,弹出一个对话框,让您从本地的基于BDE的数据集中引入数据,如图2.4所示。
选择一个本地的数据集,然后单击OK按钮。
再次用鼠标右键单击TClientDataSet构件,在弹出的菜单中选择“Save ToFile”命令。
2.3.5 在文件中存取数据 在单层的基于文件的应用程序中,TClientDataSet能自动维护一个数据变动情况的记录。如果不准备恢复原来的数据,可以调用MergeChangeLog把变动情况合并在数据中。
不过,即使把变动情况合并到数据中,数据仍然还在内存中,当应用程序关闭时,这些数据将丢失。因此,还必须调用SaveToFile把数据保存到文件中。
SaveToFile只保存数据集的结构和数据,但不保存索引。因此,每次打开数据集时都要重建索引。
要打开先前用SaveToFile保存的数据集,可以调用LoadFromFile。
如果每次打开和保存的文件是不变的,也可以设置TClientDataSet的FileName属性。当TClientDataSet的Active属性设为True时,就会自动从指定的文件中读取数据。当TClientDataSet的Active属性设为False时,就会自动把数据保存到指定的文件中。
2.3.6 公文包模式 对于那些经常出差需要在路上办公的人来说,出发之前往往要从数据库中获取某些数据,回来以后又要把修改后的数据写回到数据库中,这就是所谓的“公文包”模式。
在多层体系结构的应用程序中,TClientDataSet一般是通过IProvider接口从应用服务器获取数据。而要按“公文包”模式工作,就必须在文件中存取数据。
“公文包”模式的关键是把数据保存到文件中,因此,客户程序的用户界面应当允许用户从应用服务器检索数据并保存到文件中,允许用户从文件中调出数据并更新数据库,而TClientDataSet的LoadFromFile和SaveToFile完全可以实现这些功能。
“公文包”模式的另一个关键是客户程序必须能够判断当前是否连接了应用服务器,换句话说,就是即使在离线的情况下,客户程序也要能够工作。
2.3.7 平滑过渡到三层体系结构 在两层的体系结构中,一层是数据库服务器,另一层是应用程序。应用程序在逻辑上又分为两大部分,一是用户界面,另一个是数据访问链路。要把一个两层的Client/Server应用程序平滑过渡在三层的Client/Server应用程序,一般采取这么几个步骤。
第一步是创建一个新的项目作为应用服务器,然后把数据访问链路部分放到应用服务器上。再把TDataSetProvider或TProvider构件加到应用服务器上,有一个数据集,就要加一个TDataSetProvider或TProvider构件。
第二步是修改已有的两层体系结构,把涉及到数据访问链路的部分去掉,这样,这个程序只剩下用户界面部分。
第三步是用TClientDataSet构件替换原来的数据集构件,然后加入一个或几个MIDAS连接构件,如TDCOMConnection。