Hibernate em Sistemas Desktop

Boa tarde galera!

Estou com um baita problema. Desenvolvo um sistema para gerenciamento de um estúdio fotográfico utilizando swing e hibernate e banco de dados mysql. Tenho a aplicação instalada em dois computadores, o grande problema é que as alterações feitas em um computador não são acessadas pelo outro até que este encerre e inicie novamente a aplicação, ou seja, o sistema de cache do hibernate não “vê” estas alterações feitas em outro computador, o que é lógico, pois seria esperar demais que adivinhasse atualizações. Tentei desabilitar o sistema de caches do hibernate com o seguinte código:

            .setProperty("hibernate.cache.use_query_cache", "false")
            .setProperty("hibernate.cache.provider_class", "org.hibernate.cache.NoCacheProvider")
            .setProperty("hibernate.cache.use_second_level_cache", "false");

Realmente não sei o que fazer para resolver este problema, se implemento uma comunicação entre as aplicações para que se avisem quando fizerem alterações e dar um flush (ou sei lá o que) no hibernate para ele elimitar os objetos em cache.

Espero que tenha sido claro em meu problema. Agradeço a atenção de todos.

Nunca ouvi falar de um problema parecido.
Quer dizer que se por exemplo, você atualizar o cadastro de um cliente. Você troca o endereço e tel dele em um computador. Aí, logo depois você consulta os dados do cliente no outro computador e aparece os dados antigos ??

É esse mesmo seu problema ?

Pelo que eu entendi, você não está utilizando o conceito de Transação do próprio Hibernate, ou vc está?
Experimente após cada ação, efeutar um commitTransaction().
Desse modo a base deve se alterada com os novos dados

Boa sorte

Pergunto…

Vc está comitando a transação?
Fechando a session?
Eu lembro de já ter visto algo assim… tenta dar um session.flush() pra ver se resolve.

VELO

Mas se depois que ele reestarta a aplicação ele vê as alterações, parece n ser problema de controle de transação… :roll: ou está com o autocommit = true ??? Nuh

Provavelmente.
Mas será que o Hibernate não provê esse mecanismo? Commitar a transação quando o coletor tentar apanhar o objeto da sessão?

Talvez o problema não seja com o hibernate mas com a arquitetura utilizada. Sugiro criar uma camada de negócios remota que seja acessada por ambas aplicações…

Olá galera… vamos aos códigos!

Tenho usado esta classe auxiliar para o hibernate:

/*
 * HibernateUtil.java
 *
 * Created on 9 de Agosto de 2006, 15:44
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package dao;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
import org.hibernate.*;
import org.hibernate.cfg.*;

/**
 *
 * @author flavio
 */
public class HibernateUtil {
    
    private static final SessionFactory factory;
    private static final Configuration cfg;
    private static final ThreadLocal sessionThread = new ThreadLocal();
    private static final ThreadLocal transactionThread = new ThreadLocal();
    
    private static String host = null;
    private static String database = null;
    private static String user = null;
    private static String passwd = null;
    private static Properties prop = new Properties();
    
    static {
        //Bloco estático que inicializa o Hibernate, escreve o stack trace se houver algum problema e relança a exceção
        try {
            carregarParametros();
            
            cfg = new Configuration()
            .addClass(bean.Cliente.class)
            .addClass(bean.Evento.class)
            .addClass(bean.Imagem.class)
            .addClass(bean.Classificador.class)
            .addClass(bean.Uf.class)
            .addClass(bean.Produto.class)
            .addClass(bean.Formato.class)
            .addClass(bean.Pedido.class)
            .addClass(bean.ItemPedidoImagem.class)
            .setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect")
            .setProperty("hibernate.connection.driver_class", "com.mysql.jdbc.Driver")
            .setProperty("hibernate.connection.url", "jdbc:mysql://" + host + "/" + database + "?autoReconnect=true&jdbcCompliantTruncation=false")
            .setProperty("hibernate.show_sql", "true")
            .setProperty("hibernate.connection.username", user)
            .setProperty("hibernate.connection.password", passwd)
            .setProperty("hibernate.cache.use_query_cache", "false")
            .setProperty("hibernate.cache.provider_class", "org.hibernate.cache.NoCacheProvider")
            .setProperty("hibernate.cache.use_second_level_cache", "false");
            
            
            factory = cfg.buildSessionFactory();
            
        } catch (RuntimeException e) {
            e.printStackTrace();
            throw e;
        }
    }
    
