Design Pattern: criando uma classe DAO genérica

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.

Mestre Jedi Design Pattern: criando uma classe DAO genérica

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

  1. IDE de sua preferência
  2. JARs da biblioteca JPA, implementação Hibernate
  3. Servidor MYSQL 5
  4. JAR do conector MYSQL

Estrutura do artigo

  1. Criação do projeto de exemplo e configuração da biblioteca JPA
  2. Criando a classe de conexão com o banco de dados
  3. Criação das classes de modelo
  4. Criação da classe DAO genérica
  5. 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!");
		
	}

}

Mestre Jedi Design Pattern: criando uma classe DAO genérica

Mestre Jedi Design Pattern: criando uma classe DAO genérica  Mestre Jedi Design Pattern: criando uma classe DAO genérica

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());
		
		
	}

}

Mestre Jedi Design Pattern: criando uma classe DAO genérica

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.");
		
	}

}

Mestre Jedi Design Pattern: criando uma classe DAO genérica

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