Hibernate - Classe DAO Genérica para persistencia de todos os objetos

Seguite galera, estou implementando um sistema usando hibernate annotations e conheco o Hibernate a pouco tempo!

De todas as literaturas que pesquisei, todas criam uma classe DAO para respectiva classe persistente… então decidi criar uma classe GenericDAO, para controlar a persistencia de todos os objetos da aplicação.

Os códigos seguem logo abaixo:

HiberbateUtil

import org.hibernate.*;
import org.hibernate.cfg.*;
/**
 *
 * @author Rogerio MQ
 */

public final class HibernateUtil
{
    private static SessionFactory sessionFactory;

    private static SessionFactory getSessionFactory()  throws MappingException
    {
        if(sessionFactory == null)
            sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
        return sessionFactory;
    }

    public static Session getSession()
    {
        return getSessionFactory().openSession();
    }

}

GenericDAO, Classe que vai controlar a persistencia de todos os meus objetos.

/*
 * GenericDAO.java
 *
 * Created on 22 de Abril de 2007, 16:15
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package spsv02.persistencias;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import javax.swing.JOptionPane;
import org.hibernate.*;
import org.hibernate.criterion.Order;
import org.hibernate.stat.SessionStatistics;
/**
 *
 * @author Rogerio MQ
 */
public class GenericDAO
{
    private static Session sessao;
    private Transaction transacao;

    /** Creates a new instance of GenericDAO */

    public void objInserir( Object obj ) throws Exception
    {
            sessao = HibernateUtil.
            transacao = sessao.beginTransaction();
            sessao.save(obj);
            sessao.flush();
            transacao.commit();
            sessao.close();
    }

    public void objAlterar( Object obj ) throws Exception
    {
            sessao = HibernateUtil.getSession();
            transacao = sessao.beginTransaction();

            sessao.update(obj);
            sessao.flush();
            transacao.commit();
            sessao.close();

    }

    public void objDeletar( Object obj ) throws Exception
    {
            sessao = HibernateUtil.getSession();
            transacao = sessao.beginTransaction();

            sessao.delete(obj);
            sessao.flush();
            transacao.commit();
            sessao.close();

    }

    public List listar(Class clazz) throws Exception
    {
        sessao = HibernateUtil.getSession();
        transacao = sessao.beginTransaction();

        List objts;
        objts = null;
        Criteria selectAll = sessao.createCriteria(clazz);
        transacao.commit();
        objts = selectAll.list();
        sessao.close();
        return objts;
    }

     public Object listar(Class clazz, String pk) throws Exception
    {
        sessao = HibernateUtil.getSession();
        transacao = sessao.beginTransaction();
        Fornecedor objt = (Fornecedor) sessao.load(clazz, new Integer(Integer.parseInt(pk)) );
        transacao.commit();
        sessao.flush();
        sessao.close();
        return objt;
    }


    public void rollBack()
    {
        transacao.rollback();
        sessao.close();
    }

    public void closeConnection() throws Exception
    {
            sessao = HibernateUtil.getSession();
            sessao.connection().createStatement().execute("SHUTDOWN");
    }

}

Por enquanto a classe GenericDAO tem apenas essas funcionalidades, mais pretendo deixa-la padrão para uso na persistencia de qualquer tipo de objeto que persistirá no Banco, ao infez de ficar criando classes DAO para controlar cada uma das classes persistentes.
Para quem gostar da idéia ( transcrever as classes acima padronizando-as para qualquer tipo de uso)… estamos ai!!!

Agradeço desde já!
Abraços!

Opa para complementar vou deixar meu blog com a primeira parte do tutorial de JSF q uso DAO Genérico, com Hibernate e JPA :smiley:

http://javawora.blogspot.com/2007/06/tutorial-parte-1.html

Tem uma explicação bem passo a passo :smiley:
Espero ter ajudado

2 curtidas

alguns comentarios


    public List listar(Class clazz) throws Exception
    {
         sessao = HibernateUtil.getSession();
        transacao = sessao.beginTransaction();

        List objts;
        objts = null;
        Criteria selectAll = sessao.createCriteria(clazz);
        transacao.commit();
        objts = selectAll.list();
        sessao.close();
        return objts;
    }