    public static void carregarParametros() {
        try {
            FileInputStream file = new FileInputStream(new File("config.ini"));
            prop.load(file);
            host = prop.getProperty("host");
            database = prop.getProperty("database");
            user = prop.getProperty("user");
            passwd = prop.getProperty("passwd");
            
            System.setProperty("path", prop.getProperty("path", "c:/photos/"));
            System.setProperty("pathThumbs", prop.getProperty("pathThumbs", "c:/photos/thumbnails/"));
            System.setProperty("pathPreviews", prop.getProperty("pathPreviews", "c:/photos/previews/"));
            
            file.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static Session getSession() {
        Session session = (Session) sessionThread.get();
        if ( session  == null  || !session.isOpen()  ) {
            session = factory.openSession();
            sessionThread.set( session );
        }
        return (Session) sessionThread.get();
    }
    
    public static void closeSession() {
        Session session = (Session) sessionThread.get();
        if ( session != null && session.isOpen() ) {
            sessionThread.set(null);
            session.close();
        }
    }
    
    public static void beginTransaction() {
        Transaction transaction = getSession().beginTransaction();
        transactionThread.set(transaction);
    }
    
    public static void commitTransaction() {
        Transaction transaction = (Transaction) transactionThread.get();
        if ( transaction != null &&  !transaction.wasCommitted() && !transaction.wasRolledBack() ) {
            transaction.commit();
            transactionThread.set(null);
        }
    }
    
    public static void rollbackTransaction() {
        Transaction transaction = (Transaction) transactionThread.get();
        if ( transaction != null &&  !transaction.wasCommitted() && !transaction.wasRolledBack() ) {
            transaction.rollback();
            transactionThread.set(null);
        }
    }
    
}

Tenho o seguinte DAO para clientes:

public class ClienteDAO {
    
    public static void save(Cliente cliente) {
        Session session = HibernateUtil.getSession();
        HibernateUtil.beginTransaction();
        
        session.save(cliente);
        
        HibernateUtil.commitTransaction();
    }
    
    public static void update(Cliente cliente) {
        Session session = HibernateUtil.getSession();
        HibernateUtil.beginTransaction();
        
        session.update(cliente);
        
        HibernateUtil.commitTransaction();
        HibernateUtil.closeSession();
    }
    
    public static void delete(Cliente cliente) {
        Session session = HibernateUtil.getSession();
        HibernateUtil.beginTransaction();
        
        session.delete(cliente);
        
        HibernateUtil.commitTransaction();
    }
    
    public static List<Cliente> list() {
        List<Cliente> clientes;
        Session session = HibernateUtil.getSession();
        session.flush();
        
        clientes = session.createQuery("from Cliente order by nome").list();
        
        return clientes;
        
    }
    
    public static List<Cliente> list(String nome) {
        List<Cliente> clientes;
        Session session = HibernateUtil.getSession();
        session.flush();
        
        clientes = session.createQuery("from Cliente where nome like :nome order by nome").setString("nome", "%" + nome + "%").list();
        
        return clientes;
        
    }
    
    public static List<Cliente> getByNome(String nome) {
        List<Cliente> clientes;
        Session session = HibernateUtil.getSession();
        session.flush();
        
        clientes = session.createQuery("from Cliente where nome = :nome order by nome").setString("nome", nome).list();
        
        return clientes;
    }
    
    public static Cliente getById(Integer id) {
        Session session = HibernateUtil.getSession();
        return (Cliente)session.get(Cliente.class, id);
    }
    
}

As linhas session.flush() acrescentei-as a pouco para testar, mas não funcionou… Creio que o hibernate esteja mantendo as instancias já lidas em cache o que faz com que novas leituras (ClienteDAO.list(), por exemplo) não apareçam na outra aplicação aberta. E isto ocorre mesmo se rodar duas instancias de aplicação na mesma máquina.

Já to pensando seriamente em fazer uma camada de acesso remoto como indicou o Guerr@, mas o problema é que sou iniciante em Java e sei que tomarei uma surra tremendar…

Ouço falar de RMI e outras coisas referentes a objetos remotos… Acho que a solução seria algo neste sentido (isto claro se minha solução atual não funcionar mesmo).

Obrigado a todos.

[quote=flaviomreis]Já to pensando seriamente em fazer uma camada de acesso remoto como indicou o Guerr@, mas o problema é que sou iniciante em Java e sei que tomarei uma surra tremendar…

Ouço falar de RMI e outras coisas referentes a objetos remotos… Acho que a solução seria algo neste sentido (isto claro se minha solução atual não funcionar mesmo).[/quote]

Embora eu acredite que seu problema imediato possa ser resolvido de outras formas, centralizar a camada de negócios num servidor de aplicação provavelmente é a melhor estratégia pro seu caso.

O genesis foi criado pra resolver justamente esse tipo de problema: criar aplicações desktop com backend enterprise. Ele torna tudo isso bem fácil e elimina a necessidade de você ter que conhecer RMI e afins. Na verdade, ele permite até mesmo a transição de um modo de execução local pra remoto de forma transparente, sem modificar nenhuma linha do seu código. Vale a pena conferir.

mister__m, desculpe a ignorância, ainda estou lendo a documentação do genesis, mas tu não terias algum exemplo simples de como utilizar o genesis para resolver meu problema do hibernate para que eu possa estudar/adaptar? Obrigado

Dei uma olhada superficial no genesis, mas me pareceu um pouco confuso, além de demonstrar-se ser um canhão para meu pequeno problema… Acho que vou partir para obtenção de objetos remotamente, utilizando uma aplicação só para servidor dados às minha aplicações finais… Bora estudar objetos remotos…

Bah… nada é fácil na vida ehehehe (que bom!)… Bom, existe como eu eliminar totalmente os caches do hibernate? Gostaria detestar isto antes de partir para outra solução, porque pelo que to vendo implementar um servidor de dados com RMI não é tão simples assim. Valeu!

Pelo que vi pelo seu código, o problema é que esse closeSession() não é chamado e você usa sempre a mesma Session. Tente resolver isso.

que tal tentar executar o session.flush() e hibernateUtil.closeSession() após o save e update?
Não sei se foi seu caso, mas tem gente que pega essa classe no Hibernate In Action e simplesmente tenta usá-la na marra, sem saber o que está acontecendo a cada linha.
Tente realmente descarregar a sessão e posteriormente fechá-la. Teoricamente o hibernate é OBRIGADO a salvar os objetos no banco

Boa sorte

Valeu mister__m e xgucax… alterei o método save e update conforme orientam e alterei-os:

    public static void save(Cliente cliente) {
        Session session = HibernateUtil.getSession();
        HibernateUtil.beginTransaction();
        
        session.save(cliente);
        session.flush();
        
        HibernateUtil.commitTransaction();
        HibernateUtil.closeSession();
    }
    
    public static void update(Cliente cliente) {
        Session session = HibernateUtil.getSession();
        HibernateUtil.beginTransaction();
        
        session.update(cliente);
        session.flush();
        
        HibernateUtil.commitTransaction();
        HibernateUtil.closeSession();
    }

Com ambos métodos chamados, a seguinte exceção ocorre ao tentar salvar a mesma instância duas vezes (clicar no botão de salvar duas vezes sem alterar nada, o que faz chamar o método ClienteDAO.update()…

Exception occurred during event dispatching:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [bean.Cliente#1]
        at org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:556)
        at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:259)
        at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:217)
        at org.hibernate.event.def.DefaultUpdateEventListener.performSaveOrUpdate(DefaultUpdateEventListener.java:33)
        at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
        at org.hibernate.impl.SessionImpl.fireUpdate(SessionImpl.java:564)
        at org.hibernate.impl.SessionImpl.update(SessionImpl.java:552)
        at org.hibernate.impl.SessionImpl.update(SessionImpl.java:544)
        at dao.ClienteDAO.update(ClienteDAO.java:32)

A linha 32 de ClienteDAO.java é uma chamada à session.update(cliente).

Se retiro a chamada ao método closeSession() tudo funciona perfeitamente na aplicação, mas a outra instância da aplicação (digamos aplicação2) continua não “enxergando” as mudança, até que ela seja fechada e executada novamente(a aplicação2)… assim, creio que o problema não seja flush ou closeSession(), pois a sessão de uma aplicação, nada tem a ver com a sessão de outra… e se, como anteriormente, não chamando flush e closeSession(), ao simples reiniciar da aplicação2 fazia com que as mudanças aparececem, creio que seja um problema de cache.

Como rmi é um troço bem mais complicado do que eu via a princípio, já to pensando até em alterar meu DAO para utilizar JDBC ao invés de hibernate… sinceramente não sei o que fazer.

Obrigado gente!

Flavio,

Você precisa chamar o closeSession() em todos os lugares onde abre a Session, em um bloco finally, para que, mesmo se uma exceção seja lançada, a Session seja encerrada corretamente.

Conceitualmente no Hibernate, a Session é uma unit-of-work, ou seja, o seu contexto imutável, em que uma operação lógica de negócios acontece. Então você sempre quer usar uma Session nova e encerá-la corretamente toda vez que fizer uma operação de negócios envolvendo o banco.

O seu problema atual mostra que a mesma Session já leu uma instância de Cliente com a mesma chave e que por um acaso você está usando essa mesma Session no seu código. É isso que você tem que resolver.

Se você usasse o genesis ou o Spring pra gerenciar seu código que interage com o banco, você não estaria lutando com esse problema ainda. Então, ao invés de reescrever seu código pra usar JDBC e baixar o nível, por que não adotar um framework que poderá simplificar seu trabalho daqui pra frente?

Obrigado mister__m… Só pra finalizar então… Se eu utilizar, digamos, springer é certo que eu consiguirei utilizar duas estações, ambas com um programa swing atualizando a base de dados ao mesmo tempo via hibernate e que as atualizações poderão ser percebidas em ambos após uma releitura (select através de HQL)?. Valeu novamente.

Tanto usando Spring como o genesis - que vai te ajudar em outras coisas no desktop também - você vai resolver seu problema.

Falou… Mãos a obra então… bora pesquisar… Brigadão mister__m.

[]'s

heheheh…

Antes de partir pro spring modifiquei um DAO para teste, ou seja, coloquei um closeSession ao final de cada método:

public class ClienteDAO {
    
