Hibernate - Comportamento estranho no UPDATE

Boa noite pessoal, estou usando a versão 3.2.0cr2 do Hibernate e a versão 3.2.0cr1 do Hibernate Annotations.

Estou fazendo uma pequena aplicação para testar a combinação Mentawai + Hibernate. Surgiu um comportamento estranho no Hibernate que não estou sabendo identificar.

O problema é o seguinte…

Tenho a seguinte definição de tabela (Postgres 8.1)

CREATE TABLE tb_contato
(
  id_contato serial NOT NULL,
  ds_nome varchar(255),
  ds_email varchar(255),
  ds_endereco varchar(255),
  CONSTRAINT tb_contato_pkey PRIMARY KEY (id_contato),
  CONSTRAINT ind_unique_nome UNIQUE (ds_nome)
) 
WITHOUT OIDS;
ALTER TABLE tb_contato OWNER TO postgres;

Observem que o campo ds_nome tem que ser único.

O seguinte bean de mapeamento:

@Entity
@Table(name="tb_contato")
@javax.persistence.SequenceGenerator(
    name="contacts_seq",
    sequenceName="tb_contato_id_contato_seq"
)
public class Contact implements Serializable {
    
    public Contact() {
    }

    private java.lang.Integer id;

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE,generator="contacts_seq") 
    @Column(name="id_contato")
    public java.lang.Integer getId() {
        return this.id;
    }

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

    private String name;

    @Column(name="ds_nome", unique=true)
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private String email;

    @Column(name="ds_email")
    public String getEmail() {
        return this.email;
    }

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

    private String address;

    @Column(name="ds_endereco")
    public String getAddress() {
        return this.address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public boolean equals(Object obj) {
        if (obj instanceof Contact) {
            Contact contact = (Contact)obj;
            if (contact.getId() == this.getId()) 
                return true;
            return false;
        }
        return false;
    }

    public String toString() {
        return "ID: " + this.getId();
    }

    public int hashCode() {
        return this.getId();
    }
    
}

E tenho o seguinte método na minha classe HibernateContactDAO:

...
    public void update(Contact contact) throws Exception {
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
            Contact c = (Contact)session.load(Contact.class,contact.getId());
            BeanUtils.copyProperties(c, contact);            
            tx.commit();
        } catch (Exception e) {
            if (tx != null && tx.isActive())
                tx.rollback();
            throw e;
        }
        
    }
...

Vou simular o erro para vcs…

Imaginem que no BD tem 2 registros com ds_nome = “A” e ds_nome= “B” ,repectivamente.

Agora imaginem que o usuário carrega o objeto contact com o ds_nome=“A”, muda o nome para “B” e chama o método update().

Gera um erro de violação de constraint até aí normal.

agora o objeto contact está com ds_nome=“B” (porque ao gerar o erro, o sistema volta para tela de cadastro com o campo input preenchido). O usário dá update novamente. BINGO! Dessa vez não dá erro nenhum.

Só para constar, não estou usando cache de segundo nível.

O que pode estar acontecendo!?

:roll:

  1. o banco possui um bug e deixa a unique da coluna quebrar. eh isso que esta acontecendo?
  2. o hibernate engole a exception do banco ao alterar tal coluna
  3. o hibernate nao coloca o campo no statement de update

Seria interessante:

a) imprimir o sql gerado pelo hibernate
b) fazer tal sql na mao

Se esse sql gerar erro do banco, esquece (1) (mto provavel, certo?)
Se esse sql nao possui o campo no update (dynamic-update?), entao eh o (3)
Se esse sql possui o campo do update alterando a chave e gera o erro, entao eh (2)

Abraco

Guilherme

Fala Guilherme! Então vamos aos pontos…

1. o banco possui um bug e deixa a unique da coluna quebrar. eh isso que esta acontecendo? 

Esse não é. Qualquer violação de constraint o Postgres avisa na hora para qualquer situação.

 2. o hibernate engole a exception do banco ao alterar tal coluna 

Vamos lá. A explicação desse é um pouco mais extensa.
Imagina que no BD tenho dois registros (só vou colocar id_contato, e ds nome):

id_contato ds_nome

15 fulano
16 siclano

Tenho um form html com os os campos (só vou colocar os principais): id (hidden) e nome(text). Como é um update o id vai estar preenchido com o valor do registro no BD (e.g: 15) e suponhamos que o nome foi alterado com para o valor siclano.

Na minha action preencho um objeto contact (classe Contact) com os respectivos valores. Minha action chama o método update(Contact). O que acontece:

  1. Contact c = (Contact)session.load(Contact.class,contact.getId()); c agora possui os valores que vieram do banco.

  2. BeanUtils.copyProperties(c, contact); Copio os valores que vieram da requisição para c, ou seja a propriedade name foi alterada (fulano->siclano)

  3. Commit. Acontece o esperado. O Hibernate caputra a exceção do banco e gera uma mensagem de violação de constraint. Minha action retorna um ERROR que faz voltar para a tela de cadastro.

No formulário html o id (hidden) ainda estará com o valor 15, já o campo nome estará com o valor siclano (gerador do erro). Daí submeto o form novamente. Agora que acontece o comportamento estranho:

  1. Contact c = (Contact)session.load(Contact.class,contact.getId()); c não traz os valores do banco e sim o valor da última alteração, ou seja, c.id = 15 e c.name = siclano (Pq não trouxe o valor do banco?)

  2. BeanUtils.copyProperties(c, contact); Copio os valores que vieram da requisição para c, neste caso a propriedade name do objeto para o Hibernate não foi alterada.

  3. Commit. Não gera erro nenhum. Pois para o Hibernate não houve alteração no objeto.

