Há algum tempo atrás era muito comum encontrar a lógica de persistência de dados junto das regras de negócio. Isto aumentava consideravelmente a complexidade de entendimento e manutenção do projeto. Com o passar do tempo as técnicas de desenvolvimento amadureceram e deram origem a alguns padrões de projeto fortemente utilizados nos dias de hoje, como por exemplo o padrão de projeto MVC (Model – View – Controller). Este design pattern divide a aplicação em camadas com responsabilidades específicas, sendo:
- Model: responsável por abrigar as lógicas de persistência e conversão de dados do SGDB em objetos da aplicação, de forma genérica ou não.
- View: objetivo de abrigar todas as informações visuais contendo entradas de dados, regras de visualização, entre outros.
- Controller: responsável por aplicar as regras de negócio da aplicação.
No contexto acima, é possível comparar o design pattern DAO (Data Access Object) com a camada Model do padrão MVC, uma vez que o mesmo surgiu da necessidade de separar as lógicas de persistência das lógicas de negócio da aplicação. Este padrão tem como objetivo trocar informações de forma independente com o SGBD e fornecer os conceitos básicos para a realização de CRUDs ou recursos mais complexas. Quando aplicado de forma correta, toda a necessidade de busca e cadastro de informações é abstraída, tornando o processo de manipulação de dados das entidades transparente às demais camadas do sistema, ou seja, uma classe Pessoa pode ter um DAO dedicado a realizar operações específicas no SGBD, como por exemplo, consultar pessoas por CPF, idade, etc.
O problema
Levando em consideração que uma classe X pode ter uma representação XDAO responsável por executar as suas lógicas de persistência, imagine se houver 100, 150, 1000 entidades que necessitam de integração com o banco de dados. Seria necessário criar a mesma quantidade de classes DAO para executar as regras de persistência destas entidades?
A solução
Graças ao conceito de Generics presente no Java, é possível criar uma classe DAO que será capaz de abstrair um tipo qualquer de entidade da aplicação e executar comandos específicos, eliminando assim os chamados boilerplates (código clichê). Desta forma, se um número N de entidades do sistema tem características semelhantes, ao invés de se criar N classes DAO, cada uma representando um modelo, a aplicação passaria a ter apenas uma classe genérica abstraindo as funcionalidades em comum entre um grupo específico de objetos que necessitam de comunicação com o banco de dados.
Obs: Para minimizar o esforço de criação das queries de banco de dados e elevar a produtividade deste artigo, será utilizada a biblioteca JPA (Java Persistence API) a qual abstrai os métodos de CRUD de forma simples e legível.
Recursos necessários
- IDE de sua preferência
- JARs da biblioteca JPA, implementação Hibernate
- Servidor MYSQL 5
- JAR do conector MYSQL
Estrutura do artigo
- Criação do projeto de exemplo e configuração da biblioteca JPA
- Criando a classe de conexão com o banco de dados
- Criação das classes de modelo
- Criação da classe DAO genérica
- Testando a classe DAO genérica
1. Criação do projeto de exemplo e configuração da biblioteca JPA
Crie um projeto Java e em seguida crie o diretório META-INF na pasta raiz. Esta pasta será responsável por hospedar o arquivo de configuração da JPA, o persistence.xml.
Antes de criar este arquivo, faça o download dos JARs do Hibernate com JPA e o conector MYSQL. Feito o download, acrescente-os no build path do projeto.
O persistence.xml é responsável por definir os parâmetros de funcionamento da JPA e deve conter informações como: nome da unidade de persistência, string de conexão com o bando de dados, usuário, senha, nome do banco, driver de conexão, entre outros. Como o objetivo do artigo não é esmiuçar este arquivo, segue abaixo o modelo básico para completar este tutorial.
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="dao-generico" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <properties> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/dao" ></property> <property name="javax.persistence.jdbc.user" value="root" /> <property name="javax.persistence.jdbc.password" value="root" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" /> <property name="hibernate.connection.shutdown" value="true" /> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="false" /> <property name="hibernate.format_sql" value="false"/> </properties> </persistence-unit> </persistence>
2. Criando a classe de conexão com o banco de dados
Agora crie a classe que será responsável por estabelecer a conexão com o banco de dados utilizado pelo sistema.
package br.com.raphaelneves.connection; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public class ConnectionFactory { private static EntityManagerFactory factory = Persistence.createEntityManagerFactory("dao-generico");; public static EntityManager getEntityManager(){ return factory.createEntityManager(); } }
Por ser um recurso caro para a aplicação, o atributo factory será utilizado com o modificador non-access static, ou seja, este atributo será único para toda instância produzida da classe ConnectionFactory. Desta forma, a factory será criada apenas na primeira vez em que a classe ConnectionFactory for utilizada.
3. Criação das classes de modelo
As classes modelo indicam a representação de entidades que necessitam de integração com o banco de dados. Para isto, crie uma interface que estipula o método getId(). Este método será responsável por retornar a chave primária das entidades “clientes”, que por sua ver é do tipo Long. A interface EntidadeBase será o contrato das entidades da aplicação que precisam utilizar recursos do SGBD, ou seja, toda e qualquer entidade que trabalhe com persistência de dados deverá “assinar” este contrato.
public interface EntidadeBase{ public Long getId(); }
Sabendo que as entidades modelo terão o contrato de EntidadeBase assinado, é possível amarrar a especificação da classe DAO genérica para que ela aceite todo tipo de entidade que seja implemente esta interface e atenda aos requisito de IS-A EntidadeBase. Crie duas classes, Pessoa e Carro, implementando a interface EntidadeBase e especifique os atributos e comportamenos conforme abaixo:
package br.com.raphaelneves.pojo; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="tb_pessoa") public class Pessoa implements EntidadeBase { private Long id; private String nome; private int idade; @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="id") public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Column(name="nome") public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } @Column(name="idade") public int getIdade() { return idade; } public void setIdade(int idade) { this.idade = idade; } }
package br.com.raphaelneves.pojo; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="tb_carro") public class Carro implements EntidadeBase { private Long id; private String modelo; private int anoFabricacao; @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="id") public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Column(name="modelo") public String getModelo() { return modelo; } public void setModelo(String modelo) { this.modelo = modelo; } @Column(name="ano_fabricacao") public int getAnoFabricacao() { return anoFabricacao; } public void setAnoFabricacao(int anoFabricacao) { this.anoFabricacao = anoFabricacao; } }
As anotações @Entity, @Table, @Column, @Id, @GeneratedValue são recursos fornecidos pela JPA a fim de especificar que uma determinada classe seja identificada como uma classe modelo para manipulação de dados junto ao SGBD. Em palavras mais grosseiras, estas anotações fazem com que a classe represente uma entidade, tabela ou coluna no banco de dados.
4. Criação da classe DAO genérica
Agora que a aplicação é capaz de gerar uma conexão com o banco de dados e mapear as entidades de modelo, crie uma classe com o nome de DaoGenerico. Esta classe será responsável por centralizar as lógicas de persistência em comum entre o grupo de entidades do contrato EntidadeBase. Este artigo abordará os métodos para adicionar, modificar, pesquisar por chave primária e excluir um registro no banco de dados, o famoso CRUD.
public class DaoGenerico<T extends EntidadeBase> {}
Esta declaração de classe permite gerar uma instância de DaoGenerico representando qualquer entidade que assine o contrato EntidadeBase (T extends EntidadeBase). Em seguida, defina um atributo manager, do tipo EntityManager e com modificador non-access static. Este atributo permitirá a utilização dos métodos da JPA.
Para facilitar e simplificar o entendimento, o ciclo de vida do EntityManager não será gerenciado.
public class DaoGenerico<T extends EntidadeBase> { private static EntityManager manager = ConnectionFactory.getEntityManager(); }
Um breve exemplo de como instanciar um objeto de DaoGenerico representando a entidade Pessoa.
DaoGenerico<Pessoa> daoPessoa = new DaoGenerico<Pessoa>();
A JPA possui um método capaz de realizar a busca se baseando pelo tipo de uma entidade modelo e sua chave primária, no caso o atributo ID das classes de modelo. A sintaxe desse método é:
manager.find(Entidade.class, chavePrimaria);
Crie o método de busca, findById, recebendo como parâmetro o tipo da entidade modelo desejada e o ID da mesma. Além disto, será definido como retorno uma entidade do tipo T. Observe que T é a entidade modelo que contratou os serviços de EntidadeBase.
public class DaoGenerico<T extends EntidadeBase> { private static EntityManager manager = ConnectionFactory.getEntityManager(); public T findById(Class<T> clazz, Long id){ return manager.find(clazz, id); } }
Os métodos de cadastrar e editar possuem um comportamento muito parecido, salvar algo no banco de dados. Sendo assim, é possível verificar se o argumento do método possui um ID nulo ou não. Caso a verificação seja verdadeira, a entidade será salva, caso contrário atualizada.
Para isto, o método saveOrUpdate será criado sem retorno, pois o objetivo no momento é apenas inserir/atualizar o registro no banco, e receberá como parâmetro uma entidade do tipo T. Imagine que a entidade modelo tenha várias outras entidades que são persistidas em cascata. Para garantir a integridade dos dados no banco, será aplicado o conceito de transação. Desta forma, se houver algum erro no processo de persistência em cascata, será feito o rollback de todo o processo envolvido neste contexto.
public class DaoGenerico<T extends EntidadeBase> { private static EntityManager manager = ConnectionFactory.getEntityManager(); public T findById(Class<T> clazz, Long id){ return manager.find(clazz, id); } public void saveOrUpdate(T obj){ try{ manager.getTransaction().begin(); if(obj.getId() == null){ manager.persist(obj); }else{ manager.merge(obj); } manager.getTransaction().commit(); }catch(Exception e){ manager.getTransaction().rollback(); } } }
O método de exclusão funcionará um pouco diferente dos demais, pois se faz necessário realizar uma busca da entidade que será removida para somente então removê-la. Infelizmente este é um ciclo implementado pelo próprio método remove() da JPA. O método remove da classe genérica não terá retorno e receberá como parâmetro o tipo da classe modelo e a chave primária da mesma, no caso o ID. Em seguida, o método de busca que já está criado será acionado e retornará a entidade que será excluída.
public class DaoGenerico<T extends EntidadeBase> { private static EntityManager manager = ConnectionFactory.getEntityManager(); public T findById(Class<T> clazz, Long id){ return manager.find(clazz, id); } public void saveOrUpdate(T obj){ try{ manager.getTransaction().begin(); if(obj.getId() == null){ manager.persist(obj); }else{ manager.merge(obj); } manager.getTransaction().commit(); }catch(Exception e){ manager.getTransaction().rollback(); } } public void remove(Class<T> clazz, Long id){ T t = findById(clazz, id); try{ manager.getTransaction().begin(); manager.remove(t); manager.getTransaction().commit(); }catch (Exception e) { manager.getTransaction().rollback(); } } }
5. Testando a classe DAO genérica
Para testar os recursos genéricos de persistência crie as classes InsertApplication, UpdateApplication, FindByIdApplication e RemoveApplication, cada uma representando uma funcionalidade de CRUD na implementação do método main().
public class InsertApplication { public static void main(String[] args) { Pessoa pessoa = new Pessoa(); Carro carro = new Carro(); DaoGenerico<Pessoa> daoPessoa = new DaoGenerico<Pessoa>(); DaoGenerico<Carro> daoCarro = new DaoGenerico<Carro>(); pessoa.setNome("Raphael Neves"); pessoa.setIdade(28); carro.setModelo("Mustang"); carro.setAnoFabricacao(1989); daoPessoa.saveOrUpdate(pessoa); daoCarro.saveOrUpdate(carro); System.out.println("Entidades salvas com sucesso!"); } }
public class FindByIdApplication { public static void main(String[] args) { DaoGenerico<Pessoa> daoPessoa = new DaoGenerico<Pessoa>(); DaoGenerico<Carro> daoCarro = new DaoGenerico<Carro>(); Pessoa pessoa = daoPessoa.findById(Pessoa.class, 3L); Carro carro = daoCarro.findById(Carro.class, 3L); System.out.println("### Entidade Pessoa encontrada ###"); System.out.println("ID: " + pessoa.getId()); System.out.println("NOME: " + pessoa.getNome()); System.out.println(""); System.out.println("### Entidade Carro encontrada ###"); System.out.println("ID: " + carro.getId()); System.out.println("MODELO: " + carro.getModelo()); } }
public class UpdateApplication { public static void main(String[] args) { DaoGenerico<Pessoa> daoPessoa = new DaoGenerico<Pessoa>(); Pessoa pessoa = daoPessoa.findById(Pessoa.class, 3L); pessoa.setNome("Raphael Oliveira Neves"); daoPessoa.saveOrUpdate(pessoa); System.out.println("Entidade atualizada com sucesso."); } }
public class RemoveApplication { public static void main(String[] args) { DaoGenerico<Pessoa> daoPessoa = new DaoGenerico<Pessoa>(); DaoGenerico<Carro> daoCarro = new DaoGenerico<Carro>(); daoPessoa.remove(Pessoa.class, 3L); daoCarro.remove(Carro.class, 3L); System.out.println("Entidades removidas com sucesso!"); } }
Você pode baixar o código fonte deste artigo no meu GitHub.
Até a próxima!
Artigo publicado originalmente em Blog Raphael Neves
O post Design Pattern: criando uma classe DAO genérica apareceu primeiro em Profissionais TI.
Powered by WPeMatico