    public static void save(Cliente cliente) {
        Session session = HibernateUtil.getSession();
        HibernateUtil.beginTransaction();
        
        session.save(cliente);
        
        HibernateUtil.commitTransaction();
        HibernateUtil.closeSession();
    }
    
    public static void update(Cliente cliente) {
        Session session = HibernateUtil.getSession();
        HibernateUtil.beginTransaction();
        
        session.update(cliente);
        
        HibernateUtil.commitTransaction();
        HibernateUtil.closeSession();
    }
    
    public static void delete(Cliente cliente) {
        Session session = HibernateUtil.getSession();
        HibernateUtil.beginTransaction();
        
        session.delete(cliente);
        
        HibernateUtil.commitTransaction();
        HibernateUtil.closeSession();
    }
    
    public static List<Cliente> list() {
        List<Cliente> clientes;
        Session session = HibernateUtil.getSession();
        
        clientes = session.createQuery("from Cliente order by nome").list();
        
        HibernateUtil.closeSession();
        return clientes;
        
    }
    
    public static List<Cliente> list(String nome) {
        List<Cliente> clientes;
        Session session = HibernateUtil.getSession();
        
        clientes = session.createQuery("from Cliente where nome like :nome order by nome").setString("nome", "%" + nome + "%").list();
        
        HibernateUtil.closeSession();
        return clientes;
        
    }
    