Vejo que vc não usou generics. Tlv porque quer usar java 1.4 ou anteiror
mas algo que fala ali é controle de exceptions, não ?
Primeiro Exception é demasiado geral para lançar. Segundo deveria lançar uma RuntimeException
Se isso foi escolha sua pense melhor nas opções. Por outro lado fala o controlo de roolback.
Se vc o incluir no metodo não precisa de um método á parte.
Eu pensaria que seria assim:


    public List listar(Class clazz) throws Exception {
        sessao = HibernateUtil.getSession();
        transacao = sessao.beginTransaction();

        try {
              List list = sessao.createCriteria(clazz).list();
              transacao.commit();  
/* tudo bem que numa listagem 
 *nem deveria haver commit, mas 
 * nem é esse o ponto
 */ 
              return list;
        } catch (Exception e){
            transacao.roolback();
            throw e;
        } finally {
           sessao.close();
        }
    }

Tem uma classe Fornecedor aparecendo ali :roll:
O parse para integer é redundante.
O metodo não lista nada, ele só traz um elemento. O nome é enganador

     public Object listar(Class clazz, String pk) throws Exception
    {
        sessao = HibernateUtil.getSession();
        transacao = sessao.beginTransaction();
        Fornecedor objt = (Fornecedor) sessao.load(clazz, new Integer(Integer.parseInt(pk)) );
        transacao.commit();
        sessao.flush();
        sessao.close();
        return objt;
    }

Tlv melhor assim

     public <T> find(Class<T> clazz, Integer pk) throws Exception
    {
        sessao = HibernateUtil.getSession();
        try {
             transacao = sessao.beginTransaction();
             T obj = sessao.load(clazz, pk );
             sessao.flush();
             transacao.commit();
             return  obj
        } finally {
             sessao.close();
        }
    }

Seria tb bom implementar um método que persista vários objetos na mesma transação

Bem, eu tenho uma solução generica para DAOs, no Hibernate e no JPA.
Se liga!

Interface de Repositorio Generica…

public interface GenericRepository<ScopeType, CriteriaType, PkType, DTOClass> {
	
	public void setScope(ScopeType scope);
	public ScopeType getScope();
	
	public void save(DTOClass obj) throws DynaServiceModelException;
	public void update(DTOClass obj) throws DynaServiceModelException;
	public void delete(DTOClass obj) throws DynaServiceModelException;
	
	public DTOClass findById(PkType id) throws DynaServiceModelException;
	public void deleteById(PkType id) throws DynaServiceModelException;

	//Outros métodos

}

Interface para Repositorios usando Hibernate

public interface HibernateGenericRepository<HibernatePkType, HibernateDTOClass> extends GenericRepository<HibernateScope, Criteria, HibernatePkType, HibernateDTOClass>{
	//Só faz a transformação de alguns itens genéricos
}

Classe para DAO Hibernate…

public class HibernateGenericDAO<LocalPkType, LocalDtoClass> extends HibernateSessionFactory implements HibernateGenericRepository<LocalPkType, LocalDtoClass> {

	public HibernateGenericDAO() {
	}

	public void delete(LocalDtoClass obj) throws DynaServiceModelException {
		try {
			Session session = super.getCurrentSession();
			session.delete(obj);
			session.flush();
			checkForLocalScopeAndCommit();
		} catch (Exception e) {
			throw new DynaServiceModelException("Falha ao excluir " + obj.getClass().getSimpleName(), e);
		}
	}

	public void deleteById(LocalPkType id) throws DynaServiceModelException {
		try {
			delete(findById(id));
		} catch (Exception e) {
			throw new DynaServiceModelException(e);
		}
	}

	//Outros métodos

}

Dai em diante, as classes de entidade só extendem essas classes/interfaces…
Com isso:

ClienteRepository extends HibernateGenericRepository<Long, Cliente>
ClienteDAO extends HibernateGenericDAO<Long, Cliente>

E assim por diante…

Bom vamos la :smiley: abaixo minhas humildes considerações

Que tal um pouco de IoC ai?

não acho que seja responsabilidade do seu dao ir abrir ou fechar a sessão,
você poderia colocar essa dependência no construtor, algo como seu código ter um construtor assim:


private Session session;
private Class<T> clazz;

public GenericDAO(Session session, Class<T> clazz){
      this.session = session;
      this.clazz = clazz;
}

até por que você não reparou que você repete esse codigo


sessao = HibernateUtil.   
            transacao = sessao.beginTransaction(); 

umas vinte vezes?
fora que ai você comete um erro terrível, você começa e termina uma transação dentro do seu DAO, sua implementação atual só permite
que eu faça uma ação por transação, eu não poderia por exemplo incluir um cliente depois uma unidade de negocio na mesma transação pro caso de ocorrer um erro na unidade de negocio eu desfazer a inserção do cliente.

