Como deve ser implementado equals e hashcode ao usar JPA e Hibernate

103

Como o equals e o hashcode da classe do modelo devem ser implementados no Hibernate? Quais são as armadilhas comuns? A implementação padrão é boa o suficiente para a maioria dos casos? Há algum sentido em usar chaves comerciais?

Parece-me muito difícil fazer funcionar corretamente em todas as situações, quando o lazy fetching, geração de id, proxy, etc. são levados em consideração.

egaga
fonte
Consulte também stackoverflow.com/a/39827962/548473 (implementação de spring-data-jpa)
Grigory Kislin

Respostas:

74

O Hibernate tem uma boa e longa descrição de quando / como substituir equals()/ hashCode()na documentação

A essência disso é que você só precisa se preocupar se sua entidade fará parte de um Setou se você for desanexar / anexar suas instâncias. Este último não é tão comum. O primeiro geralmente é mais bem tratado por meio de:

  1. Baseando-se equals()/ hashCode()em uma chave de negócios - por exemplo, uma combinação única de atributos que não vai mudar durante a vida útil do objeto (ou, pelo menos, da sessão).
  2. Se o acima for impossível, baseie equals()/ hashCode()na chave primária SE estiver definida e na identidade do objeto / System.identityHashCode()caso contrário. A parte importante aqui é que você precisa recarregar seu Set após a nova entidade ter sido adicionada a ele e persistida; caso contrário, você pode acabar com um comportamento estranho (em última análise, resultando em erros e / ou corrupção de dados) porque sua entidade pode ser alocada para um intervalo que não corresponde ao seu atual hashCode().
ChssPly76
fonte
1
Quando você diz "recarregar" @ ChssPly76, quer dizer fazer um refresh()? Como sua entidade, que obedece ao Setcontrato, acaba no balde errado (supondo que você tenha uma implementação de hashcode boa o suficiente).
non sequitor
4
Atualize a coleção ou recarregue toda a entidade (proprietário), sim. No que diz respeito ao intervalo errado: a) você adiciona uma nova entidade para definir, seu id ainda não está definido, então você está usando identityHashCode que coloca sua entidade no intervalo # 1. b) sua entidade (dentro do conjunto) é persistida, agora ela tem um id e, portanto, você está usando hashCode () com base nesse id. É diferente do anterior e teria colocado sua entidade no balde # 2. Agora, supondo que você tenha uma referência a essa entidade em outro lugar, tente ligar Set.contains(entity)e você retornará false. O mesmo vale para get () / put () / etc ...
ChssPly76
Faz sentido, mas nunca usei o IdentityHashCode, embora eu o veja usado na fonte do Hibernate como em seus ResultTransformers
non sequitor
1
Ao usar o Hibernate, você também pode encontrar esse problema , para o qual ainda não encontrei uma solução.
Giovanni Botta de
@ ChssPly76 Devido às regras de negócios que determinam se dois objetos são iguais, precisarei basear meus métodos equals / hashcode nas propriedades que podem mudar durante a vida útil de um objeto. Isso é realmente um grande negócio? Se sim, como faço para contornar isso?
ubiquibacon
39

Não acho que a resposta aceita seja correta.

Para responder à pergunta original:

A implementação padrão é boa o suficiente para a maioria dos casos?

A resposta é sim, na maioria dos casos é.

Você só precisa substituir equals()e hashcode()se a entidade for usada em um Set(o que é muito comum) E a entidade será desanexada e posteriormente reconectada às sessões de hibernação (que é um uso incomum de hibernação).

A resposta aceita indica que os métodos precisam ser substituídos se qualquer uma das condições for verdadeira.

Phil
fonte
Isso se alinha com a minha observação, hora de descobrir o porquê .
Vlastimil Ovčáčík
"Você só precisa substituir equals () e hashcode () se a entidade for usada em um Conjunto" é completamente o suficiente se alguns campos identificam um objeto e, portanto, você não quer depender de Object.equals () para identificar objetos.
davidxxx
17

A melhor equals/ hashCodeimplementação é quando você usa uma chave comercial exclusiva .

A chave de negócios deve ser consistente em todas as transições de estado da entidade (transiente, anexado, desanexado, removido), é por isso que você não pode contar com id para igualdade.

Outra opção é passar a usar identificadores UUID , atribuídos pela lógica do aplicativo. Dessa forma, você pode usar o UUID para o equals/ hashCodeporque o id é atribuído antes que a entidade seja liberada.

