Arquivo para Fevereiro, 2008

Classe DAO genérica com Hibernate

DAO (Objeto de Acesso a Dados) é um padrão de projeto muito famoso. O objetivo é separar das regras da aplicação a tecnologia de persistência de dados. O padrão DAO interliga a aplicação ao SGBD (Sistema Gerenciador de Banco de Dados). O objetivo maior ao usar este padrão é promover isolamento e flexibilidade, principalmente, quando se deseja, por exemplo, mudar o SGBD. Neste tipo de aplicação nenhuma regra deve estar no SGDB. Ele passa a ser apenas um repositório de dados, ou seja, um local onde os dados são armazenados e consultados. Triggers, procedures, functions, roles e views são dispensáveis neste contexto. Claro que não podemos dispensar as chaves primárias, índices, integridade referencial e tudo mais que define as entidades do banco e ajudam a melhorar o desempenho durante seu uso.

Em posts anteriores falamos sobre quão desgastante pode ser a construção de um DAO genérico e com possibilidades de uso em qualquer banco de dados. Mostramos que, mesmo utilizando o SQL ANSI na construção do DAO, poderemos não ter muito sucesso. Sugerimos, então, a utilização de um framework de persistência nessa construção: o Hibernate. Ele é gratuito, código aberto, bem documentado e possui uma legião de adeptos que podem ajudar os iniciantes nos fóruns e blogs espalhados pelo mundo.

A intenção deste post é comentar um pouco sobre a construção de uma Classe DAO genérica utilizando o Hibernate. Vamos descrever as características que esta classe deve ter para dar maior flexibilidade ao desenvolvedor durante a construção das regras na camada de negócio. Em outras palavras, que métodos e atributos essa classe deve conter para ser realmente útil. Ao longo dos anos adquirimos alguma experiência nessa área. Longe de mim esgotar o assunto. Mas este pode ser um bom roteiro pra quem está iniciando. Fiquem a vontade pra comentar e sugerir alterações nestes conceitos. Iremos utilizar o C#.NET com NHibernate (a versão .NET do Hibernate do Java) por entender que facilitará bastante a compreensão e, claro, por ser nossa preferência em linguagem de desenvolvimento. ;) É importante que o leitor já tenha alguma experiência com o Hibernate e C#.NET. Este post tratará apenas de conceitos envolvendo a sua utilização.

Vejamos a inteface da nossa classe DAO genérica:


