Criando seu próprio framework de persistência Objeto-Relacional no Delphi XE
Veja neste artigo como criar seu próprio framework de persistência Objeto-Relacional no Delphi XE.
Veja neste artigo como criar um framework de persistência em Delphi.
Para o desenvolvimento de softwares, atualmente, a arquitetura mais disseminada é a multicamadas. Geralmente divida em três camadas: banco de dados, camada de negócios e interface (apresentação) (Figura 1).
O fator complicador nesse modelo de arquitetura está no acesso aos dados que é realizada pela camada de negócios através do acesso nativo a camada de dados. Para minimizar esta situação, nos deparamos com a necessidade de intercalar nessas camadas uma camada de persistência de dados (Figura 2).
As operações básicas da camada de persistência são conhecidas como operações de CRUD (Create, Retrieve (ou Read), Update e Delete). Assim sendo, a camada de persistência deve realizar estas operações de forma transparente a aplicação e conseqüentemente livrar a camada de negócios das preocupações relativas ao acesso a dados.
Portanto, o objetivo deste artigo é justamente mostrar como os desenvolvedores que trabalham com arquitetura multicamadas, podem criar o seu próprio framework de persistência objeto-relacional em Delphi XE, levando suas aplicações a um nível de abstração de dados necessário a produtividade do desenvolvimento.
Criando as Classes de Entidades
Quando se pensa em camada de persistência primeiramente se pensa em criar as classes de entidades. E quem seriam estas classes? Seriam exatamente espelhos das entidades, ou tabelas, do esquema da base de dados usado na aplicação. Para ilustrar a criação do framework de persistência objeto-relacional, primeiramente será necessário criar uma tabela no Firebird (Listagem 1).
CREATE TABLE TB_CAD_ALUNO (
MATRICULA VARCHAR(10) NOT NULL,
NOME_ALUNO VARCHAR(100) NOT NULL,
ENDERECO VARCHAR(200),
TELEFONE VARCHAR(20),
CPF VARCHAR(11)
);
ALTER TABLE TB_CAD_ALUNO ADD CONSTRAINT TB_CAD_ALUNO_PK PRIMARY KEY (MATRICULA);
Depois de criada a tabela, cria-se um novo projeto no Delphi XE para começar a implementar os artefatos necessários a camada de persistência. Assim sendo:
Crie um novo projeto no Delphi XE do tipo VCL Forms Applications. Salve o projeto. Crie junto com os fontes deste projeto uma pasta chamada Framework. Esta será a pasta onde os fontes necessários ao Framework de persistência serão arquivados, objeto de estudo deste artigo (Figura 3).
Usa-se o conceito de RTTI (Run-time Type Information) [2] para criar as classes de entidades. Assim sendo, adicione uma nova Unit ao seu projeto e dê a ela o nome de TEntity.pas, salve-a dentro da pasta Framework e implemente-a de acordo com a Listagem 2. Esta classe será a classe genérica para qualquer classe de entidade presente em qualquer esquema de banco de dados.
unit TEntity;
interface
Uses RTTI;
type
TGenericEntity = class(TObject)
end;
implementation
end.
Agora crie a classe de entidade referente à tabela TB_CAD_ALUNO (Listagem 1). Para tal crie uma nova Unit e salve-a dentro da pasta Framework como tblAluno.pas e implemente-a de acordo com a Listagem 3.
unit tblAluno;
interface
Uses TEntity;
type
//nome da classe de entidade
TAluno = class(TGenericEntity)
private
//colocar aqui todas as propriedades privadas da classe de acordo com
//os campos da tabela
FMatricula:string;
FNomeAluno:string;
FEndereco:string;
FTelefone:string;
FCPF:string;
//colocar aqui todos os métodos sets para cada uma das propriedades acima
procedure setFMatricula(value:string);
procedure setFNomeAluno(value:string);
procedure setFEndereco(value:string);
procedure setFTelefone(value:string);
procedure setFCPF(value:string);
public
//colocar aqui todas as propriedades públicas de acesso aos objetos dessa
//classe
property Matricula: string read FMatricula write setFMatricula;
property Nome:string read FNomeAluno write setFNomeAluno;
property Endereco:string read FEndereco write setFEndereco;
property Telefone:string read FTelefone write setFTelefone;
property CPF:string read FCPF write setFCPF;
function ToString:string; override;
end;
implementation
procedure TAluno.setFMatricula(value:string);
begin
FMatricula:= value;
end;
procedure TAluno.setFNomeAluno(value:string);
begin
FNomeAluno:= value;
end;
procedure TAluno.setFEndereco(value:string);
begin
FEndereco:= value;
end;
procedure TAluno.setFTelefone(value:string);
begin
FTelefone:= value;
end;
procedure TAluno.setFCPF(value:string);
begin
FCPF:= value;
end;
function TAluno.toString;
begin
result := '''' Matricula: ''''+ Matricula + '''' Nome: ''''+ Nome +
'''' Endereco: '''' + Endereco +
'''' Fone: '''' + Telefone + '''' CPF: '''' + CPF;
end;
end.
Agora é necessário customizar a classe TAluno usando conceitos de Custom Attributes [3]. O que será realizado é simplesmente colocar Custom Attributes para cada elemento da classe TAluno de forma que seja possível tirar proveito deles através da RTTI e criar o DAO (Data Access Object).
Primeiro crie os Custom Attributes necessários para identificar dentro da classe de entidade a tabela do banco de dados e seus campos. Crie, portanto, uma nova Unit, chamada de CAtribEntity e salve-a dentro de Framework, implementando-a conforme a Listagem 4.
unit CAtribEntity;
interface
//atributo para determinar o nome da tabela na entidade a ser usada
type //nome da tabela
TableName = class(TCustomAttribute)
private
FName: String;
public
constructor Create(aName: String);
property Name: String read FName write FName;
end;
type //determinar se o campo é um campo chave
KeyField = class(TCustomAttribute)
private
FName: String;
public
constructor Create(aName: String);
property Name: String read FName write FName;
end;
type //nome do campo na tabela
FieldName = class(TCustomAttribute)
private
FName: String;
public
constructor Create(aName: String);
property Name: String read FName write FName;
end;
implementation
constructor TableName.Create(aName: String);
begin
FName := aName
end;
constructor KeyField.Create(aName: String);
begin
FName := aName;
end;
constructor FieldName.Create(aName: String);
begin
FName := aName;
end;
end.
Depois customize a classe de entidade TAluno com os Custom Attributes necessários, alterando a implementação da classe conforme a Listagem 5. Dessa forma, tem-se neste exato momento o mapeamento objeto-relacional usando para isso os Custom Attributes.
unit tblAluno;
interface
Uses TEntity, CAtribEntity;
type
//nome da classe de entidade
[TableName(''''TB_CAD_ALUNO'''')]
TAluno = class(TGenericEntity)
private
//colocar aqui todas as propriedades privadas da classe de acordo com
//os campos da tabela
FMatricula:string;
FNomeAluno:string;
FEndereco:string;
FTelefone:string;
FCPF:string;
//colocar aqui todos os métodos sets para cada uma das propriedades acima
procedure setFMatricula(value:string);
procedure setFNomeAluno(value:string);
procedure setFEndereco(value:string);
procedure setFTelefone(value:string);
procedure setFCPF(value:string);
public
//colocar aqui todas as propriedades públicas de acesso aos objetos dessa
//classe
[KeyField(''''MATRICULA'''')]
[FieldName(''''MATRICULA'''')]
property Matricula: string read FMatricula write setFMatricula;
[FieldName(''''NOME_ALUNO'''')]
property Nome:string read FNomeAluno write setFNomeAluno;
[FieldName(''''ENDERECO'''')]
property Endereco:string read FEndereco write setFEndereco;
[FieldName(''''TELEFONE'''')]
property Telefone:string read FTelefone write setFTelefone;
[FieldName(''''CPF'''')]
property CPF:string read FCPF write setFCPF;
function ToString:string; override;
end;
implementation
procedure TAluno.setFMatricula(value:string);
begin
FMatricula:= value;
end;
procedure TAluno.setFNomeAluno(value:string);
begin
FNomeAluno:= value;
end;
procedure TAluno.setFEndereco(value:string);
begin
FEndereco:= value;
end;
procedure TAluno.setFTelefone(value:string);
begin
FTelefone:= value;
end;
procedure TAluno.setFCPF(value:string);
begin
FCPF:= value;
end;
function TAluno.toString;
begin
result := '''' Matricula: ''''+ Matricula + '''' Nome: ''''+ Nome
+ '''' Endereco: '''' + Endereco +
'''' Fone: '''' + Telefone + '''' CPF: '''' + CPF;
end;
end.
Criando o DAO
DAO (Data Access Object) é um padrão de desenvolvimento de softwares que trata da persistência de objetos separando a camada de dados da camada de negócios [4]. Para entender melhor a implementação do DAO é necessário conhecer os conceitos de Generics [4, 5]. Este recurso nos permite trabalhar com uma estrutura genérica, o que faz muita diferença em linguagens fortemente tipada como o Delphi, podendo definir-se uma estrutura genérica customizada, ou melhor, tipada de acordo com a necessidade.
Primeiro crie um DataModule com o nome de srvModBaseDados e salve também dentro da pasta Framework. Este DataModule será responsável por fazer a interface entre a camada de dados e o DAO da camada de persistência, ou seja, neste ponto já se cria a primeira independência de projeto em relação ao SGBD utilizado. Coloque TDSServerModuleBaseDados na propriedade name do DataModule. Veja na Figura 4 os componentes visuais necessários ao DataModule.
Configure o componente TSQLConnection para acesso ao banco de dados criado neste artigo. E depois configure o Componente TSQLDataSet para o respectivo TSQLConnection do DataModule. Depois crie os métodos necessários à conexão com o banco e os métodos necessários ao CRUD (Listagem 6).
unit srvModBaseDados;
interface
uses
SysUtils, Classes, DBXFirebird, DBXPool, FMTBcd, DB, SqlExpr;
type
TDSServerModuleBaseDados = class(TDataModule)
LSCONEXAO: TSQLConnection;
SQLDSServidor: TSQLDataSet;
private
{ Private declarations }
public
{ Public declarations }
//funções para o banco de dados
function Conectar:boolean;
function Desconectar:boolean;
//funções para manipular as entidades
function getDataSet(strQry:string): TDataSet;
function execSql(strQry:string): boolean;
end;
var
DSServerModuleBaseDados: TDSServerModuleBaseDados;
implementation
{$R *.dfm}
//funções para o banco de dados
function TDSServerModuleBaseDados.Conectar:boolean;
begin
try
LSCONEXAO.Connected := true;
result := true;
except
result := false;
end;
end;
function TDSServerModuleBaseDados.Desconectar:boolean;
begin
try
LSCONEXAO.Connected := false;
result := true;
except
result := false;
end;
end;
//funções para manipular as entidades
function TDSServerModuleBaseDados.getDataSet(strQry:string): TDataSet;
begin
SQLDSServidor.Close;
SQLDSServidor.Params.Clear;
SQLDSServidor.CommandType := ctQuery;
SQLDSServidor.CommandText := strQry;
SQLDSServidor.Open;
result := SQLDSServidor;
end;
function TDSServerModuleBaseDados.execSql(strQry:string): boolean;
Var
msgErro:string;
begin
result := false;
SQLDSServidor.Close;
SQLDSServidor.Params.Clear;
SQLDSServidor.CommandType := ctQuery;
SQLDSServidor.CommandText := strQry;
try
SQLDSServidor.ExecSQL;
result := true;
except
on e: Exception do begin
raise Exception.Create(e.Message)
end;
end;
end;
end.
Assim sendo, crie outra Unit ao seu projeto, salvando também dentro de Framework com o nome de GenericDao. A Unit GenericDao consolida todos os conceitos de Generics, Rtti, DAO para que seja possível manipular determinada classe de entidade fazendo o mapeamento objeto-relacional (Listagem 7).
unit GenericDao;
interface
Uses Db, Rtti, CAtribEntity, TypInfo, SysUtils, srvModBaseDados;
type
TGenericDAO = class
private
class function GetTableName<T: class>(Obj: T): String;
public
//procedimentos para o crud
class function Insert<T: class>(Obj: T):boolean;
class function GetAll<T: class>(Obj: T): TDataSet;
end;
implementation
class function TGenericDAO.GetTableName<T>(Obj: T): String;
var
Contexto: TRttiContext;
TypObj: TRttiType;
Atributo: TCustomAttribute;
strTable: String;
begin
Contexto := TRttiContext.Create;
TypObj := Contexto.GetType(TObject(Obj).ClassInfo);
for Atributo in TypObj.GetAttributes do
begin
if Atributo is TableName then
Exit(TableName(Atributo).Name);
end;
end;
//funções para manipular as entidades
class function TGenericDAO.Insert<T>(Obj: T):boolean;
var
Contexto: TRttiContext;
TypObj: TRttiType;
Prop: TRttiProperty;
strInsert, strFields, strValues: String;
Atributo: TCustomAttribute;
begin
strInsert := '''''''';
strFields := '''''''';
strValues := '''''''';
strInsert := ''''INSERT INTO '''' + GetTableName(Obj);
Contexto := TRttiContext.Create;
TypObj := Contexto.GetType(TObject(Obj).ClassInfo);
for Prop in TypObj.GetProperties do begin
for Atributo in Prop.GetAttributes do begin
if Atributo is FieldName then begin
strFields := strFields + FieldName(Atributo).Name
+ '''','''';
case Prop.GetValue(TObject(Obj)).Kind of
tkWChar, tkLString, tkWString, tkString,
tkChar, tkUString:
strValues := strValues +
QuotedStr(Prop.GetValue(TObject(Obj)).AsString)
+ '''','''';
tkInteger, tkInt64:
strValues := strValues +
IntToStr(Prop.GetValue(TObject(Obj)).AsInteger)
+ '''','''';
tkFloat:
strValues := strValues +
FloatToStr(Prop.GetValue(TObject(Obj)).AsExtended)
+ '''','''';
else
raise Exception.Create(''''Type not Supported'''');
end;
end;
end;
end;
strFields := Copy(strFields, 1, Length(strFields) - 1);
strValues := Copy(strValues, 1, Length(strValues) - 1);
strInsert := strInsert + '''' ( '''' + strFields + '''' )
VALUES ( '''' + strValues + '''' )'''';
result := DSServerModuleBaseDados.execSql(strInsert);
end;
class function TGenericDAO.GetAll<T>(Obj: T): TDataSet;
begin
result := DSServerModuleBaseDados.getDataSet(''''SELECT T1.* '''' +
'''' FROM '''' + GetTableName(Obj) + '''' T1 '''' );
end;
end.
Portanto, até este ponto o seu Framework de persistência está criado. No próximo tópico vamos integrá-lo a aplicação Delphi criada no inicio deste artigo. A pasta Framework poderá ser copiada para qualquer aplicativo e usada sem necessidades de reescrita de código, ou poderá ficar em área pública a todos os seus projetos.
Usando a Camada de Persistência
Agora que a camada de persistência está criada cria-se o aplicativo para usá-la. O projeto do aplicativo foi criado no início deste artigo, portanto, será construída uma interface simples apenas para mostrar o uso do framework (Figura 5).
Veja a implementação dos botões "Inserir" e "GetAll" (Listagem 8).
procedure TForm5.btnInserirClick(Sender: TObject);
Var
aluno:TAluno;
begin
aluno := TAluno.Create();
aluno.Matricula := edMatricula.Text;
aluno.Nome := edNome.Text;
aluno.Endereco := edEndereco.Text;
aluno.Telefone := edTelefone.Text;
aluno.CPF := edCPF.Text;
if TGenericDAO.Insert(aluno) then begin
ShowMessage(''''Aluno inserido!'''');
end else begin
ShowMessage(''''Aluno não inserido!'''')
end;
end;
procedure TForm5.btnGetAllClick(Sender: TObject);
begin
ClientDataSet1.Close;
DataSetProvider1.DataSet := TGenericDAO.GetAll(TAluno.Create());
ClientDataSet1.Open;
end;
Conclusão
Conclui-se portanto que os desenvolvedores devem estar atento as novas implementações e recursos disponíveis em seus ambientes de desenvolvimento. Isso torna o trabalho de produção de softwares não tão árduo e é notória a necessidade cada vez mais inerente desta área em promover as boas práticas no desenvolvimento de softwares.
- Granaty, Jones. Introdução ao Modelo Multicamadas. Acesso em 01 jul. 2013.
- Leonhardt, Rodrigo. RTTI (Run-time Type Information). Acesso em 01 jul. 2013.
- Guedes, José Mário Silva. Trabalhando com Atributos (Custom Attributes). Acesso em 01 jul. 2013.
- Mourão, Rodrigo Carreiro. DataSnap XE, Generics, RTTI e DAO. Acesso em 01 jul. 2013.
- Embarcadero, docwik. Overview of Generics. Acesso em 01 jul. 2013.
Artigos relacionados
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo