2.2 连接数据库
终于到这个标题了,一个MIS系统不可能没有数据库的支持,如果没有了就变成“没本之木,无源之水”。在这个教程中,我选用的数据库是MS SQL Server系列,你可以使用SQL Server 2000或者2005,2005有一个免费的Express版本,你当然也可以使用它。这个教程并不打算教大家如何使用SQL Server(我也没有这本事,大家可以有空多上CSDN看看邹健大侠的文章),所以怎么安装,设置和建表之类的事情我就不多说了。我在这里为我们的MIS系统准备了一个简单的操作员表,如下图所示:
当然在实际的应用中这么简单的表结构是不行的,但在这一章里面我们只需要这么多字段就可以说明问题。在这里,OperatorID表示的是操作员号,PSW表示的是该操作员的密码,为了简单,这里使用了明文密码。另外一个问题需要说明,大家最好不要使用password作为字段名,因为有些数据库认为password是一个关键字(例如FireBird或Interbase),不能用来作字段名,因此为了你程序的可移植性,不要使用这种敏感的词。当然如果只是在MSSQL上使用的话,那也可以使用。
除了建立好对应的表以外,为了能成功使用数据库,我们还需要了解数据库服务器所在的位置等,例如我们这次使用的是建立在本机上的数据库,所以我可以通过127.0.0.1或(local)来访问服务器,对应的数据库名是mditut,同时我的SQL Server数据库用户和密码分别是sa/sa。
建立并了解数据库后,我们就可以在我们的MIS程序中使用数据库了。我们选用的数据库控件是Delphi里最通用(不是最好用的)的ADO组件,如下图所示:
在这个组的控件里面,ADOConnection是数据库连接的控件,其它的ADODataSet,ADOTable等等都是用于返回结果集的控件的,我们会在下面再说明。在这里需要特别说明的是,因为除了ADOConnection能通过一定的配置能连接上数据库外,其它所有的ADO控件都能单独通过配置连接串来连接上数据库,因为它们内部都包含了一个ADOConnection控件。但我认为一个MIS的应用应该由此至终只使用一个数据库的连接(当然如果你的MIS应用需要同时连接两个不同的数据库的话,那么也确实存在多个连接,就像我写的FlexQue一样),这是因为在客户端每增加一个连接,数据库服务器那边都会相应增加一定的资源来为维持这一连接的。所以连接数越多,数据库服务器的资源就耗用得越多。基于上面的理由,所以我们的程序中只使用一个ADOConneciton,其它要的ADO控件都通过这个ADOConnection来获取数据库的连接。
那么这个ADOConneciton应该放在什么地方比较合适呢?我们现在来看程序:
在MDI_Tutorial.dpr中
……
begin
Application.Initialize;
frmLogin := TfrmLogin.Create(Application);
frmLogin.ShowModal;
if frmLogin.RetValue then
begin
Application.CreateForm(TfrmMain, frmMain);
end;
frmLogin.free;
Application.Run;
GFormClassMgr.Free;
end.
从这里,我们可以看出来,frmLogin是我们的登录窗体,在这个窗体中就需要查找数据库中的tb_operator表的,所以我们的数据库接连接应该放在frmLogin中或者放在frmLogin之前进行初始化。现在我们先来分析第一种的情况,把ADOConneciton放在frmLogin中,这样好办,我们可以在frmLogin中放上一个ADOConnection控件。双击这个控件会弹出一个对话框,如下图所示
选择下面的use connection string,然后选择build的,接着会弹出另外的对话框
选择Microsoft OLE DB Provider for SQL Server,然后按下一步,
然后在弹出的对话框中按要求填定好对应的值,最好点击确定。这个对话框会关闭,同时刚才的对话框中会自动填上组装好的连接串:
这个连接串十分有用,我们把它复制下来放到记事本上,我们接下来在别的地方也会用到它的。
接下来的工作就好办了,我们改造一下frmLogin中的代码:
procedure TfrmLogin.btnOKClick(Sender: TObject);
var
qry : TADOQuery;
k : integer;
begin
qry := TADOQuery.Create(nil); //mark A
qry.Connection := ADOConnection1;
qry.SQL.Add('select count(*) from tb_operator where operatorid =:opid and psw =:psw');
qry.Parameters.ParamByName('opid').Value := edtOperatorID.text; //mark B
qry.Parameters.ParamByName('psw').Value := edtPassword.Text;
qry.Open;
k := qry.Fields[0].AsInteger;
qry.Close;
qry.Free;
if k = 1 then
begin
FRetValue := true;
Close;
end
else
begin
MessageBox(Handle, '错误的用户名或密码!', 'MDI_Tutorial', MB_ICONWARNING or MB_OK);
edtOperatorID.SetFocus;
end;
end;
在上面的代码中,我们使用了一个ADOQuery进行查询数据库,在Delphi的组件面板中我们可以通过拖放在形式来使用ADOQuery,但其实有时候使用代码来完成可能更方便,就像上面Mark A中的代码就可以了。因为我们要把用户输入的操作员号和密码与数据库中tb_operator表的数据进行比较,所以,我们不可避免得要使用sql 的where子句,很多初用Delphi的朋友都会使用‘拼语句’的方式来完成where子句中的条件编写,其实在delphi的ADO中支持使用“参数”的方式,就像Mark B中的写法一样,通过使用参数的方式,使得整个语句写起来更加的清爽!
经过这样的改造,我们来运行一下程序,如果你没拼写错误的话,那么程序应该能正常的运行起来。并且能正确判断用户所输入的操作员号跟密码是否正确。
所有的东西到目前来看都十分美好,但事实真是这样吗?
2.3 把数据库连接独立出来
我们把frmLogin的代码完成了,同时在frmLogin的数据库连接也完成了,当然这个连接按我们上面的设想,应该是供应给往后的窗体所使用的,要不然这个连接又重新建立一次的话,服务器的资源会相应的增加不少的。那么现在我们来看frmLogin所建立的数据库连接怎样在其它窗体中使用,首先我们来看MDI_Tutorial.dpr中的代码
……
begin
Application.Initialize;
frmLogin := TfrmLogin.Create(Application); //mark A
frmLogin.ShowModal;
if frmLogin.RetValue then
begin
Application.CreateForm(TfrmMain, frmMain); //mark B
end;
frmLogin.free; //mark C
Application.Run;
GFormClassMgr.Free;
end.
我们可以知道能过mark A标识的语句,我们的frmLogin建立起来了,同时作为frmLogin中的成员变量的ADOConneciton1也会同时被建立起来的,当程序完成mark B的语句后,我们应该把这个ADOConnection1的值赋予给frmMain中,以便frmMain中能连接上数据库,但当程序来到mark C时,因为要释放掉 frmLogin,所以frmLogin中的ADOConnection1也会被释放掉的,所以当程序进行Windows的消息循环之前,数据库连接就被破坏掉了,frmMain如果调用的话,那么只会产生错误的信息。
由于这个错误的信息是因为frmLogin被析构引起的,那么我们先不急于把frmLogin析构就可以了,把frmLogin.free放在Application.Run之后就不会产生这样的问题了。但为了保持数据库连接而不释放整个的登录窗体,这样值得吗?
为了使得整个程序的条理能更清晰,我们引入另一个全局的变量GBaseConf,这个变量存放于BaseConfUnit.pas中,代码如下:
{
MDI Tutorial v2.3
Written by flexitime.
}
unit BaseConfUnit;
interface
uses
DB, ADODB;
type
TBaseConf = class (TObject)
private
FMainConnection : TADOConnection;
public
constructor Create;
destructor Destroy; override;
property MainConnection : TADOConnection read FMainConnection;
procedure BuildConnection;
end;
var
GBaseConf : TBaseConf;
implementation
{ TBaseConf }
procedure TBaseConf.BuildConnection;
begin
FMainConnection.ConnectionString := 'Provider=SQLOLEDB.1;Password=sa;Persist Security Info=True;User ID=sa;Initial Catalog=mditut;Data Source=(local)'; //mark A
FMainConnection.Open;
end;
constructor TBaseConf.Create;
begin
FMainConnection := TADOConnection.Create(nil);
FMainConnection.LoginPrompt := false; // mark B
BuildConnection;
end;
destructor TBaseConf.Destroy;
begin
if FMainConnection <> nil then
begin
if FMainConnection.Connected then FMainConnection.Close;
FMainConnection.Free;
end;
inherited;
end;
end.
这个单元的代码其实也比较简单,在MarkA中,就是我刚才说让大家保留到记事本的那个连接串代码了。Mark B的作用是禁止 ADOConnection弹出数据库登录窗口。整个BaseConf的作用其实就是初始化一个数据库连接,并保持它。当然,目前BaseConf中只有这么一点的内容。为了保持一个数据库连接而引入一个全局变量确实有小题大做之嫌,但随着我们程序编写的深入,我们会发现整个MIS中其实还会有很多基本的变量需要一直保存下来的,我们就把这些变量全部都放到BaseConf中来统一管理,这样整个程序就会变得有条理了,而且减少内存泄露的风险。
接着我们来修改MDI_Tutorial.dpr中的内容:
……
Application.Initialize;
GBaseConf := TBaseConf.Create;
frmLogin := TfrmLogin.Create(Application);
frmLogin.ShowModal;
if frmLogin.RetValue then
begin
Application.CreateForm(TfrmMain, frmMain);
end;
frmLogin.free;
Application.Run;
GFormClassMgr.Free;
GBaseConf.Free;
……
在这个单元中,我们添加了GBaseConf的初始化以及析构的语句。
接着再来修改frmLogin中的内容,我们首先把刚才放到窗体上的ADOConnection1控件删除掉。然后再来修改以下的代码:
……
implementation
uses
BaseConfUnit;
……
procedure TfrmLogin.btnOKClick(Sender: TObject);
var
qry : TADOQuery;
k : integer;
begin
qry := TADOQuery.Create(nil);
qry.Connection := GBaseConf.MainConnection;
qry.SQL.Add('select count(*) from tb_operator where operatorid =:opid and psw =:psw');
……
我们引用了BaseConfUnit单元,并把连接串的位置换掉了。
把上面的代码修改都完成后,现在就让我们来测试一下程序,如果没有其它拼写问题的话,那么程序应该能正常运行起来的。
经过上面的改造后,其它的窗体,如frmMain需要连接数据库的话,那么它只须像frmLogin一样,引用BaseConfUnit单元,并调用GBaseConf.MainConnection就可以了。