Como devem ser equals e hashcode implementados ao usar o JPA e o Hibernate

Como devem ser equacionados os iguais e hashcode da class no Hibernate? Quais são as armadilhas comuns? A implementação padrão é boa o suficiente para a maioria dos casos? Existe algum sentido para usar chaves de negócios?

Parece-me que é muito difícil fazer tudo certo para funcionar em todas as situações, quando a busca preguiçosa, geração de id, proxy, etc são levados em conta.

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

A essência disso é que você só precisa se preocupar com isso se a sua entidade fizer parte de um Set ou se você estiver desanexando / anexando suas instâncias. Este último não é tão comum. O primeiro geralmente é melhor manipulado por meio de:

  1. Baseando equals() / hashCode() em uma chave comercial – por exemplo, uma combinação exclusiva de atributos que não serão alterados durante a vida útil do object (ou, pelo menos, da session).
  2. Se o acima for impossível, base equals() / hashCode() na chave primária SE é setado e object identity / System.identityHashCode() caso contrário. A parte importante aqui é que você precisa recarregar seu Set depois que uma nova entidade for adicionada a ele e persistir; caso contrário, você pode acabar com um comportamento estranho (resultando em erros e / ou corrupção de dados), pois sua entidade pode ser alocada para um bucket que não corresponda ao seu atual hashCode() .

Eu não acho que a resposta aceita seja correta.

Para responder a 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 sobrescrever equals() e hashcode() se a entidade for usada em um Set (que é muito comum) E a entidade será desconectada e subseqüentemente reconectada a sessões de hibernação (que é um uso incomum). de hibernação).

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

Quando uma entidade é carregada por meio de carregamento lento, ela não é uma instância do tipo base, mas é um subtipo gerado dinamicamente gerado pelo javassist, portanto, uma verificação no mesmo tipo de class 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 iguais em Práticas de Java .

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

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

A chave de negócios deve ser consistente em todas as transições de estado de entidade (transitória, anexada, desanexada, removida), por isso você não pode confiar em id para igualdade.

Outra opção é mudar para o uso de identificadores de UUID , atribuídos pela lógica do aplicativo. Dessa forma, você pode usar o UUID para o equals / hashCode porque 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 valor de hashCode para garantir que o valor de hashCode da entidade seja consistente em todas as transições de estado de entidade. Confira este post para mais sobre este tópico .

Sim, é dificil. No meu projeto igual e hashCode ambos dependem do id do object. O problema desta solução é que nenhum deles funciona se o object ainda não foi persistido, pois o id é gerado pelo database. No meu caso, isso é tolerável, pois em quase todos os casos os objects são persistidos imediatamente. Fora isso, funciona muito bem e é fácil de implementar.

Se você substituiu os equals , certifique-se de cumprir seus contratos:

  • SIMETRIA
  • REFLEXIVO
  • TRANSITIVO
  • CONSISTENTE
  • NON NULL

E replace hashCode , como seu contrato depende de implementação equals .

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

  • item 9: Sempre substitua o hashCode quando você sobrescreve

Há sérios efeitos indesejados quando você não segue esses contratos. Por exemplo, List.contains(Object o) pode retornar um valor boolean incorreto, pois o contrato geral não foi atendido.

Na documentação do Hibernate 5.2 ele diz que você pode não querer implementar hashCode e igual a todos – 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 objects carregados da mesma session serão iguais se forem iguais no database (sem implementar hashCode e iguais).

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

Além disso, você terá problemas se seu método equals estiver comparando IDs que são gerados apenas enquanto persistem um object pela primeira vez. Eles podem não estar lá ainda quando os iguais são chamados.

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

Citando uma linha importante do artigo:

Recomendamos implementar equals () e hashCode () usando 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 identifica 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 } }