Skip to content


Suposto bug com versionamento do Hibernate3

Na última semana descobri um problema (pra mim é um bug) no Hibernate3 que me deixou muito confuso por algumas horas.

Eu tenho uma entidade do Hibernate chamada Pessoa, que é usada em vários outros sistemas (afinal, todos os sistemas possuem alguma associação com pessoas).

Tudo estava funcionando perfeitamente, até que surgiu a idéia de implementar versioning for optimistic locking nesta classe (se você não conhece como funciona o versionamento do Hibernate, clique aqui). Foi muito simples, apenas adicionei @Version em um atributo do tipo Date e as instâncias ficaram protegidas de alterações concorrentes.

O problema é que, depois dessa alteração, todos os sistemas começaram apresentar problemas em telas variadas. A solução (depois de quebrar muito a cabeça) foi não usar versionamento para esta entidade. Eu sei que na verdade não foi uma solução, mas resolveu o problema. E nessas horas o difícil é imaginar que um simples @Version gerou tudo isso.

Para tentar exemplificar o problema, criei duas entidades chamadas Customer e Invoice, como seguem:

@Entity

@Table

public class Customer implements Serializable {
    @Id

    @GeneratedValue

    private Long id;    private String name;
    @Version

    @Column(name="update_date")

    private Date updateDate;
    //getters and setters
}

@Entity

@Table

public class Invoice implements Serializable {
    @Id

    @GeneratedValue

    private Long id;

    private Integer number;

    private BigDecimal amount;
    //getters and setters
}

Veja que o atributo updateDate da classe Customer está anotada com @Version.

Criei um método com o seguinte código para testar o comportamento do Hibernate com @Version:

Session session = sessionFactory.getCurrentSession();

Transaction transaction = session.beginTransaction();Customer customer = new Customer();

customer.setId(1L);
Invoice invoice = new Invoice();

invoice.setNumber(1234);

invoice.setAmount(new BigDecimal(4500.19));

invoice.setCustomer(customer);session.merge(invoice);

transaction.commit();

Considere o customer de código 1 já cadastrado no banco de dados.
Agora tente adivinhar o resultado da execução desse código. Pra mim deveria funcionar, pois o merge() deveria tornar o meu objeto customer persistente, mas infelizmente é lançada a seguinte exceção:

org.hibernate.TransientObjectException: object references an unsaved transient instance – save the transient instance before flushing: example.Customer

O engraçado é que se incluirmos a seguinte linha depois de atribuir o código 1 ao id de customer, funciona:

customer.setUpdateDate(new Date(1, 1, 1, 1, 1, 1));

Veja que eu defini uma data de alteração (que nem faz sentido) ao objeto, e agora funciona perfeitamente, gerando o seguinte SQL:

Hibernate: select customer0_.id as id0_0_, customer0_.name as name0_0_, customer0_.update_date as update3_0_0_ from Customer customer0_ where customer0_.id=?

Hibernate: insert into Invoice (amount, customer_id, number) values (?, ?, ?)

Se comentarmos o @Version do atributo updateDate da classe Customer e também a atribuição da data de alteração de Customer, funcionará corretamente também, pois aí não estaria mais utilizando versionamento.

Se quiser fazer outros testes, baixe do código-fonte do projeto de exemplo clicando aqui.

E pra você, isso é um bug? Será que a especificação do JPA contempla isso?
Assim que tiver tempo, irei traduzir esse post e cadastrar no JIRA do Hibernate.

Abraços,

Posted in Hibernate, Java.

Tagged with , , , , .


