这个模块本质上就是一个TDataModule,我采用了 ado来作为访问数据库的组件。为了隔离其他模块对ado组件的依赖,我在其他实现模块中使用TClientDataSet来作为操作的数据集。实现了以下方法:
execSql -执行一条更新SQL代码,不需要返回结果,如果有异常就自动抛出。
getData - 执行一条select SQL代码,同时返回OleVariant类型的数据集,这个数据集可以直接赋值给TClientDataSet.DATA。
getDataSet - 执行一条select SQL代码,并返回一个TDataSet类型的实例(其实质是TClientDataSet)。
为了在用户操作中不一直保持数据库连接,在以上方法里需要先连接数据库,执行完成后再断开数据库连接。而为了在同一个业务操作中不必要的频繁连接数据库,以上方法提供一个KeepConnection的参数,让调用者决定调用完成后是否断开数据库连接,也即将管理连接数据库的任务交给调用者。
实现定义如下:
TDBHelper = class(TDataModule)
public
//执行一条不需要返回结果的SQL。
procedure execSql(sql: string);
function getData(sql: string): OleVariant;
//创建一个TClientDataSet实例,但是以TDataSet类型提供给调用者,避免调用者依赖DBClient。
function getDataSet(sql: string): TDataSet;
end;
后续思考,因为不同的DBMS产品,有一些语法不同的地方,为了避免在调用层考虑这些问题,我们可以将TDBHelper作为一个基础类,然后针对不同的DBMS产品提供特殊的实现。例如自增长字段的管理,MSSQL的语法跟MySQL以及Oracle的语法都不一样,那么我们可以在DBHelper里提供一个虚方法getNewId,然后在针对不同DBMS的实现类中重载getNewId这个方法.
比如针对MySql的实现,可以用MySql的LAST_INSERT_ID函数来获取最后的id,TMySqlDBHelper.getNewId可以这样实现:
TMySqlDBHelper.getNewId();
var
dataset: TDataSet;
begin
dataset := self.getDataSet('select LAST_INSERT_ID()');
result := dataset.fields[0].AsInteger;
end;
针对MSSQL的实现,可以是使用MSSQL的Current_identity()函数来获取最新的id,TMSSQLDBHelper.getNewId则可以这样写:
TMSSqlDBHelper.getNewId();
var
dataset: TDataSet;
begin
dataset := self.getDataSet('select current_IDENTITY('''+tableName+''')');
result := dataset.fields[0].AsInteger;
end;
当然上述实现存在大量重复代码,我们可以修改TDBHelper的接口,只需要实现类返回一条提取最新id的SQL就好了。那么修改TDBHelper.getNewId实现,然后引入一个新的function来给实现类返回SQL即可。这样就实现了不同DBMS的兼容。我们在具体的业务模块就不用考虑不同DBMS的问题了。