O padrão DAO em Java
1. Visão geral
O padrão Data Access Object (DAO) é um padrão estrutural que nos permiteisolate the application/business layer from the persistence layer (usually a relational database, but it could be any other persistence mechanism) using an abstract API.
A funcionalidade dessa API é ocultar do aplicativo todas as complexidades envolvidas na execução de operações CRUD no mecanismo de armazenamento subjacente. Isso permite que ambas as camadas evoluam separadamente sem saber nada uma da outra.
Neste tutorial, daremos um mergulho profundo na implementação do padrão e aprenderemos como usá-lo para abstrair chamadas para aJPA entity manager.
Leitura adicional:
Introdução ao Spring Data JPA
Introdução ao Spring Data JPA com Spring 4 - a configuração do Spring, o DAO, consultas manuais e geradas e gerenciamento de transações.
Visão geral dos tipos de cascata JPA / Hibernate
Uma visão geral rápida e prática dos tipos de cascata JPA / Hibernate.
2. Uma implementação simples
Para entender como funciona o padrão DAO, vamos criar um exemplo básico.
Digamos que queremos desenvolver um aplicativo que gerencie usuários. Para manter o modelo de domínio do aplicativo completamente agnóstico em relação ao banco de dados, criaremosa simple DAO class that will take care of keeping these components neatly decoupled from each other.
2.1. A classe de domínio
Como nosso aplicativo funcionará com os usuários, precisamos definir apenas uma classe para implementar seu modelo de domínio:
public class User {
private String name;
private String email;
// constructors / standard setters / getters
}
A classeUser é apenas um contêiner simples para os dados do usuário, portanto, não implementa nenhum outro comportamento digno de destaque.
Obviamente, a escolha de design mais relevante que precisamos fazer aqui é como manter o aplicativo que usa essa classe isolado de qualquer mecanismo de persistência que possa ser implementado em algum momento.
Bem, esse é exatamente o problema que o padrão DAO tenta resolver.
2.2. A API DAO
Vamos definir uma camada DAO básica, para que possamos ver como ela podekeep the domain model completely decoupled from the persistence layer.
Aqui está a API DAO:
public interface Dao {
Optional get(long id);
List getAll();
void save(T t);
void update(T t, String[] params);
void delete(T t);
}
Do ponto de vista de um pássaro, fica claro que a interfaceDao define uma API abstrata que realiza operações CRUD em objetos do tipoT.
Devido ao alto nível de abstração que a interface fornece, é fácil criar uma implementação concreta e refinada que funcione com objetosUser.
2.3. A classeUserDao
Vamos definir uma implementação específica do usuário da interfaceDao:
public class UserDao implements Dao {
private List users = new ArrayList<>();
public UserDao() {
users.add(new User("John", "[email protected]"));
users.add(new User("Susan", "[email protected]"));
}
@Override
public Optional get(long id) {
return Optional.ofNullable(users.get((int) id));
}
@Override
public List getAll() {
return users;
}
@Override
public void save(User user) {
users.add(user);
}
@Override
public void update(User user, String[] params) {
user.setName(Objects.requireNonNull(
params[0], "Name cannot be null"));
user.setEmail(Objects.requireNonNull(
params[1], "Email cannot be null"));
users.add(user);
}
@Override
public void delete(User user) {
users.remove(user);
}
}
A classeUserDao implementa toda a funcionalidade necessária para buscar, atualizar e remover objetosUser.
For simplicity’s sake, the users List acts like an in-memory database, which is populated with a couple of User objects in the constructor.
Claro, é fácil refatorar os outros métodos, para que eles possam funcionar, por exemplo, com um banco de dados relacional.
Embora as classesUser eUserDao coexistam independentemente dentro do mesmo aplicativo, ainda precisamos ver como a última pode ser usada para manter a camada de persistência oculta da lógica do aplicativo:
public class UserApplication {
private static Dao userDao;
public static void main(String[] args) {
userDao = new UserDao();
User user1 = getUser(0);
System.out.println(user1);
userDao.update(user1, new String[]{"Jake", "[email protected]"});
User user2 = getUser(1);
userDao.delete(user2);
userDao.save(new User("Julie", "[email protected]"));
userDao.getAll().forEach(user -> System.out.println(user.getName()));
}
private static User getUser(long id) {
Optional user = userDao.get(id);
return user.orElseGet(
() -> new User("non-existing user", "no-email"));
}
}
O exemplo é artificial, mas mostra, em poucas palavras, as motivações por trás do padrão DAO. Nesse caso, o métodomain usa apenas uma instânciaUserDao para realizar operações CRUD em alguns objetosUser.
The most relevant facet of this process is how UserDao hides from the application all the low-level details on how the objects are persisted, updated, and deleted.
3. Usando o padrão com JPA
Há uma tendência geral entre os desenvolvedores de pensar que o lançamento do JPA foi rebaixado para zero a funcionalidade do padrão DAO, já que o padrão se torna apenas mais uma camada de abstração e complexidade implementada em cima daquela fornecida pelo gerente de entidade do JPA.
Inquestionavelmente, em alguns cenários isso é verdade. Mesmo assim, sometimes we just want to expose to our application only a few domain-specific methods of the entity manager’s API. Nesses casos, o padrão DAO tem seu lugar.
3.1. A classeJpaUserDao
Dito isso, vamos criar uma nova implementação da interfaceDao, para que possamos ver como ela pode encapsular a funcionalidade que o gerenciador de entidade JPA fornece pronto para uso:
public class JpaUserDao implements Dao {
private EntityManager entityManager;
// standard constructors
@Override
public Optional get(long id) {
return Optional.ofNullable(entityManager.find(User.class, id));
}
@Override
public List getAll() {
Query query = entityManager.createQuery("SELECT e FROM User e");
return query.getResultList();
}
@Override
public void save(User user) {
executeInsideTransaction(entityManager -> entityManager.persist(user));
}
@Override
public void update(User user, String[] params) {
user.setName(Objects.requireNonNull(params[0], "Name cannot be null"));
user.setEmail(Objects.requireNonNull(params[1], "Email cannot be null"));
executeInsideTransaction(entityManager -> entityManager.merge(user));
}
@Override
public void delete(User user) {
executeInsideTransaction(entityManager -> entityManager.remove(user));
}
private void executeInsideTransaction(Consumer action) {
EntityTransaction tx = entityManager.getTransaction();
try {
tx.begin();
action.accept(entityManager);
tx.commit();
}
catch (RuntimeException e) {
tx.rollback();
throw e;
}
}
}
A classeJpaUserDao é capaz de trabalhar com qualquer banco de dados relacional suportado pela implementação JPA.
Além disso, se olharmos atentamente para a classe, vamos perceber como o uso deCompositioneDependency Injection nos permite chamar apenas os métodos do gerenciador de entidade exigidos por nosso aplicativo.
Simplificando, temos uma API personalizada específica do domínio, em vez de toda a API do gerenciador de entidade.
3.2. Refatorando a classeUser
Neste caso, usaremos o Hibernate como a implementação padrão do JPA, portanto, iremos refatorar a classeUser de acordo:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String email;
// standard constructors / setters / getters
}
3.3. Inicializando um gerenciador de entidades JPA programaticamente
Assumindo que já temos uma instância de trabalho do MySQL rodando local ou remotamente e uma tabela de banco de dados“users” preenchida com alguns registros de usuário, precisamos obter um gerenciador de entidade JPA, para que possamos usar a classeJpaUserDao para executar operações CRUD no banco de dados.
Na maioria dos casos, fazemos isso por meio do arquivo“persistence.xml” típico, que é a abordagem padrão.
Neste caso, vamos usar uma abordagem“xml-less” e obter o gerenciador de entidades com Java simples por meio da classeEntityManagerFactoryBuilderImpl útil do Hibernate.
Para obter uma explicação detalhada sobre como inicializar uma implementação JPA com Java, verifiquethis article.
3.4. A classeUserApplication
Finalmente, vamos refatorar a classeUserApplication inicial, para que ela possa trabalhar com uma instânciaJpaUserDaoe executar operações CRUD nas entidadesUser:
public class UserApplication {
private static Dao jpaUserDao;
// standard constructors
public static void main(String[] args) {
User user1 = getUser(1);
System.out.println(user1);
updateUser(user1, new String[]{"Jake", "[email protected]"});
saveUser(new User("Monica", "[email protected]"));
deleteUser(getUser(2));
getAllUsers().forEach(user -> System.out.println(user.getName()));
}
public static User getUser(long id) {
Optional user = jpaUserDao.get(id);
return user.orElseGet(
() -> new User("non-existing user", "no-email"));
}
public static List getAllUsers() {
return jpaUserDao.getAll();
}
public static void updateUser(User user, String[] params) {
jpaUserDao.update(user, params);
}
public static void saveUser(User user) {
jpaUserDao.save(user);
}
public static void deleteUser(User user) {
jpaUserDao.delete(user);
}
}
Mesmo quando o exemplo é bastante limitado, ele permanece útil para demonstrar como integrar a funcionalidade do padrão DAO com a que o gerenciador de entidade fornece.
Na maioria dos aplicativos, há uma estrutura de DI, que é responsável por injetar uma instânciaJpaUserDao na classeUserApplication. Para simplificar, omitimos os detalhes deste processo.
O ponto mais relevante a enfatizar aqui é comothe JpaUserDao class helps to keep the UserApplication class completely agnostic about how the persistence layer performs CRUD operations.
Além disso, poderíamos trocar o MySQL por qualquer outro RDBMS (e mesmo por um banco de dados plano) mais adiante e, ainda assim, nosso aplicativo continuaria funcionando como esperado, graças ao nível de abstração fornecido pela interfaceDao e o gerente da entidade.
4. Conclusão
Neste artigo, demos uma olhada em detalhes nos conceitos-chave do padrão DAO, como implementá-lo em Java e como usá-lo em cima do gerenciador de entidade JPA.
Como de costume, todos os exemplos de código mostrados neste artigo estão disponíveisover on GitHub.