/* T é o tipo da classe que a DAO irá gerenciar e ID é o tipo do campo chave primária. Não utilize chave composta nem chave com dados naturais, ou seja, dados com significado, por exemplo, RG, CPF, etc. O ideal é utilizar um campo numérico auto-incrementável na chave primária.*/
public interface IBaseDAO<T, ID> : IDisposable
{
void BeginTransaction(); //Inicia transação
void CommitTransaction(); //Grava transação
void RollbackTransaction(); //Desfaz transação
T SelecionarPorId(ID id); // Seleciona um registro pelo ID
T Inserir(T obj);
T Alterar(T obj);
void Excluir(T obj);
IList<T> Listar(string ordem); //listar ordenado por campo
ICriteria getCriteria();
ISession getSession();
T Atualiza(T obj);
void Excluir(IList<T> lst);
void Incluir(IList<T> lst);
void Alterar(IList<T> lst);
bool EmUso<U>(string join, string propertyName, ID objectValue);
IList CreateQuery(string hql);
IList<U> CreateQuery<U>(string hql);
T UniqueResult(string hql);
U UniqueResult<U>(string hql);
T SelecionarPor(string propertyName, object value);
}


  • Iniciamos com o controle de transação com o banco de dados: BeginTransaction(), CommitTransaction() e RoolbackTransaction().
  • SelecionarPorId(ID id): Como toda tabela tem chave primária podemos selecionar uma linha da tabela pelo seu ID, ou seja, o identificador único do registro.
  • Temos, após, as três operações básicas de persistência: incluir, alterar e excluir. Note que na inclusão e alteração, há um retorno que é exatamente a linha na qual sofreu persistência. O parâmetro de entrada é a linha a ser persistida e o retorno é a linha após a persistência.
  • Listar(string ordem) – Listar linhas da tabela ordenadas por um campo. O nome do campo é passado ao método no parâmetro ‘ordem’.
  • getCriteria() – Este método é importante pra quem deseja utilizar ICriteria na montagem dos comandos SQL. Essa forma é muito interessante. Utiliza-se métodos de classes para construir comandos SQL. Ganha-se na independencia de banco de dados. Este método deve ser protected.
  • getSession() – Este método nos dá acesso a sessão hibernate que estamos usando no momento. A sessão ou ISession é a conexão estabelecida com o banco de dados. Todos os comandos ao banco de dados são feitos atravéz da sessão, ou seja, por essa conexão (inserir, alterar, listar, transações, excluir, etc.). Poderiamos simplesmente explicitar um método com esta sessão e permitir a manipulação da sessão na camada de negócio. Não é uma boa prática. A idéia é ter uma classe DAO com métodos para manipulação de dados que já utilizam a ISession. Não devemos acessar o ISession diretamente na camada de negócio. Os objetos de acesso a dados devem conter métodos que possam ser utilizados na camada de negócio e esta camada não precisa conhecer ou acessar detalhes dessa implementação. Este método deve ser protected.
  • Atualiza() – Este método funciona como um refresh. Todas as alterações feitas no objeto persistente (objeto criado pelo hibernate após leitura da linha do banco de dados) pode ser desfeita executando este método. O retorno é o objeto persistente original.
  • Incluir, Alterar e Excluir listas de objetos – Esses métodos recebem uma lista de objetos e realizam as operações respectivas em todos os objetos da lista. Um exemplo da utilização é exclusão de email. Marcamos os que queremos excluir e ao confirmar, a lista é excluída de uma vez só e não um a um.
  • EmUso – Este método é útil para verificar se um objeto está na composição de outro. Por exemplo, podemos utilizar este método pra saber se a exclusão de um objeto que é uma linha no banco fere a integridade referencial: verificar se um objeto está em uso em outro. Isso é feito através do inner join e checando se um select lista algum registro no banco. Se listar, o registro não poderá ser excluído.
  • CreateQuery – Ambos recebem como parâmetro uma instrução em HQL (Hibernate Query Language). A diferença está no retorno. Claro que a instrução HQL de entrada tem que ser compatível com o retorno. O primeiro recebe uma instrução HQL como parâmetro e retornar uma lista de objetos na forma de System.Array e nessa array, colunas podem ser de qualquer tipo de dados. Quem determina essa saida são os campos enumerados no select do HQL de entrada. Já o segundo CreateQuery recebe um HQL de entrada mas tem quer ter uma saída do tipo <U> definido na assinatura do método.
  • UniqueResult – Ambos recebem como parâmetro uma instrução HQL. O retorno não é uma lista e sim um resultado em uma única linha ou dado, podendo ser classe ou tipo primitivo. A instrução HQL deve ser construída de acordo com este retorno. O primeiro método retorna um objeto igual ao tipo de dados <T> definido na instância da classe. O segundo permite uma adaptação, mas deve retornar um dado de acordo com o tipo <U> defindo na assinatura do método.
  • SelecionarPor() – Este método é bem simples. Recebe como parâmetros o nome de uma propriedade e o valor e retorna o objeto correspondente. Muito útil em relacionamentos um-para-um. Pode-se selecionar um objeto por outra propriedade que não o ID.

Pra quem deseja começar a construir uma classe DAO genérica deve, pelo menos, implementar esses métodos. Deixamos a cargo do leitor criar uma classe que implemente os métodos da interface que comentamos. Na interface que utilizamos em nossos trabalhos existem outros métodos de listagens e manipulação de objetos persistentes (citado acima) e transientes (instâncias criadas pelo programador e que não foram persistidas no banco; muito usadas na transferência de dados entre camadas – DTO). Esses métodos foram construídos após notar que certas operações são recorrentes. Se alguém quiser ver as minhas interfaces e implementações, pode pedir que eu mando.

NA PRÁTICA

Todas as tabelas do banco deverão ter uma classe DAO definida como na classe CidadeDAO abaixo. Note que a nova classe herda as características na nossa BaseDAO<T,ID>. Além da herança, existe uma especialização nessa nova classe. O método SelecionarPorNome() foi criado utilizando recursos da super-classe: getCriteria().


public class CidadeDAO : BaseDAO<Cidade,long>
{
public Cidade SelecionarPorNome(Uf uf, string nomeCidade)
{
ICriteria crit = this.getCriteria()
.Add(Expression.Eq(“Descricao”,nomeCidade))
.Add(Expression.Eq(“IdUf”,uf));
return crit.UniqueResult();
}
}


