(评分:★★★ , 回复: 24, 阅读: 2049) 在数据库应用的三层结构中,客户端能够取得应用程序服务器的接口方法来获取与更新数据. 我所作的尝试是,在中间层运用OOP设计方法,将数据封装在一些对象中,在客户端获取这些对象的 接口的引用来实现获取数据及更新数据. 由于COM+提供了事务处理能力,并且能轻易实现Object Pooling技术,所以我选择了COM+组件 形式作为中间层. 第一步,新建一个ActiveX Library,新增一个Transactional Data Module,名为UserMgr.意思为用户管理模块,提供的接口为IUserMgr.在数据模块中放入一个TADOConnection(ADOConnection1), 一个TADOQuery(ADOQuery1). 设置ADOConnection1的连接属性,将ADOQuery1 的Connection属性设为ADOConnection1.在UserMgr的OnActive与OnDeactive事件中处理据库连 接: procedure TUserMgr.MtsDataModuleActivate(Sender: Tobject); begin ADOConnection1.Connected:=True; end; procedure TUserMgr.MtsDataModuleDeactivate(Sender: Tobject); begin ADOConnection1.Connected:=False; end; 第二步,开始我们的OOP设计了.由于这个模块的目的是用户管理,所以写封装用户信息的 Tuser类,由于这个类需要被客户端引用.所以我们采用一个新的基类-----TAutoIntfObject. 先回到Type Library,新增一个接口,名为Iuser,在这个接口下新增以下几个属性: UserID: long read only; LoginID:BSTR read write; UserName:BSTR read write; Password;BSTR read write; Updated:Variant_Bool read write; //记录是否被修改 Deleted; Variant_Bool read write; //记录是否被删除 再新增一个Unit名为uUser,在里面写上Tuser的声明与实现,该单元如下: unit uUser;
interface
uses Classes, SysUtils, TestServer_TLB, ComObj, ComServ, uUserMgr;
type Tuser=class(TAutoIntfObject,Iuser) private FUserID:Integer; //FUserID=-1时代表为新增用户 FLoginID:string; FUserName:string; Fpassword:string; Fupdated:Boolean; Fdeleted:Boolean; protected function Get_UserID: Integer; safecall; function Get_LoginID: WideString; safecall; procedure Set_LoginID(const Value: WideString); safecall; function Get_UserName: WideString; safecall; procedure Set_UserName(const Value: WideString); safecall; function Get_Password: WideString; safecall; procedure Set_Password(const Value: WideString); safecall; function Get_Updated: WordBool; safecall; procedure Set_Updated(Value: WordBool); safecall; function Get_Deleted: WordBool; safecall; procedure Set_Deleted(Value: WordBool); safecall; public constructor Create(AUserID:Integer=-1;ALoginID:string=''; AUserName:string=''; Apassword:string='');reintroduce; end;
implementation
{ Tuser }
constructor Tuser.Create(AUserID: Integer; ALoginID, AUserName, Apassword: string; Aadmin: Boolean); begin inherited Create(ComServer.TypeLib,Iuser); FUserID:=AUserID; FLoginID:=ALoginID; FUserName:=AUserName; Fpassword:=Apassword; end;
function Tuser.Get_Deleted: WordBool; begin Result:=Fdeleted; end;
function Tuser.Get_Updated: WordBool; begin Result:=Fupdated; end;
function Tuser.Get_LoginID: WideString; begin Result:=FLoginID; end;
function Tuser.Get_Password: WideString; begin Result:=Fpassword; end;
function Tuser.Get_UserID: Integer; begin Result:=FUserID; end;
function Tuser.Get_UserName: WideString; begin Result:=FUserName; end;
procedure Tuser.Set_Deleted(Value: WordBool); begin Fdeleted:=Value; end;
procedure Tuser.Set_Updated(Value: WordBool); begin Fupdated:=Value; end;
procedure Tuser.Set_LoginID(const Value: WideString); begin if FLoginID <> Value then begin FLoginID:=Value; Fupdated:=True; end; end;
procedure Tuser.Set_Password(const Value: WideString); begin if Fpassword <> Value then begin Fpassword:=Value; Fupdated:=True; end; end;
procedure Tuser.Set_UserName(const Value: WideString); begin if FUserName <> Value then begin FUserName:=Value; Fupdated:=True; end; end; 由于Tuser实现了Iuser,而Iuser继承自Idispatch,所以客户端可以通过获取Iuser的接口来访问 位于中间层的Tuser.
第三步, 由于用户不止一个,我们将创建一个用户列表类TUserList,实现新增或者删除一个用 户,修改一个用户的信息等. 先也是回到Type Library,新增一个接口IUserList, 再新增两个性: Item[Index]:Iuser; 它为只读属性. 注意, 在这里用到了Iuser接口,用户将可以通过Item 来获取一个Tuser对象的接口. Count:Long ; 它也为只读属性. 代表用户的个数. 再增加三个方法: New: New的返回类型为Iuser,它可以实现新增一个用户. Delete: 传入一个参数Index:long, 它用来实现删除一个用户; ApplyUpdates:更新当前所有已经修改(包括新增,删除)的用户信息; 同样的,TUserList也是继承自TAutoIntfObject,实现IUserList接口. 以下是TUserList的代码: unit uUserList;
interface
uses Classes, SysUtils, ComObj, ComServ, TestServer_TLB, uUser, uUserMgr, Dialogs;
type TUserList=class(TAutoIntfObject,IUserList) private FDM:TUserMgr; Fref:Iunknown; Flist:TInterfaceList; procedure Initialize; protected function Get_Item(Index: Integer): Iuser; safecall; function Get_Count: Integer; safecall; procedure Delete(Index: Integer); safecall; function New: Iuser; safecall; function ApplyUpdates: Integer; safecall; public constructor Create(ADM:TUserMgr; Aref:Iunknown);reintroduce; destructor Destroy;override; end;
implementation
{ TUserList }
function TUserList.ApplyUpdates: Integer; var I:Integer; User:Iuser; begin Result:=0; with FDM, ADOQuery1 do try for I:=0 to Flist.Count-1 do begin User:=Iuser(Flist.Items[I]); if User.Deleted and (User.UserID>0) then //删除 begin Close; SQL.Clear; SQL.Add('DELETE Users WHERE UserID=' + IntToStr(User.UserID)); ExecSQL; Inc(Result); end else if User.Updated and (User.UserID=-1) then //新增 begin Close; SQL.Clear; SQL.Add('INSERT INTO Users(LoginID,UserName,Password)'); SQL.Add('VALUES(:LoginID,:UserName,:Password)'); Parameters[0].Value:=User.LoginID; Parameters[1].Value:=User.UserName; Parameters[2].Value:=User.Password; ExecSQL; Inc(Result); end else if User.Updated and (User.UserID > 0) then //修改 begin Close; SQL.Clear; SQL.Add('UPDATE Users SET LoginID=:LoginID, UserName=:UserName,'); SQL.Add(' Password=:Password); SQL.Add('WHERE UserID=:UserID'); Parameters[0].Value:=User.LoginID; Parameters[1].Value:=User.UserName; Parameters[2].Value:=User.Password; Parameters[3].Value:=User.UserID; ExecSQL; Inc(Result); end; end; Initialize; SetComplete; except SetAbort; Result:=-1; end; end;
constructor TUserList.Create(ADM: TUserMgr; Aref:Iunknown); begin inherited Create(ComServer.TypeLib,IUserList); FDM:=ADM; Fref:=Aref; Flist:=TInterfaceList.Create; Initialize; end;
procedure TUserList.Delete(Index: Integer); begin Iuser(Flist.Items[Index]).Deleted:=True; //作删除标记 end;
destructor TUserList.Destroy; begin Fref:=nil; Flist.Clear; Flist.Free; inherited; end;
function TUserList.Get_Count: Integer; begin Result:=Flist.Count; end;
function TUserList.Get_Item(Index: Integer): Iuser; begin Result:=Flist.Items[Index] as Iuser; end;
procedure TUserList.Initialize; var User:Iuser; begin Flist.Clear; with FDM,ADOQuery1 do try Close; SQL.Clear; SQL.Add('SELECT UserID, LoginID, UserName, Password'); SQL.Add('FROM Users'); Open; while not Eof do begin User:=Tuser.Create(FieldByName('UserID').AsInteger, FieldByName('LoginID').AsString, FieldByName('UserName').AsString, FieldByName('Password').AsString) Flist.Add(User); Next; end; Close; SetComplete; except SetAbort; end; end;
function TUserList.New: Iuser; begin Result:=Tuser.Create; Flist.Add(Result); end;
end. 这段代码里,肯定有不少人会不明白为什么创建这个对象时要加上一个Aref:Iunknown参数吧? 这是一个技巧,因为这个对象是通过一个服务器的一个方法输出,方法执行完后,将进入Deactive 状态,一段时间后,COM+对象若再无客户端连接的话,它会释放,这样,对象Tuser中的FDM引用将无效,无法更新数据.所以,在创建这个TUserList对象时,我们人为地引用一次COM+服务器对象. 直到TUserList释放时,再释放该引用.
第四步, 在数据模块中新增一个方法,让用户可以通过该方法获取IUserList接口.也是先回到 Type Library Editor,在IUserMgr接口下新增一个GetUserList方法.刷新后写上以下实现代码: function TUserMgr.GetUserList: IUserList; begin _AddRef; Result:=TUserList.Create(Self, Self as Iunknown); _Release; end; 你一定会问,为什么要加上_AddRef与_Release呢,因为Self as Iunknown会增加一次引用,若不人 为用_AddRef来增加一次引用,将导致一退出该过程,TUserList就将自动释放,用户端无法引用该 对象.
OK,到这里为止,服务器部分算是完成了,那么客户端如何使用这个对象呢? 在连接服务器上, 我比较喜欢先期连接,即客户端直接引用服务器的Type Library,这样编程方便,速度性能好. 以下是客户端部分代码: uses ….,TestServer_TLB;
var UserList:IUserList;
procedure TForm1.FormCreate(Sender: Tobject); var I:Integer; Item:TListItem; User:Iuser; begin FUserList:=CoUserMgr.CreateRemote('192.168.1.1').GetUserList; Lv.Clear; //Lv为TListView for I:=0 to FUserList.Count-1 do begin User:=FUserList.Item[I]; Item:=Lv.Items.Add; Item.Caption:=User.LoginID; Item.SubItems.Add(User.UserName); end; if Lv.Items.Count>0 then Lv.ItemIndex:=0; end;
后记:这可是我这一次发表的"长篇大论", 其实自己也觉得没什么高深的技术,新各位看后不 要见笑,有什么更好的看法大家多多交流,呵呵.
2003-5-12 20:19:00
|