Implementando o Data Access Object no Java EE

Veja neste artigo o que é o padrão de projeto Data Access Object e como podemos implementá-lo no Java EE 7. Também veremos como podemos utilizar APIs de baixo nível, tais como JPA, para executar operações CRUD.

Toda aplicação empresarial interage com um data source, pode ser um banco de dados relacional, orientado a objetos, um banco de dados NoSQL, um repositório LDAP (Lightweight Directory Access Protocol), um sistema de arquivos, um web servisse SOAP ou REST, ou qualquer outro tipo de sistema externo.

Quaisquer que sejam os data sources, a aplicação deve interagir com estes por meio de operações CRUD (create, retrieve, update, e delete), permitindo assim criar, recuperar, atualizar e deletar objetos do mesmo.

Existem diversas formas de utilizar um data source, além disso, a sua implementação pode variar bastante. Por exemplo, existem diferentes tipos de dialetos SQL dependendo da tecnologia, como os do PostgreSQL e do Oracle.

Assim, o principal objetivo do padrão Data Access Object (DAO) é encapsular o acesso ao data source fornecendo uma interface para que as diversas outras camadas da aplicação possam se comunicar com o data source.

No restante do artigo será visto o que é o padrão DAO, como ele é implementado na plataforma Java EE e quais são os outros tipos de tecnologias e padrões que podem ser utilizados para implementá-lo. Veremos com mais detalhe como podemos utilizar a API JPA, que auxilia bastante na implementação do padrão DAO.

Padrão Data Access Object

O padrão DAO é definido no livro "Core J2EE Patterns" como: "o padrão utilizado para abstrair e encapsular todos os acessos ao data source. O DAO gerencia a conexão com o data source para obter e armazenar informações."

Por meio desta abstração e desse encapsulamento do data source, o maior problema que existia antes da sua criação foi resolvido e, com isso, a aplicação não fica dependente da implementação de um data source específico. Isto desacopla a camada de negócio da camada de data source. Assim, se o data source for alterado, como por exemplo, se mudarmos de um banco de dados Oracle para um banco de dados Postgree, o desaclopamento reduz significativamente qualquer impacto que poderia ocorrer.

No é pouco comum no mundo corporativo a alteração entre fornecedores de um mesmo data source, como por exemplo, de um banco de dados Oracle para um banco de dados Postgree. Isso se dá muito nos órgãos públicos que procuram uma maior atualização nas tecnologias e a troca para outras emergentes ou de software livre. Porém, é mais comum a alteração, por exemplo, de um banco de dados para um sistema de arquivos XML, ou um repositório LDAP, ou então para um web service. Nesse caso, verifica-se o ganho de possuirmos um padrão DAO em um projeto de software. Outro ganho desse padrão é quando o projeto possui mocks e testes de unidade, na qual o padrão DAO ajuda significativamente a manter o código estruturado e limpo.

Alguns desenvolvedores também afirmam um ganho na utilização do padrão DAO no encapsulamento de sistemas de armazenamento legados ou nos casos em que se procura simplificar o acesso às implementações complexas de data sources.

Em relação à implementação do padrão DAO tem-se que este encapsula operações CRUD em uma interface que deve ser implementada por uma classe concreta. Essa interface poderia ser mockada e facilmente testada, evitando uma conexão com o banco de dados, por exemplo. Como sabe-se, a utilização de testes com mocks é mais fácil e recomendável do que os testes de integração com banco de dados, pois estes costumam sujar o banco de dados, mesmo que os dados inseridos sejam removidos, e não oferecem simplicidade e foco nos testes. Por fim, a implementação concreta do DAO usa APIs de baixo nível, tais como JPA e Hibernate, para executar operações CRUD.

Implementação do Padrão DAO

A implementação do padrão DAO envolve vários componentes, tais como a interface DAO e sua implementação concreta, a fábrica (ou Factory) DAO e o DTO (Data Transfer Object), todos opcionais.

O DTO (Data Transfer Object) é um padrão responsável por transportar os dados recuperados ou persistidos numa base de dados através das camadas lógicas de um software. Por exemplo, para transferir uma lista de objetos “Usuario” com informações de um usuário, como seu nome, cpf e endereço, que foram recuperados de uma “Camada de Acesso a Dados” para uma camada “Web”, a camada de "Servico" seria responsável por transferir de um DAO para um DTO os dados solicitados. Por isso que o DTO é também referenciado como um VO (Value Object), que é outro padrão bastante conhecido pelos desenvolvedores Java.

O livro do "Core J2EE Patterns" define o DTO como "o responsável por transportar múltiplos elementos contendo informações entre as camadas do software".

Outra vantagem do DTO é que ele reduz o número de solicitações remotas através da rede em aplicações que fazem muitas chamadas de métodos para enterprise beans. Isso, portanto, resulta em uma melhora significativa no desempenho.