    public static List<Cliente> getByNome(String nome) {
        List<Cliente> clientes;
        Session session = HibernateUtil.getSession();
        
        clientes = session.createQuery("from Cliente where nome = :nome order by nome").setString("nome", nome).list();
        
        HibernateUtil.closeSession();
        return clientes;
    }
    
    public static Cliente getById(Integer id) {
        Session session = HibernateUtil.getSession();
        
        Cliente cliente = (Cliente)session.get(Cliente.class, id);
        
        HibernateUtil.closeSession();
        
        return cliente ;
    }
    
}

De cara, quando precisei obter a Uf (cliente.getUf()) do indivíduo, através de lazy=“true”, lançou-se a exceção:

Exception in thread "AWT-EventQueue-0" org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed

Classe Cliente:

public class Cliente {
    
    private Integer id;
    private String nome;
    private String endereco;
    private String complemento;
    private String bairro;
    private String cidade;
    private Uf uf;
    private String cep;
    private String telefone;
    private String celular;
    private String email;
    private String cpf;
    private String rg;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNome() {
        return nome;
    }

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

    public String getEndereco() {
        return endereco;
    }

    public void setEndereco(String endereco) {
        this.endereco = endereco;
    }

    public String getComplemento() {
        return complemento;
    }

    public void setComplemento(String complemento) {
        this.complemento = complemento;
    }

    public String getBairro() {
        return bairro;
    }

    public void setBairro(String bairro) {
        this.bairro = bairro;
    }

    public String getCidade() {
        return cidade;
    }

    public void setCidade(String cidade) {
        this.cidade = cidade;
    }

    public Uf getUf() {
        return uf;
    }

    public void setUf(Uf uf) {
        this.uf = uf;
    }

    public String getCep() {
        return cep;
    }

    public void setCep(String cep) {
        this.cep = cep;
    }

    public String getTelefone() {
        return telefone;
    }

    public void setTelefone(String telefone) {
        this.telefone = telefone;
    }

    public String getCelular() {
        return celular;
    }

    public void setCelular(String celular) {
        this.celular = celular;
    }

    public String getCpf() {
        return cpf;
    }

    public void setCpf(String cpf) {
        this.cpf = cpf;
    }

    public String getRg() {
        return rg;
    }

    public void setRg(String rg) {
        this.rg = rg;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    
}

Desculpe eu ser insistente, mas o spring ao comportar-se da mesma forma, isto fechando minha sessão a cada uso, também não ocorrerá as famigeradas LazyInitializatioException?

Obrigado novamente