terça-feira, 24 de setembro de 2013

Como criar uma camada DAO

Este post faz parte de uma série sobre Spring, JPA e JSF. Caso algum assunto abordado aqui não esteja claro, consulte este link: Spring + JPA.

Criar uma camada DAO (Data Access Objects) que possa ser reutilizada em outros projetos não é uma tarefa muito difícil, embora envolva vários conceitos de OOP (Object Oriented Programming) e Design Patterns.

Este exemplo mostrará uma construção que vai disponibilizar ao desenvolvedor um ambiente de CRUD (Create, Retrieve, Update, Delete) com algumas características interessantes:

  1. decisão automática por inserção ou atualização, baseada no valor da chave primária;
  2. recuperação por consulta total, personalizada ou nativa do banco de dados;
  3. exclusão direta ou por meio do sinalizador da classe Entidade (flagRemover);
  4. processamento de uma única entidade ou de uma lista de entidades, pertencentes a qualquer subclasse da classe Entidade.
A última característica é a mais interessante, pois possibilita enviar várias entidades diferentes de uma só vez, garantindo o processamento dentro da mesma transação, o que garante um rollback total em caso de erro.

Dentro de um pacote chamado de dao, vamos criar uma interface chamada InterfaceDAO. É muito importante a programação voltada a interfaces (ou classes abstratas, quando for interessante), pois traz ao projeto uma versatilidade muito grande, permitindo que sejam agregados outros mecanismos, sem interferir naquilo que já foi desenvolvido.

Segue o código da classe InterfaceDAO:

package dao;

import java.io.Serializable;
import java.util.List;

public interface InterfaceDAO<T> {

    public void salvar(T entidade);

    public void salvarLista(List<T> lista);

    public T carregar(Class<T> classe, Serializable chavePrimaria);

    public List<T> listar(Class<T> classe);

    public List<T> consultaPersonalizada(String consulta);

    public List<T> consultaNativa(String consulta);

}

Essa interface orienta o desenvolvedor a respeito dos métodos que serão necessários ao projeto e que podem ser utilizados em outras camadas, já que são métodos públicos. Não há método de atualização e exclusão na interface, pois a atualização depende apenas do valor da chave primária, e a exclusão, como foi visto anteriormente, será feita por meio da sinalização do atributo flagRemover. É importante observar que a interface não fixa o tipo das classes que serão processadas. Em vez disso, usa o tipo genérico, representado por T.

A seguir, a implementação da classe  que o projeto utilizará para as operações CRUD:

package dao;

import entidades.Entidade;
import java.io.Serializable;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.orm.jpa.support.SharedEntityManagerBean;
import org.springframework.stereotype.Repository;

@Repository("genericDAO")
@Scope(value = "singleton")
public class GenericDAO<T extends Entidade> implements InterfaceDAO<T> {

    @Autowired
    private SharedEntityManagerBean jpaTemplate;

    @Override
    public void salvar(T entidade) {
        if (entidade != null) {
            salvarSemFlush(entidade);
            jpaTemplate.getObject().flush();
        }
    }

    private void salvarSemFlush(T entidade) {
        if (entidade != null) {
            try {
                if (entidade.getFlagRemover()) {
                    if (entidade.getId() != null) {
                        if (jpaTemplate.getObject().find(entidade.getClass(), entidade.getId()) != null) {
                            jpaTemplate.getObject().remove(jpaTemplate.getObject().getReference(entidade.getClass(), entidade.getId()));
                        }
                    }
                } else {
                    if (entidade.getId() == null) {
                        jpaTemplate.getObject().persist(entidade);
                    } else {
                        if (jpaTemplate.getObject().find(entidade.getClass(), entidade.getId()) != null) {
                            jpaTemplate.getObject().merge(entidade);
                        }
                    }
                }
            } catch (Exception e) {
                System.out.println(e.getLocalizedMessage());;
            }
        }
    }

    @Override
    public void salvarLista(List<T> lista) {
        Integer i = 0;
        if (lista != null) {
            for (T entidade : lista) {
                salvarSemFlush(entidade);
                i++;
                // sincroniza a cada 500 registros
                if ((i % 500) == 0) {
                    try {
                        jpaTemplate.getObject().flush();
                    } catch (Exception e) {
                        System.out.println(e.getLocalizedMessage());;
                    }
                }
            }
            // sincroniza os ultimos registros
            if (i % 500 != 0) {
                try {
                    jpaTemplate.getObject().flush();
                } catch (Exception e) {
                    System.out.println(e.getLocalizedMessage());;
                }
            }
        }
    }

    @Override
    public T carregar(Class<T> classe, Serializable chave) {
        if (classe == null || chave == null) {
            return null;
        }
        return jpaTemplate.getObject().find(classe, chave);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<T> listar(Class<T> classe) {
        if (classe == null) {
            return null;
        }
        return jpaTemplate.getObject().createQuery("select a from " + classe.getSimpleName() + " a ").getResultList();
    }

    @Override
    public List<T> consultaPersonalizada(String consulta) {
        if (consulta == null) {
            return null;
        }
        return jpaTemplate.getObject().createQuery(consulta).getResultList();
    }

    @Override
    public List<T> consultaNativa(String consulta) {
        if (consulta == null) {
            return null;
        }
        return jpaTemplate.getObject().createNativeQuery(consulta).getResultList();
    }

    public SharedEntityManagerBean getJpaTemplate() {
        return jpaTemplate;
    }
}

Vamos à análise das linhas mais relevantes:

  • linhas 11 e 12: configurações do Spring. Essa classe será injetada pelo Spring posteriormente, na camada de controle.
  • linha 13:Essa classe restringe o uso às subclasses de Entidade e utiliza os métodos definidos em InterfaceDAO.
  • linhas 15 e 16: modelo JPA injetado pelo Spring, e que foi definido em AppConfig (Configurando o Spring (II)).
  • linhas 19 e 26: aqui a camada DAO possibilita ao desenvolvedor chamar o método salvar (para apenas uma entidade, com flush no final) ou chamar o método salvarLista (linha 51), que vai gerenciar o flush conforme o processamento vai atingindo blocos de 500 registros.
  • demais linhas: implementação dos demais métodos.

No próximo post, a implementação de um serviço que faz o gerenciamento de transações para chamar os métodos DAO.

Nenhum comentário:

Postar um comentário