No banco a coluna ds_nome do registro com id_contato = 15 continua fulano, porém na aplicação pela listagem do hibernate aparecem dois siclanos. Copio o mesmo select que ele executa e coloco no banco vem tudo certo ((15, fulano),(16,siclano)).

 3. o hibernate nao coloca o campo no statement de update 

Pelo sql que ele gerou. Está colocando o campo direitinho no update. Lembrando que no segundo submit ele não executa update pois para ele não houve alteração no objeto.

E aí pessoal? Alguma idéia!?

É muito parecido com esse provável bug aqui:

http://opensource.atlassian.com/projects/hibernate/browse/HHH-1808

Com umas pequenas diferenças:

  • Com ou sem o ehcache o problema continua
  • não estou usando o executeUpdate (pelo menos não diretamente)

Um problema desse tipo em produção é que deixa o cara maluco :slight_smile:

Encontrei uma coisa interessante na documentação do hibernate:

An exception thrown by Hibernate means you have to rollback your database 
transaction and close the Session immediately (discussed later in more detail). 
If your Session is bound to the application, you have to stop the application. 
Rolling back the database transaction doesn't put your business objects back 
into the state they were at the start of the transaction. 
This means the database state and the business objects do get out of sync. 
Usually this is not a problem, because exceptions are not recoverable and you 
have to start over after rollback anyway.

O trecho “and close the Session immediately” me chamou a atenção. O mentawai SEMPRE fecha a sessão mesmo em caso de exceções geradas pelo Hibernate!? Vou investigar isso. Se alguém já souber responder vai ajudar muito.

" O método afterConsequence será chamado pelo filtro após a execução da consequencia da action. Repare que esse método será executado mesmo que haja um exception e a consequência não seja totalmente executada."

    public void afterConsequence(Action action, Consequence c, boolean conseqExecuted)
    {
        Session session = (Session)hibernateSessionThreadLocal.get();
        if(session != null)
        {
            hibernateSessionThreadLocal.set(null);
            removeValue(hibernateSessionKey);
            session.close();
        }
    }

Acho que o Menta está correto. Será que é mesmo um bug do Hibernate!?

Tá usando a versão 1.4 do menta, a última onde o afterConsequence está sendo usado ?

Estou saoj.

http://www.mentaframework.org/mentawai-1.4.zip

E testei com o beta também.

http://www.mentaframework.org/beta/mentawai.jar

Teria alguma possibilidade do AfterConsequence não está sendo chamado!?

Não. Tem outras pessoas usando isso sem problema. Após a execução do forward (consequencia) a session será fechada.

Acho que tu vai ter que fazer o que o Guilherme falou.

Faz um código isolado, só hibernate que faz isso aí que vc está querendo e tenta entender o que acontece. Bem-vindo ao Hibernate !!!

Eu tenho um projeto rodando a mais de 1 ano com a versão 3.0 e nunca tive problemas. Inventei de baixar a 3.2.0cr2 e tou batendo cabeça. :frowning:

Valeu mesmo pessoal! Vou isolar o problema e os resultados posto aqui. :wink:

Olha eu de novo pessoal!

Só tive tempo de isolar o problema agora. Tenho uma notícia boa, usando somente o Hibernate funciona. :slight_smile:

import org.hibernate.*;
import org.hibernate.cfg.*;
import java.util.*;
import com.dwprototipo.bean.*;
class HibernateUtil {

private static final SessionFactory sessionFactory;

    static {
        try {
            sessionFactory = new AnnotationConfiguration().configure("hibernate.cfg.xml").buildSessionFactory();
        } catch (Throwable ex) {
            // Log exception!
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static Session getSession()
            throws HibernateException {
        return sessionFactory.openSession();
    }
}


public class TestesHibernate {
    public void listarContatos() {
        //Listando contatos atuais
        Session session = HibernateUtil.getSession();
        Transaction tx = session.beginTransaction();
        List<Contact> contacts = session.createQuery("from Contact c").list();
        tx.commit();
        session.close();
        for (Contact c : contacts)
                System.out.println(c);        
    }    
    
    public void updateContato() {
        Session session = HibernateUtil.getSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
            Contact c = (Contact) session.load(Contact.class,1);
            c.setName("siclano");
            tx.commit();
        } catch (Exception e) {
            if (tx != null && tx.isActive())
                tx.rollback();
            e.printStackTrace();
        } finally {
            session.close();
        }
    }
    public static void main (String[] args) {
        TestesHibernate testes = new TestesHibernate();
        
        /*
         *Exibe:
         *ID: 1 Name: fulano
         *ID: 2 Name: siclano
         */
        testes.listarContatos(); 
        
        //Gera um erro de violação de Constraint
        testes.updateContato(); //
        
        //Gera um erro de violação de Constraint        
        testes.updateContato();
        
        /*
         *Exibe:
         *ID: 1 Name: fulano
         *ID: 2 Name: siclano
         */        
        testes.listarContatos();
    }
}

Agora vou tentar descobrir onde está o problema na integração com o Mentawai. :wink:

Descobriiiiiii!!!

O Mentawai não está chamando o método afterConsequence do HibernateFilter.

Alterei a classe do Mentawai para exibir na tela mensagem de abertura e fechamento da sessão que constatei que ele só faz a abertura mas não o fechamento.

Se não usar o HibernateFilter e controlar a sessão manualmente como fiz no exemplo acima funciona normalmente.

Espero ter ajudado :wink:

[size=28][color=red]Putz !!![/color][/size]

Posta o seu ApplicationManager lá no site do mentawai urgente…

Está lá :wink:

http://forum.mentaframework.org/posts/list/0/383.page#2638

jar corrigido em:

http://www.mentaframework.org/beta/mentawai.jar