Uma situação muito comum no uso dos DTOs é quando nem toda informação retornada pela base de dados é necessária em uma outra camada do software, como por exemplo, nos casos em que é necessário apenas exibir algumas informações para um usuário na camada Web. Assim, o DTO reduz a busca para trazer apenas as informações necessárias pela camada, otimizando assim a transferência de informações através das camadas.

Uma API que pode ser importante na implementação do padrão DAO é a JPA (Java Persistence API), que gerencia as interações entre as aplicações e o data source, reduzindo bastante o tempo dos desenvolvedores que antigamente tinham que se preocupar com essas questões. A JPA especifica como acessar, persistir e gerenciar informações entre objetos da aplicação e o data source. A JPA não executa o CRUD ou qualquer operação relacionada às informações, essa é apenas um conjunto de interfaces e implementações necessárias. Porém, um servidor de aplicação deve fornecer suporte para seu uso. Uma das novidades da JPA nas versões mais recentes é que ela agora sobrescreve o antigo EJB 2.0 ContainerManaged Persistence (CMP) que era muito complexo e pesado, além de raramente utilizado. Por isso que frameworks de persistência, como Toplink e Hibernate, foram amplamente utilizados em substituição ao CMP do EJB. Com isso, surgiu a especificação da JPA (lançada junto com o EJB 3.0), na qual foram reunidas todas as qualidades do Toplink e do Hibernate em uma especificação padrão e oficial.

A JPA se baseia no conceito de uma entidade (antigo entity bean do EJB 2) que é um objeto capaz de ser persistido em uma base de dados. A grande vantagem é que uma entidade na JPA é um POJO (Plain Old Java Object) em que seus membros são anotados e mapeados para um campo no data source. Por isso a sua implementação é bastante simples e limpa.

Na Listagem 1 temos um exemplo de uma entidade na JPA.

Listagem 1. Exemplo de uma entidade na JPA.