da uma olhada nisso http://www.hibernate.org/43.html

outro comentario, não é um Dao generico? então abuse de generics :slight_smile:

Outra coisa que precisa ser revisada é o “throws Exception” em tudo que é método… :stuck_out_tongue:

[quote=rodrigoallemand]Bem, eu tenho uma solução generica para DAOs, no Hibernate e no JPA.
[/quote]

Achei interessante a sua abordagem. Fiquei curioso para saber o que são (para que servem)
ScopeType e CriteriaType.

Outra coisa, é ID (Identifier) ou PK (Primary Key). Podemos pensar que não são a mesma coisa
Identifier : atributo privado que identifica univoca e globalmente o objeto.
PrimaryKey : atributos e/ou conjunto de atributos que identificam univocamente o registro.

Se o repositorio é genérico porque precisa criar ClienteRepository, etc… ? Um HibernateRepository não basta ?


Cliente c = ..
Produto p = ...

HibernateRepository  repo = new HibernateRepository ();

repo.save(c);
repo.save(p);

ScopeType é porque, onde foi implementado esse Framework, nós temos dois modelos de banco de dados, onde um não há o esquema de transação (Commit e Roolback). Com isso, vc cria um enumeration com os escopos disponiveis para que saiba onde commitar. No caso de um banco relacional com controle de transação (um Oracle da vida) esse enumeration pode ser do tipo:

Local: A cada método é aberto uma transação que é confirmada ao fim do método, independente se existem mais coisas sendo feita.
Filter: Deixa que o InterceptingFilter se vira
Request: Parecida com o Filter, mas focada em processos. No nosso Framework, um processo pode chamar outro processo, criando um evento…

E alguns outros tipos

Já o CriteriaType é, esperando a versão 2 do JPA onde se Deus quizer teremos critérios como no Hibernate. Com isso, a montagem de critérios é generica… não monstrei os metodos onde isso acontece… segue exemplo.

public List<DTOClass> find(int page, String orderField, OrderType orderType, boolean ignoreCase, CriteriaType... criterionTypes) throws DynaServiceModelException; public List<DTOClass> find(int page, String orderField, boolean ignoreCase, CriteriaType... criterionTypes) throws DynaServiceModelException; public List<DTOClass> find(int page, boolean ignoreCase, CriteriaType... criterionTypes) throws DynaServiceModelException; public List<DTOClass> find(int page, String orderField, OrderType orderType, CriteriaType... criterionTypes) throws DynaServiceModelException; public List<DTOClass> find(int page, String orderField, CriteriaType... criterionTypes) throws DynaServiceModelException; public List<DTOClass> find(int page, CriteriaType... criterionTypes) throws DynaServiceModelException;

Eu utilizo ai a classe que vc definiu como ID da entidade… mas resolvemos colocar o nome de Pk por facilidade de assimilação com o antigo frameowrks… Mas seria um ID do Hibernate ou do JPA por exemplo…

[quote=sergiotaborda]
Se o repositorio é genérico porque precisa criar ClienteRepository, etc… ? Um HibernateRepository não basta ?

[code]

Cliente c = …
Produto p = …

HibernateRepository repo = new HibernateRepository ();

repo.save©;
repo.save§;

[/code][/quote]

Poderia se vc utilizasse uma outra classe que existe no framework, que é a ObjectRepository extends HibernateGenericRepository <Objetc, Object>…
A ideia é especializar mais as classes sem criar métodos… Fazendo o que vc sugeriu (e é totalmente válido) é ter os repositorios por entidade, extendendo as interfaces sem necessidade de criação de nada mas retornando e recebendo os dados corretos para cada entidade, garantindo possiveis erros…

E se vc quizesse, no meio do projeto, criar o método sbrubleCliente()? Ai sim vc teria que criar uma interface para o repositorio de cliente e refatorar seu código para utilizar tal interface, extendendo de HibernateGenericRepository etc… Porque não criar logo essa interface, extrendendo do lugar correto?
Nós temos um gerador de código para isso… ai a aplicação já vem na estrutura certinha pra isso…

Controle de transação não tem nada a ver com DAO.

