Mostrando postagens com marcador OOP. Mostrar todas as postagens
Mostrando postagens com marcador OOP. Mostrar todas as postagens

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.

segunda-feira, 23 de setembro de 2013

Criando entidades de bancos de dados

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.

Uma boa alternativa para quem está desenvolvendo um projeto e precisa agilizar os testes durante a modelagem dos dados e a criação da camada DAO é deixar a unidade de persistência e o Hibernate se encarregarem da criação das tabelas das entidades no banco de dados.

Para que isso ocorra, é necessária uma propriedade no arquivo persistence.xml, mais precisamente na linha 13, mostrada abaixo:


  
    org.hibernate.ejb.HibernatePersistence
    java:/fonteDeDados
    entidades.Pessoa
    false
    
      
      
      
      
      
      
    
  


Essa propriedade indica ao Hibernate para apagar todas as tabelas e recriá-las, a cada vez que a aplicação for executada. Depois que todos os testes terminarem, a propriedade pode ser deixada com o valor update, que atualiza ou cria automaticamente qualquer atributo novo ou tabela que for acrescentada ao projeto.


  
    org.hibernate.ejb.HibernatePersistence
    java:/fonteDeDados
    entidades.Pessoa
    false
    
      
      
      
      
      
      
    
  


Caso não haja mais necessidade de alterar a estrutura do banco de dados, a propriedade pode ser simplesmente retirada do arquivo.

Uma pequena observação: se a propriedade estiver configurada para create-drop e ocorrer erro no servidor de aplicações, é preciso remover manualmente as tabelas do banco de dados.

Para demonstrar estas configurações, vou criar uma série de posts com a construção das camadas MVC que serão usadas em um projeto simples para uma livraria.

O primeiro passo é preparar uma classe abstrata que servirá de modelo para todas as entidades que forem criadas no projeto. O objetivo é que todas as entidades atendam os requisitos abaixo:

  1. um método chamado getId() que retorne o valor de sua chave primária;
  2. um atributo temporário chamado flagRemover, que sinalizará ao DAO que o objeto que chegou até ele deve ser removido do banco de dados;

A importância desta classe será demonstrada quando for implementado o CRUD do projeto. Vamos ao código:

package entidades;

import java.io.Serializable;
import javax.persistence.Transient;

public abstract class Entidade implements Serializable {

    @Transient
    public boolean flagRemover;

    public abstract Serializable getId();

    public Boolean getFlagRemover() {
        return flagRemover;
    }

    public void setFlagRemover(Boolean flagRemover) {
        this.flagRemover = flagRemover;
    }
}

Numa rápida análise, o pacote entidades conterá essa classe abstrata que tem  um atributo flagRemover, com seus métodos de acesso (get e set) e um método público abstrato que simula o método get de um atributo chamado id. Esse método possibilita generalizar a obtenção da chave primária de qualquer classe de entidade que herde essa classe, já que ele será implementado na subclasse, e poderá retornar justamente o atributo da chave primária da subclasse. Observamos também que o atributo flagRemover não será escrito no banco de dados, pois está anotado com @Transient. Ele será utilizado apenas para marcar uma entidade que deve ser removida, antes de enviá-la ao DAO.

É bastante clara a vantagem de usar uma classe abstrata em vez de uma interface, pois podemos deixar alguns métodos prontos já para a subclasse, o que não poderia ser feito com o uso de uma interface.

Um rápido exemplo:

package entidades;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public abstract class Pessoa extends Entidade implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer reg;
    @Column(length=50)
    private String nome;
    @Column(length=50)
    private String sobrenome;

    public Integer getReg() {
        return reg;
    }

    public void setReg(Integer reg) {
        this.reg = reg;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }

    public String getSobrenome() {
        return sobrenome;
    }

    public void setSobrenome(String sobrenome) {
        this.sobrenome = sobrenome;
    }

    @Override
    public Serializable getId() {
        return reg;
    }

}

A classe Pessoa (que também é uma classe abstrata!) herda a estrutura da classe Entidade (linha 11), ou seja, automaticamente possui um atributo flagRemover com get e set. Essa classe possui sua própria chave primária reg, anotada devidamente com @Id, e que não tem, inicialmente, uma ligação com o método getId.  No final do código, podemos ver a implementação (que é obrigatória) do método getId, que está retornando o atributo reg (chave primária da classe Pessoa). Portanto, outras classes poderão acessar o valor da chave primária da classe Pessoa, utilizando o método getId(), sem saber que o atributo original é chamado de reg, ou seja, deixando o atributo reg encapsulado.

No próximo post, a construção das classes DAO.