Novos métodos podem ser criados nas classes-filhas de acordo com as novas necessidade. O mais importante é que a classe BaseDAO, que deve ser uma classe abstrata, possui muitos métodos já definidos que podemos usar livremente, reaproveitando código. Na camada de negócio poderemos instanciar esses DAOs, onde métodos da super-classe estão disponíveis jutamente com os novos métodos criado como no exemplo acima.Ter uma BaseDAO bem definida pode ser a diferença entre uma aplicação bem ou mal projetada. Afinal a comunicação com o banco é feita por esta classe já que todas as outras estarão herdando suas características.

Esperamos ter ajudado na construção da sua BaseDAO. Estamos aberto a dúvidas e comentários. Convidamos, também, os interessados a adicionar implementações para os métodos acima e sugerir métodos interessantes e úteis para enriquecer nosso exemplo de DAO genérico.

Clique aqui e veja continuação deste post.

Independência de Banco de Dados

Um requisito não-funcional importante na engenharia de software é a escolha do repositório de dados da aplicação. A princípio, isso não constitui um grande problema. Afinal, durante a construção do sistema, é sulficiente se adaptar a infra-extrutura já existente no estabelecimento que irá usar o sistema. Se não houver um parque de informática definido, é comum escolher um banco de dados gratuito que normalmente atende a maioria das situações ou, mesmo, comprar licenças de uso de algum banco de dados comercial.

O problema começa na manutenção do sistema. A empresa decide, por exemplo, mudar o banco de dados padrão. Isso pode acontecer por vários motivos: contenção de custos, limitações do banco de dados atual, mudança da plataforma, etc. Aí pode ser um transtorno para os desenvolvedores: migrar os dados e modificar o sistema para se adaptar à nova situação. Sem falar nas mudanças e reinstalações que poderão ocorrer nas máquinas dos usuários. E se forem muitos usuários?! E se for pra ontem?! E se o pessoal pra fazer essas mudanças for limitado?! Veja que o desgaste gerado por esta “mudança” pode crescer de maneira exponencial. Eu já passei por isso. Posso garantir que os problemas envolvidos podem exigir várias noites de pesquisas e aprendizado instantâneo para poder continuar merecendo a confiança dos seus superiores. Fui obrigado a mudar duas vezes de plataforma de desenvolvimento em menos de dois anos. Hoje cheguei a conclusão que só existem duas plataformas que se adaptam bem a quase todos os cenários no mundo da micro-informática: .NET ou Java.

Pensando nisso decidi postar algumas considerações visando atingir o tão sonhado grau de indepêndencia de banco de dados.

Vamos analisar a seguinte situação:

Acesso direto a base

Note o seguite:

  1. A aplicação desktop faz acesso direto a base de dados. Todas a regras de negócio e persistência de dados são feitas diretamente pela aplicação. Essa arquitetura é muito utilizada pelos programadores Delphi, VB e similares.
  2. Numa aplicação que utiliza servidor WWW, o browser faz requisições ao servidor que por sua vez acessa a base diretamente, da mesma forma que uma aplicação desktop. Os sites no servidor web contém regras de negócio e são responsáveis pelo acesso aos dados. Isso é comum para programadores ASP, JSP, PHP, ColdFusion e similares.

Uma maneira de atingir a indepêndencia de banco neste contexto é utilizar somente instruções SQL ANSI para persistir dados. Mesmo assim isso possui alguns incovenientes. Numa aplicação desktop que está instalada em várias máquinas, qualquer mudança na regra de negócio implicará em reinstalação em todas as máquinas que fazem uso dessa aplicação. Se forem muitos usuários? Se os usuários não sabem instalar sistema? Certamente o help desk vai ter trabalho dobrado pra implantar a nova versão. Utilizando servidor WWW isso ameniza e muito o problema. Basta atualizar o servidor e tudo bem. Todos os usuários terão a nova versão no seu browser. Mas a depender de como essas páginas foram criadas os programadores terão que atualizar a regra ou as configurações de banco em cada página do site. Sem falar que nem sempre é possível desenvolver com instruções SQL ANSI apenas. Uma função de banco, uma VIEW, procedure ou trigger precisa ser reescrita no momento que o banco for trocado.

Quando eu utilizava o Delphi ou o PHP optei pela seguinte solução: Minhas aplicações serviam apenas como GUI. As regras de negócio e a persistência de dados era responsabilidade das procedures de banco. Usava também usuários de banco, functions, roles e views. Triggers não utilizava por serem um tipo de procedures.

Fig02 Acesso a base por procedure