Você pode até usar o identificador de entidade para equals e hashCode, mas isso exige que você sempre retorne o mesmo hashCodevalor para que você tenha certeza de que o valor hashCode da entidade é consistente em todas as transições de estado da entidade. Confira esta postagem para mais informações sobre este tópico .

Vlad Mihalcea
fonte
1 para a abordagem uuid. Coloque isso em um BaseEntitye nunca mais pense sobre esse problema. É preciso um pouco de espaço do lado do banco de dados, mas é melhor pagar esse preço pelo conforto :)
Martin Frey
12

Quando uma entidade é carregada por meio de carregamento lento, não é uma instância do tipo base, mas é um subtipo gerado dinamicamente gerado por javassist, portanto, uma verificação no mesmo tipo de classe falhará, portanto, não use:

if (getClass() != that.getClass()) return false;

em vez disso, use:

if (!(otherObject instanceof Unit)) return false;

que também é uma boa prática, conforme explicado em Implementando equals no Java Practices .

pelo mesmo motivo, acessar diretamente os campos pode não funcionar e retornar nulo, em vez do valor subjacente, portanto, não use a comparação nas propriedades, mas use os getters, pois eles podem disparar para carregar os valores subjacentes.

Stivlo
fonte
1
Isso funciona se você estiver comparando objetos de classes concretas, o que não funcionou na minha situação. Eu estava comparando objetos de superclasses, caso em que este código funcionou para mim: obj1.getClass (). IsInstance (obj2)
Tad
6

Sim, é difícil. Em meu projeto, equals e hashCode contam com o id do objeto. O problema dessa solução é que nenhuma delas funciona se o objeto ainda não foi persistido, pois o id é gerado pelo banco de dados. No meu caso, isso é tolerável, pois em quase todos os casos os objetos são persistidos imediatamente. Fora isso, funciona muito bem e é fácil de implementar.

Carlos
fonte
O que acho que fizemos foi usar a identidade do objeto no caso em que o id não foi gerado
Kathy Van Stone
2
o problema aqui é que se você persistir no objeto, seu código hash muda. Isso pode ter grandes resultados prejudiciais se o objeto já fizer parte de uma estrutura de dados baseada em hash. Portanto, se você acabar usando a identidade do objeto, é melhor continuar usando obj id até que o objeto seja completamente liberado (ou remova o objeto de qualquer estrutura baseada em hash, persista e, em seguida, adicione-o novamente). Pessoalmente, acho que seria melhor não usar id e basear o hash nas propriedades imutáveis ​​do objeto.
Kevin Day
1

Na documentação do Hibernate 5.2 diz que você pode não querer implementar hashCode e equals - dependendo da sua situação.

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

Geralmente, dois objetos carregados da mesma sessão serão iguais se forem iguais no banco de dados (sem implementar hashCode e equals).

Fica complicado se você estiver usando duas ou mais sessões. Nesse caso, a igualdade de dois objetos depende da implementação do método igual.

Além disso, você terá problemas se seu método equals estiver comparando IDs que são gerados apenas durante a persistência de um objeto pela primeira vez. Eles podem não estar lá ainda quando igual é chamado.

Nina
fonte
0

Há um artigo muito bom aqui: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Citando uma linha importante do artigo:

Recomendamos implementar equals () e hashCode () usando a igualdade de chave de negócios. Igualdade de chave de negócios significa que o método equals () compara apenas as propriedades que formam a chave de negócios, uma chave que identificaria nossa instância no mundo real (uma chave candidata natural):

Em termos simples

public class Cat {

...
public boolean equals(Object other) {
    //Basic test / class cast
    return this.catId==other.catId;
}

public int hashCode() {
    int result;

    return 3*this.catId; //any primenumber 
}

}
Ravi Shekhar
fonte
0

Se acontecer de você substituir equals, certifique-se de cumprir seus contratos: -

  • SIMETRIA
  • REFLETIVO
  • TRANSITIVO
  • CONSISTENTE
  • NÃO NULO

E substituir hashCode, já que seu contrato depende da equalsimplementação.

Joshua Bloch (designer do framework Collection) recomendou enfaticamente que essas regras fossem seguidas.

  • item 9: Sempre substitui hashCode quando você substitui igual a

Existem efeitos não intencionais graves quando você não segue esses contratos. Por exemplo, List#contains(Object o)pode retornar um booleanvalor incorreto porque o contrato geral não foi cumprido.

Awan Biru
fonte