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.

mvc_crudb11

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

}

Screenshot at 11-36-28

Screenshot at 11-37-31 Screenshot at 11-37-21

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

}

Screenshot at 11-45-09

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

}

Screenshot at 11-46-57

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

Raphael Oliveira Neves

Mais artigos deste autor »

Engenheiro de software com 6+ anos de experiência em projetos de arquitetura e desenvolvimento de sistemas em Java, certificado Oracle, aspirante à escritor nas horas vagas e entusiasta de novas tecnologias.


4 Comentários

W.k almeida
3

Raphael, muito bom o tutorial. Agora ajudaria muito se utilizasse este mesmo exemplo com uma interface gráfica javax ou swing. Muitos estudantes têm dificuldade na busca dos dados por um parâmetro diferente do id, por exemplo, nome, modelo, etc. Além disso fica como sugestão buscar os dados e apresentar em uma tabela (jTable).
obs. (li outro artigo seu sobre JPA e Hibernate ta muito legal)

Todd
4

Desculpa, não sei o que aconteceu com meu código, mas quando vou persistir objetos relacionados apresenta o erro: java.lang.IllegalStateException: Transaction not active.
Quando é apenas um objeto sem relação, a API persiste.
Se puder ajudar agradeço.

Deixe seu comentário

Seu endereço de e-mail não será publicado. Campos com * são obrigatórios!