Exemplo DAO genérico

To read the english version, click here.

Olá a todos!

Para esse post vou mostrar como implementar um “DAO genérico”. É algo que desenvolvedores iniciantes apanham bastante, e normalmente acabam escrevendo um monte de código repetido que faz a mesma coisa. Um “DAO genérico” é uma maneira conveniente de ter uma única classe (ou componente se preferir) que consiga fazer operações de persistência em tipos de entidade diferentes. Por exemplo:

//Salvar um cliente usando seu próprio DAO
CustomerDAO customerDAO = new CustomerDAO();
Customer customer = new Customer();
customerDAO.save(customer); //pronto

//Fazer algo parecido para outra entidade
InvoiceDAO invoiceDAO = new InvoiceDAO();
Invoice invoice = new Invoice();
invoiceDAO.save(invoice); //saved

//Porque não ter um único DAO para as duas entidades?
GenericDAO dao = new GenericDAO();
dao.save(customer);
dao.save(invoice);

Claro que se você conhece JPA/Hibernate você já sabe (ou deveria) que o próprio persistence manager, o EntityManager do JPA ou a Session do Hibernate, já é uma implementação de DAO. Para todos os propósitos, um objeto EntityManager é uma abstração para a camada de persistência. Por exemplo, a operação de “salvar” mostrada acima poderia ser facilmente feita somente com o EntityManager:

Customer customer = new Costumer();
Invoice invoice = new Invoice();

//EntityManager já possui métodos para as operações básicas de CRUD
EntityManager em = getEntityManager(); //returns the EntityManager
em.persist(customer);
em.persist(invoice);

Mesmo assim ainda existem operações comuns que não são fornecidas por padrão pela interface EntityManager. Algumas delas que vou mostrar:

  • Recuperar todos os registros de uma entidade, ordenando ou não.
  • Buscar todos os registros de uma entidade pelo valor de uma de suas propriedades.
  • Buscar todos os registros de uma entidade pelo valor textual de uma de suas propriedades, usando o operador “like”.

Para ter uma idéia do que eu to falando, vamos ver como ficaria o código se nós quiséssemos buscar todos os registros de qualquer entidade no nosso domínio:

//Nosso DAO "faz tudo"
GenericDAO dao = GenericDAO();

//Recuperar todos os clientes
List<Customer> customers = dao.findAll(Customer.class);

//Recuperar todos os pedidos
List<Invoice> invoices = dao.findAll(Invoice.class);

Viram só? Nossa classe GenericDAO sabe como fazer a pesquisa em qualquer entidade. Nós podemos até ordenar os resultados:

//Nosso DAO "faz tudo"
GenericDAO dao = GenericDAO();

//Recuperar clientes ordenando por nome
List<Customer> customers = dao.findAll(Customer.class, Order.ASC, "name");

//Recuperar pedidos ordenando por valor
List<Invoice> invoices = dao.findAll(Invoice.class, Order.DESC, "value");

Nem a interface EntityManager nem a interface Session possuem essa funcionalidade. Ela é bastante útil pois pesquisas como essas são feitas o tempo inteiro.

Aqui está o código de como implementamos essa funcionalidade, tanto em JPA como Hibernate puro:

//JPA version
public <T extends BaseEntity<?>> List<T> findAll(Class<T> clazz, Order order, String... propertiesOrder) {
    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<T> cq = cb.createQuery(clazz);
    Root<T> root = cq.from(clazz);
		
    List<javax.persistence.criteria.Order> orders = new ArrayList<>();
    for (String propertyOrder : propertiesOrder) {
        if (order.isAscOrder()) {
            orders.add(cb.asc(root.get(propertyOrder)));
        } else {
            orders.add(cb.desc(root.get(propertyOrder)));
        }
    }
    cq.orderBy(orders);

    return entityManager.createQuery(cq).getResultList();
}


//Hibernate version
public <T extends BaseEntity<?>> List<T> findAll(Class<T> clazz, Order order, String... propertiesOrder) {
    Criteria criteria = session.createCriteria(clazz);

    for (String propertyOrder : propertiesOrder) {
        if (order.isAscOrder()) {
            criteria.addOrder(org.hibernate.criterion.Order.asc(propertyOrder));
        } else {
            criteria.addOrder(org.hibernate.criterion.Order.desc(propertyOrder));
        }
    }
    return criteria.list();
}

Os dois métodos fazem a mesma coisa, exceto que o primeiro usa JPA e o último Hibernate puro. A diferença entre os dois são as API’s criteria de cada um. Em ambos os casos, para atingir esse nível de flexibilidade, buscar todos os registros de qualquer entidade, é necessário usar a API criteria. Tem um link para baixar o código fonte das duas implementações no fim do artigo. Não criemos pânico.

Só mais um exemplo útil do que um “DAO genérico” pode fazer. Recuperar entidades pelo valor de uma das propriedades:

GenericDAO dao = new GenericDAO();

//Recuperar todos os clientes com idade igual a 21
List<Customer> customers = dao.findByProperty(Customer.class, "age", 21);

//Recuperar todos os pedidos com valor igual a 1500.00
List<Invoice> invoices = dao.findByProperty(Invoice.class, "value", 1500.00);

As Strings “age” e “value” fornecidas acima são o nome das propriedades em cada uma das entidades. (aquelas que normalmente você tem um get/set para cada).

Tenham em mente que ter um “DAO genérico” é uma decisão de design, não uma obrigação. Na maioria dos casos eu acredito que compensa. Simplifica ter que escrever o mesmo código para aquelas operações básicas várias vezes.

Talvez a maioria de vocês considere (eu consideraria) em ter um “service genérico” para o mesmo fim, não somente um “DAO genérico”. Vai fazer sentido nos casos onde você não quer expor diretamente os seus DAOs a sua camada de visão (os ManagedBeans da vida).

Você pode baixar o código fonte do github aqui. Qualquer pergunta é só deixar um comentário!

Até a próxima 🙂

8 thoughts on “Exemplo DAO genérico

  1. Pingback: Generic DAO example | Code to live. Live to code.

  2. H how do i make this hibernate generic dao work. With mysql … When i change it’s just giving me null pointer ..please advise on how to do it

    • Hi Kelvin!

      Couple of things I could say:

      1- Since you wrote this message in english, I’m supposing you do realise there’s an english version for this article: https://rodrigouchoa.wordpress.com/2014/09/26/generic-dao-example/
      Because you posted this question on the portuguese version 🙂

      2- About your actual question, since I’m mainly using the hibernate API’s nothing in there would change to make it work for different databases. I’m 99% certain of that. The only things you need to change are hibernate/jpa specific configuration (dialect is one of them)

      3- If you post the stack trace for the exception, it might give me clues of what’s the issue.

      Cheers!

Leave a comment