@Entity public class Filme { @Id @GeneratedValue private Long id; private String titulo; private String descricao; private Float preco; public Filme(){} // getters e setters da classe public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public String getDescricao() { return descricao; } public void setDescricao(String descricao) { this.descricao = descricao; } public Float getPreco() { return preco; } public void setPreco(Float preco) { this.preco = preco; } }

Esta é uma classe simples com apenas três anotações, que já a torna persistente. A classe anotada com @Entity indica que esta deve ser tratada como uma classe entidade, e as anotações @Id e @Generated marcam o membro id como um campo de identificação auto-generated, como dito no jargão dos bancos de dados. Isso significa que quando a entidade é persistida, o campo id é automaticamente gerado de acordo com as regras de um campo como sendo auto-gerenciável. Se o data source é uma base de dados, então todos os campos nesta entidade são persistidos para a tabela do banco de dados chamada Filme. Não é necessária nenhuma outra anotação ou implementar qualquer interface ou classe para indicar que os campos são persistentes.

Implementando o padrão DAO na plataforma Java EE

A implementação do padrão DAO na plataforma Java EE é mais bem assimilada por meio de um exemplo mais completo. Como exemplo podemos imaginar que o data source é um banco de dados relacional e queremos implementar uma locadora de filmes. Primeiramente começamos com a criação de uma entidade e anotamos essa classe com as anotações apropriadas da JPA, conforme mostra a Listagem 2.

Listagem 2. Exemplo de uma entidade para a classe Filme.

package br.com.devmedia.dataaccessobject; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.NamedQuery; @Entity public class Filme implements Serializable { private static final long serialVersionUID = -6580012241620579129L; @Id @GeneratedValue private int id; private String titulo; private String descricao; private int preco; //Essa anotação indica que o atributo não é persistente @Transient private int runtimeId; public Filme() {} //getters e setters dos atributos public int getId() { return this.id; } public void setId(int id) { this.id = id; } public String getTitulo() { return this.titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } public String getDescricao() { return this.descricao; } public void setDescricao(String descricao) { this.descricao = descricao; } public int getPreco() { return this.preco; } public void setPreco(int preco) { this.preco = preco; } public int getRuntimeId() { return this.runtimeId; } public void setRuntimeId(int runtimeId) { this.runtimeId = runtimeId; } }

A classe é um simples POJO com as anotações JPA apropriadas. A anotação @Entity na classe indica que esta classe deve ser tratada como uma entidade e deveria ser gerenciada por um provedor de persistência.

Essa classe entidade deve obedecer algumas regras, como ter um construtor sem argumentos, que tem que ser public ou protected, embora ela possa também ter outros construtores. Uma entidade também precisa ser uma classe de alto nível, ou seja, não pode ser uma enum ou uma interface e não deve ser uma classe final. Além disso, nenhuma das variáveis de instância persistentes ou seus métodos getters ou setters pode ser final. A entidade também deve implementar a interface Serializable.

As anotações do atributo id com @Id e @GeneratedValue marcam o atributo id como uma chave primária e autogenerated. Todas as entidades devem ter uma chave primária, que pode ser um único atributo ou uma combinação de atributos.

A chave primária pode ser um dos seguintes tipos: qualquer tipo primitivo (byte, char, short, int, long), qualquer classe Wrapper de primitivos (Byte, Character, Short, Integer, Long), qualquer array de primitivos ou Wrapper (byte[], Byte[], short[], Shot[], int, Integer[], long[], Long[], etc) ou qualquer tipo Java (String, BigInteger, Date).

Todos os atributos da classe entidade são automaticamente mapeados para os campos de mesmo nome na tabela Filme do banco de dados, menos os campos anotados com @Transient. Dessa forma, o atributo id da entidade é mapeado para o campo id da tabela no banco de dados e assim ocorre com os outros campos da entidade.

No código da Listagem 3 será criada uma interface DAO que deve definir os métodos CRUD básicos e alguns que podem ser úteis.

Listagem 3. Exemplo da interface DAO.

package br.com.devmedia.dataaccessobject; import java.util.List; public interface FilmeDAO { public void adicionarFilme(Filme filme); public Filme getFilme(int id); public void removerFilme(int id); public void atualizarFilme(Filme filme); public List<Filme> getTodosFilmes(); }

o código da Listagem 4 tem-se a implementação concreta da interface DAO apresentada. Nesse código temos a implementação do CRUD, onde pode-se notar que o construtor aceita uma instância de EntityManager, que está associada a um contexto de persistência que é definido no arquivo “persistence.xml”.

A API EntityManager fornece funcionalidades para criar, remover, além de poder criar consultas. Vale ressaltar que qualquer campo transient não é salvo nem recuperado da base de dados.

Listagem 4. Implementação da interface DAO.

package br.com.devmedia.dataaccessobject; import java.util.List; import javax.persistence.EntityManager; public class FilmeDAOImpl implements FilmeDAO{ private EntityManager em; public FilmeDAOImpl(EntityManager em) { this.em = em; } @Override public void adicionarFilme(Filme filme) { em.persist(filme); } @Override public Movie getFilme(int id) { return getTodosFilmes().get(id); } @Override public void removerFilme(int id) { em.remove(getFilme(id)); } @Override public void atualizarFilme(Filme filme) { em.merge(filme); } @Override public List<Movie> getTodosFilmes() { return em.createQuery("SELECT filme FROM Filme filme", Filme.class).getResultList(); } }

No código da Listagem 5 tem-se um exemplo de uma fábrica DAO. O EntityManager é criado e injetado nesta classe e então passado como um argumento do construtor para o método criarFilmeDAO que cria o objeto DAO.

Listagem 5. Exemplo de implementação de uma Fábrica DAO

package br.com.devmedia.dataaccessobject; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @ApplicationScoped public class FilmeDAOFactory { @PersistenceContext(unitName="filmePU") private EntityManager em; @Produces public MovieDAO criarFilmeDAO() { //Em FilmeDAOImpl(em) passamos o Entity Manager criado return new FilmeDAOImpl(em); } }

As entidades existentes na aplicação são chamadas de unidade de persistência, que é definida no arquivo de configuração persistence.xml. Este arquivo deve estar no diretório “META-INF” da aplicação.

Os elementos mais significantes do arquivo persistence.xml são:

O EntityManager está associado com um contexto de persistência que é definido no arquivo persistence.xml, conforme mostra o exemplo da Listagem 6.

Listagem 6. Exemplo do arquivo persistence.xml.

<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"> <persistence-unit name="filmePU" transaction-type="JTA"> <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> <jta-data-source>jdbc/sample</jta-data-source> <class>br.com.devmedia.dataaccessobject.Filme</class> </persistence-unit> </persistence>

O data source específico é definido no arquivo persistence.xml. Neste exemplo foi definida a base de dados Derby usando o provedor Eclipse Link. A transação foi definida como tipo JTA porque esta é uma implementação Java EE. Além disso, foi definida uma classe entidade chamada Filme. Nesse caso, deve-se colocar todo o pacote br.com.devmedia.dataaccessobject.Filme.

Por fim, deve-se injetar o DAO que foi criado e usá-lo. O código do cliente é definido no exemplo da Listagem 7. O cliente recebe uma instância do DAO injetado e usa-o para retornar todos os filmes.

Listagem 7. Exemplo de código do cliente usando a estrutura do padrão DAO criada.

package br.com.devmedia.dataaccessobject; import javax.ejb.Stateless; import javax.inject.Inject; import java.util.List; @Stateless public class Cliente { @Inject FilmeDAO filmeDAO; public List<Filme> getTodosFilmes() { return filmeDAO.getTodosFilmes(); } }

Espero que tenham gostado do artigo.

Bibliografia

[1] Erich Gamma, Ricard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994).

[2] Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra. Head First Design Patterns. O'Reilly Media, 2004.

[3] Deepak, A.; Dan, M.; John, C.; Core J2EE Patterns: Best Practices and Design Strategies (2nd Edition). Prentice Hall, 2003.

Artigos relacionados