Concordo e essa foi uma duvida levantada no desenho da aplicação… apesar de que em 100% dos casos isso ficar no Intercepting Filter, ou seja, nem no DAO e nem das classes de negocio… mas por uma experiencia passada, resolvemos colocar esse marcador dentro do DAO…
Mais hoje em dia eu acho que a ordem do Commit tá mais pra DAO do que pra BO…

entendi. Mas se o DAO é genérico não deveria os Criteira tb ser ?
Ou seja, a interface deveria ser apenas:

public List<DTOClass> list(Criteria c);
public DTOClass find(Criteria c);

Aquele Criteria não é o do Hibernate. É um objeto que contém a query em forma de grafo que cada dao irá converter. No caso do Hibernate do futuro JPA vc converte para o objeto Criteria deles. Para um DAO JDBC puro vc converte direto para SQL.

Ok, eu sei que da ideia à prática vai um pedaço. No framework que eu uso é usada esta estratégia. Como o DAO é puro JDBC o mapeamento é direto para SQL e funciona bem.
(Entende-se que com um mecanismo assim o Hibernate/JPA tem poucas vantagens)

[quote]

Eu utilizo ai a classe que vc definiu como ID da entidade… mas resolvemos colocar o nome de Pk por facilidade de assimilação com o antigo frameowrks… Mas seria um ID do Hibernate ou do JPA por exemplo…

E se vc quizesse, no meio do projeto, criar o método sbrubleCliente()? Ai sim vc teria que criar uma interface para o repositorio de cliente e refatorar seu código para utilizar tal interface, extendendo de HibernateGenericRepository etc… Porque não criar logo essa interface, extrendendo do lugar correto?
Nós temos um gerador de código para isso… ai a aplicação já vem na estrutura certinha pra isso…[/quote]

Humm… Se eu tiver apenas um GenericDAO que serve todas as entidades quando são adicionadas novas entidades não preciso mexer no DAO.
Se precisar criar um método especial ( acho que é isso a que se refere) então basta herdade GenericDAO para um CienteDAO com o método especial. Herdar, não reimplementar toda a interface.

Outra opção é vc ter um ClienteRepository que faz o papel de criar os Criteria e enviar para o DAO respectivo.
(Com Criteria generico isto é simples, tlv com Criteria especifico do Hibernate não seja. ) Ou seja, Terei um GenericDAO com os métodos list e find que mostrei acima e um ClienteRepository com os métodos que vc mostrou tipo Cliente sbrubleCliente() ou findClientesQuePediramProduto(Produto p) que são muito especificos
mas que se resumem a criar um Criteria e enviar ao DAO.

CriteriaType, como no meu código, é o meu item genérico… no seu código está Ctriteria, que ai sim é a classe do hibernate… E o lance é justamente não ter Cast (esse é o boom do Generics [TypeSafety])…
Com isso o org.hibernate.Criteria (não sei se o pacote é esse) pode ser passado, o java.jpa.Criteria (???) tambem pode ser usado e o fulano.CriteriaArretadoDeBom tambem pode ser passado…

Ok. É uma abordagem genérica de Generics… a ideia é que vc implemente a interface ClienteRepository extendendo do HibernateRepository pra justamente vc ter um repositorio “genericamente especializado” (hehehe), ou seja, vc escreve a classe uma vez só e implementa pra entidade que vc quizer, usando o TypeSafety do Generics…
MAs a sua abordagem tb é valida… mas eu acho que vale gastar 1 min (que é o que vc irá gastar) pra gerar uma interface mais especializada.

Cara … no blog da caelum tem um post sobre Daos Genéricos com muitas implementações.

Dá uma olhada lá.

link

Tenho uma implementação de DAO Genérico com uma DAOFactory Dinâmica.

O código que usa a DAOFactory e o DAO fica mais ou menos assim:

[code]
/*

  • Exemplo de um método de transferencia bancária, entre Contas-Correntes.

  • Este método está na classe ContaCorrente.
    */
    public void transferirPara(ContaCorrente cc, BigDecimal valor){
    DAOFactory factory = DAOFactory.getInstance();
    try{
    //Abre uma conexão e inicia a transação…
    factory.txBegin();

     //Cria o dao e passa a conexão, aberta anteriormente, para ele. Estou assumindo que você não está usando uma DAOFactory.
     ContaCorrenteDAO dao = factory.getDao(ContaCorrenteDAO.class);
    
     //Creditar na CC informada
     dao.creditar(this, valor);
     //Debitar da atual
     dao.debitar(cc, valor);
    
     //Commita a transação.
     factory.txCommit();
    

    }catch (DAOException e){
    //Dá rollback na transação e lança uma Exception que caracterize erro no sistema.
    factory.txRollback();

    }finally{
    //Fecha a conexão
    factory.shutdown();
    }
    }[/code]