Quando tinha que mudar alguma regra, apenas fazia nas procedures. Mudanças na GUI eram mínimas. Assim não precisava reinstalar as aplicações Delphi toda vez que mudasse alguma regra da aplicação. No PHP, da mesma forma. Só mudava a página se houvesse extrema necessidade. Era ótimo a princípio. Mas… E a independência de banco??? Assim a situação só piora o problema. Ganha de um jeito, na manutenção da regra e help desk, mas perde na independencia de banco, ou seja, teria que reescrever todas procedures caso mudasse o banco. Conclusão: abandonei completamente o uso das procedures, views, roles, functions, usuários de banco e tudo mais que dependa diretamente do banco de dados. Minhas aplicações tiveram que ser reescritas completamente em outra arquitetura.

Uma solução padrão para o problema da independência de banco é utilizar um padrão de projeto chamado DAO. Isso mesmo, PADRÃO DE PROJETO. Padrão DAO faz parte de um conjunto de padrões que resolvem problemas recorrentes. Mas isso é outra história…

Acesso a base por um DAO

Cria-se um meio, que normalmente é uma classe da orientação a objetos, por onde a aplicação fará todas as chamadas ao banco, de forma que mudar o banco significaria apenas mudar alguns atributos dessa classe. Ou seja, a classe DAO iria se moldando de acordo com algumas informações passadas no seu construtor. É possivel que alguns métodos precisem ser adaptados por causa do SQL. Mas utilizar SQL ANSI diminue bastante esta necessidade. Eu comecei a fazer isso também. O problema maior é que construir uma classe dessas não pode ser um trabalho solitário. A programação da classe cresce muito. Afinal, para que fique boa, todas as possibilidades do SQL tem que estarem previstas: inserts, updates, deletes e selects e para todos os bancos de dados envolvidos. Se não, pode-se construir uma classe DAO com menos recursos e ir aprimorando a medida que necessidades surjam.

Uma solução que eu considero muito boa é construir uma classe DAO que faça uso de algum framework de persistência.

Fig04 Acesso a base por um DAO e persistência

A responsabilidade de persistir os dados fica para o framework que normalmente já vem preparado para utilizar vários bancos de dados, enquanto a classe DAO da aplicação apenas herda essas características. Eu aconselho a utilização de um framework muito bom, tanto para o Java quanto para .NET: o Hibernate.

O Hibernate consegue ser independente de banco de dados devido a algumas características principais:

  1. através de mapeamento de entidades do SGBD como classes da linguagem e via arquivos XML;
  2. ampliando a linguagem SQL para HQL (Hibernate Query Language). Esta linguagem manipula objetos e coleções que, depois, o próprio framework converte para SQL que o SGBD entende. Essa conversão é feita utilizando os arquivos XML de mapeamento, configurações de dialeto do banco e drivers de comunicação. Também pode-se utilizar outras formas de montagem do SQL, por exemplo, utilizando métodos de classes. Esses métodos recebem parâmetros que montam o SQL também utilizando os arquivos XML de mapeamento, configurações de dialeto do banco e drivers de comunicação;

Parece complicado mas não é. Inclusive tudo isso é muito produtivo. Desenvolver aplicações sobre essa ótica aumenta bastante a produtividade. Linhas de cada tabela do banco tornam-se objetos e as colunas, propriedades desses objetos. O resultado dos selects do SQL é visto como coleções de objetos e os DML’s são vistos como metódos das classes de regra de negócio. É responsabilidade do framework converter tudo isso para o SQL que o banco entenda, e essa conversão deve ser de acordo com o dialeto do banco escolhido e definido em algum arquivo de configuração.

Dessa forma mudar de SGBD significa migrar os dados de um banco pra outro, mudar a configuração de dialeto e alterar, se for o caso, os arquivos XML de mapeamento das entidades. Posso garantir que poucas modificações são necessárias para migrar um sistema do SQL Server para Oracle, se voce utilizar um framework de persistência como Hibernate durante o desenvolvimento. Se a migração for, por exemplo, do Firebird para Oracle ou vice-versa as modificações se resumem a uma ou duas linhas no arquivo de configuração, além, claro, da migração dos dados.

Bom, é isso. Espero que este pequeno artigo seja útil e sirva como roteiro pra quem deseja programar com independência de banco.


Calendário

Fevereiro 2008
D S T Q Q S S
« Jan   Mar »
 12
3456789
10111213141516
17181920212223
242526272829  

Desde (04/11/07)

  • 43,583 visitas