11 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Tetsuo says

    Tentou recarregar o Customer do banco ao invés de reinstanciá-lo?

    Outra possível solução seria mapear ‘customer’ com cascade=ALL ou MERGE.

    Talvez isto não seja um bug, mas sim o contrário, o Hibernate que estava deixando código incorreto ser executado, antes de você colocar o @Version. (talvez)

  2. Marcos Silva Pereira says

    Para mim é um bug.

    Vale uma busca (no fórum também) antes de cadastrar a issue.

    valeuz…

  3. Thiago Faria de Andrade says

    Tetsuo,
    Se mapear com CascadeType.ALL ou MERGE, ele tenta inserir o customer (mesmo com o id diferente de null).
    Se recarregar o customer “manualmente” funciona, mas não queria fazer isso, pois no meu caso, tenho muito código que não recarrega as instâncias (e espera que o merge faça isso).

  4. Bruno Borges says

    Sei não… Acho que não é bug. Minha suspeita é de que como o [i]versioning[/i] é feito com o atributo [i]updateDate[/i], na hora do merge espera-se que este campo esteja preenchido, para haver o optimistic locking, comparando esta coluna com o registro existente, caso exista.

    De fato, o versioning geralmente eh feito utilizando-se uma coluna de numero inteiro, que o valor default eh sempre 1. Ou seja, nunca está nulo.

    []’s
    miojo

  5. Normandes Junior says

    Thiago,

    Também acho que é um bug. Porém somente quando o atributo mapeado é um Date. Se olhar na API verá que para utilizar @Version as propriedades suportadas são: int (Integer), short (Short), long (Long) e Timestamp (que é um Date).

    Se você fizer o teste com Integer, por exemplo, verá que vai funcionar perfeitamente.

    Também acho interessante você postar este bug no JIRA do Hibernate, mas destacando que o problema existe com atributos mapeados como Timestamp.

  6. Daniel F. Martins says

    Opa!

    Nunca vi algo assim acontecer, até porque eu sempre uso a anotação @Version em campos Integer. Apesar da anotação @Version funcionar também em Timestamps, o próprio manual do Hibernate recomenda a utilização de um tipo numérico, tal como o Integer.

    Mas, de qualquer forma, também acredito que o que foi relatado no seu post representa sim um bug!

    Até mais.

  7. Helder says

    Pode ser por causa disso:

    Link direto do site Hibernate

    Em 5.1.7. version (optional)

    A version or timestamp property should never be null for a detached instance, so Hibernate will detact any instance with a null version or timestamp as transient, no matter what other unsaved-value strategies are specified. Declaring a nullable version or timestamp property is an easy way to avoid any problems with transitive reattachment in Hibernate, especially useful for people using assigned identifiers or composite keys!

  8. Tetsuo says

    Bem, eu estava fazendo uns testes, e consegui reproduzir o erro. Depois de ver o comentário do Helder, tentei simplesmente declarar o atributo updateDate já atribuindo new Date() e funcionou. Tipo

    @Version private Date updateDate = new Date();

    Colocar @Basic(optional=true) – como eu entendi que a documentação sugere – não funcionou.

    Eu continuo achando que o correto é carregar o objeto antes de usar, ao invés de instanciar um novo, mas se funciona…

  9. Daniel F. Martins says

    Ronald,

    E com @Column(nullable=true)? (embora eu pense que ambas as formas devem significar a mesma coisa).

    Não tenho como testar aqui, senão eu já verificava se isso funcionaria! :)

  10. Diego Pires Plentz says

    Thiago,

    Não cheguei a ler o post inteiro e testar, mas se chegar a conclusão que realmente é um bug, por favor, não esqueça de abrir a issue no JIRA. E por favor, procure antes se já não existe uma cadastrada com o mesmo problema, anexando um teste executável(java+mapping), pra facilitar nosso trabalho ;)

    Att.

Continuing the Discussion

  1. Blog do Márcio d’Ávila » Curtas Java linked to this post on 8 de novembro de 2007

    [...] 5, Tiago Faria postou Suposto bug com versionamento do Hibernate3 no lock otimista. Talvez seja só uma dificuldade ou mau entendimento no uso do recurso, mas não [...]



Some HTML is OK

or, reply to this post via trackback.