Nuss… que bom q esse meu post do tempo em que eu estava ainda crú no hibernate gerou uma discussão boa…

Estou quase com uma classe DAO genérica que na qual utilizo o melhor dentre varios conceitos abordados aqui…
Mais a diante posto o resultado…

A primeira solução proposta irá apresentar erro quando se usar um mapeamento @OneToMany com FetchType.LAZY … pois irá fechar a sessão sempre que realizar uma operação.
A sessão é aberta na HibernateUtil, que é estática e singleton, não sei até que ponto é vantajoso fechar uma session.HibernetUtil.getOpenSession();

apresento a seguinte DAO!

[code]/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package persistencia;

import java.io.Serializable;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;

/**
*

  • @author Rafhael
    */
    public class HibernateDAO {

    private Session session;
    private Class persistentClass;
    private Transaction transaction;

    public HibernateDAO() {
    }

    public HibernateDAO(Class persistentClass) {
    this.persistentClass = persistentClass;
    }

    public boolean save(Object obj) {
    try {
    session = HibernateUtil.getSessionFactory().openSession();
    transaction = session.beginTransaction();
    session.save(obj);
    transaction.commit();
    return true;
    } catch (HibernateException e) {
    System.out.println("Save_HibernateDAO: " + e);
    transaction.rollback();
    return false;
    }
    }

    public boolean update(Object obj) {
    try {
    session = HibernateUtil.getSessionFactory().openSession();
    transaction = session.beginTransaction();
    session.update(obj);
    transaction.commit();
    return true;
    } catch (HibernateException e) {
    System.out.println("Update_HibernateDAO: " + e);
    transaction.rollback();
    return false;
    }
    }

    public boolean saveOrUpdate(Object obj) {
    try {
    session = HibernateUtil.getSessionFactory().openSession();
    transaction = session.beginTransaction();
    session.saveOrUpdate(obj);
    transaction.commit();
    return true;
    } catch (HibernateException e) {
    System.out.println("Update_HibernateDAO: " + e);
    transaction.rollback();
    return false;
    }
    }

    public boolean delete(Object obj) {
    try {
    session = HibernateUtil.getSessionFactory().openSession();
    transaction = session.beginTransaction();
    session.delete(obj);
    transaction.commit();
    return true;
    } catch (HibernateException e) {
    System.out.println("Delete_HibernateDAO: " + e);
    transaction.rollback();
    return false;
    }
    }

    public Object findByIdPk(Serializable idPk) {
    Object returnObject = null;
    try {
    session = HibernateUtil.getSessionFactory().openSession();
    returnObject = session.get(persistentClass, idPk);
    } catch (HibernateException e) {
    System.out.println("FindById_HibernateDAO: " + e);
    } finally {
    return returnObject;
    }
    }

    public List findAll() {
    List resultList = null;
    try {
    session = HibernateUtil.getSessionFactory().openSession();
    resultList = session.createCriteria(persistentClass).list();
    } catch (HibernateException e) {
    System.out.println("Hibernate Exception: " + e);
    } finally {
    return resultList;
    }
    }
    }[/code]

O que este método exatamente faz?


public Object findByIdPk(Serializable idPk) {  
        Object returnObject = null;  
        try {  
            session = HibernateUtil.getSessionFactory().openSession();  
            returnObject = session.get(persistentClass, idPk);  
        } catch (HibernateException e) {  
            System.out.println("FindById_HibernateDAO: " + e);  
        } finally {  
            return returnObject;  
        }  
    }  

[quote=mkaule]
O que este método exatamente faz?

[code]

public Object findByIdPk(Serializable idPk) {
Object returnObject = null;
try {
session = HibernateUtil.getSessionFactory().openSession();
returnObject = session.get(persistentClass, idPk);
} catch (HibernateException e) {
System.out.println("FindById_HibernateDAO: " + e);
} finally {
return returnObject;
}
}

[/code][/quote]
Este método recupera um determinado objeto do banco de dados através de sua chave primária, ainda não concordo somente com a PK como parâmetro, ainda deveria ter mais um parâmetro que é a classe/tabela a ser pesquisada no banco.
ex: public Object findByIdPk(Serializable idPk, Clazz classe) {