Criando seu próprio framework de persistência Objeto-Relacional no Delphi XE

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (44)  (0)

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).

 Arquitetura multicamadas

Figura 1: Arquitetura multicamadas (fonte: http://www.devmedia.com.br/introducao-ao-modelo-multicamadas/5541)

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).

Arquitetura multicamadas com camada de persistência de dados

Figura 2: Arquitetura multicamadas com camada de persistência de dados

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).

Listagem 1: Script de criação da tabela TB_CAD_ALUNO

 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:

1º - 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).

Exemplo da árvore do projeto

Figura 3: Exemplo da árvore do projeto

2º - 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.

Listagem 2: TEntity.pas

unit TEntity;

interface

Uses RTTI;

type
  TGenericEntity = class(TObject)
  end;

implementation


end.

3º - 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.

Listagem 3: TblAluno.pas

 
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.

4º - 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.

Listagem 4: CAtribEntity.pas

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.

Listagem 5: tblAluno.pas – alteração usando 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.

Data Module: srvModBaseDados

Figura 4: Data Module: srvModBaseDados

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).

Listagem 6: srvModBaseDados.pas

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).

Listagem 7: GenericDao.pas

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).

Interface demonstrando o uso da camada de persistência

Figura 5: Interface demonstrando o uso da camada de persistência

Veja a implementação dos botões "Inserir" e "GetAll" (Listagem 8).

Listagem 8: Implementação da chamada dos métodos da camada de persistência.

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.

Referências Bibliográficas

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?