¿Cómo se deben implementar los valores iguales y el código hash al usar JPA e Hibernate?

103

¿Cómo se deben implementar los valores iguales y el código hash de la clase modelo en Hibernate? ¿Cuáles son los errores comunes? ¿Es la implementación predeterminada lo suficientemente buena para la mayoría de los casos? ¿Tiene sentido utilizar claves comerciales?

Me parece que es bastante difícil hacerlo bien para que funcione en todas las situaciones, cuando se tienen en cuenta la búsqueda diferida, la generación de identificaciones, el proxy, etc.

egaga
fuente
Consulte también stackoverflow.com/a/39827962/548473 (implementación de spring-data-jpa)
Grigory Kislin

Respuestas:

74

Hibernate tiene una descripción larga y agradable de cuándo / cómo anular equals()/ hashCode()en la documentación

La esencia de esto es que solo debe preocuparse por si su entidad será parte de una Seto si va a separar / adjuntar sus instancias. Este último no es tan común. El primero generalmente se maneja mejor a través de:

  1. Basado equals()/ hashCode()en una clave de negocio - por ejemplo, una combinación única de atributos que no va a cambiar durante la vida útil del objeto (o, al menos, la sesión).
  2. Si lo anterior es imposible, base equals()/ hashCode()en la clave principal SI está configurada y la identidad del objeto / de lo System.identityHashCode()contrario. La parte importante aquí es que debe volver a cargar su conjunto después de que se le haya agregado y persistido una nueva entidad; de lo contrario, puede terminar con un comportamiento extraño (que en última instancia resultará en errores y / o corrupción de datos) porque su entidad puede estar asignada a un depósito que no coincide con su actual hashCode().
ChssPly76
fuente
1
Cuando dices "recargar" @ ChssPly76 te refieres a hacer un refresh()? ¿Cómo termina su entidad, que obedece el Setcontrato, en el depósito equivocado (suponiendo que tenga una implementación de código hash suficientemente buena)?
no secuente
4
Actualizar la colección o recargar toda la entidad (propietario), sí. En lo que respecta al depósito incorrecto: a) agrega una nueva entidad para configurar, su identificación aún no está configurada, por lo que está utilizando identityHashCode que coloca su entidad en el depósito n. ° 1. b) su entidad (dentro del conjunto) persiste, ahora tiene una identificación y, por lo tanto, está utilizando hashCode () en función de esa identificación. Es diferente de lo anterior y habría colocado su entidad en el cubo # 2. Ahora, suponiendo que tenga una referencia a esta entidad en otro lugar, intente llamar Set.contains(entity)y volverá false. Lo mismo ocurre con get () / put () / etc ...
ChssPly76
Tiene sentido, pero nunca usé identityHashCode, aunque lo veo usado en la fuente de Hibernate como en su ResultTransformers
non sequitor
1
Al usar Hibernate, también podría encontrarse con este problema , al que todavía no he encontrado una solución.
Giovanni Botta
@ ChssPly76 Debido a las reglas comerciales que determinan si dos objetos son iguales, tendré que basar mis métodos equals / hashcode en propiedades que pueden cambiar durante la vida útil de un objeto. ¿Es eso realmente un gran problema? Si es así, ¿cómo puedo solucionarlo?
ubiquibacon
39

No creo que la respuesta aceptada sea precisa.

Para responder a la pregunta original:

¿Es la implementación predeterminada lo suficientemente buena para la mayoría de los casos?

La respuesta es sí, en la mayoría de los casos lo es.

Solo necesita anular equals()y hashcode()si la entidad se usará en un Set(que es muy común) Y la entidad se separará y, posteriormente, se volverá a adjuntar a sesiones de hibernación (que es un uso poco común de hibernación).

La respuesta aceptada indica que los métodos deben anularse si alguno condiciones es verdadera.

Phil
fuente
Esto se alinea con mi observación, es hora de averiguar por qué .
Vlastimil Ovčáčík
"Solo necesita anular equals () y hashcode () si la entidad se usará en un conjunto" es completamente suficiente si algunos campos identifican un objeto, por lo que no desea confiar en Object.equals () para identificar objetos.
davidxxx
17

La mejor equals/ hashCodeaplicación es cuando se utiliza una clave de negocio único .

La clave comercial debe ser coherente en todas las transiciones de estado de la entidad (transitoria, adjunta, separada, eliminada), por eso no puede confiar en la identificación para la igualdad.

Otra opción es cambiar al uso de identificadores UUID , asignados por la lógica de la aplicación. De esta forma, puede utilizar el UUID para equals/hashCode porque la identificación se asigna antes de que la entidad se vacíe.

Incluso puede usar el identificador de entidad para equalsy hashCode, pero eso requiere que siempre devuelva el mismo hashCodevalor para asegurarse de que el valor de la entidad hashCode sea coherente en todas las transiciones de estado de entidad. Consulte esta publicación para obtener más información sobre este tema .

Vlad Mihalcea
fuente
+1 para el acercamiento uuid. Pon eso en un BaseEntityy no vuelvas a pensar en ese problema. Se necesita un poco de espacio en el lado de la base de datos, pero es mejor que pague ese precio por la comodidad :)
Martin Frey
12

Cuando una entidad se carga a través de la carga diferida, no es una instancia del tipo base, sino un subtipo generado dinámicamente generado por javassist, por lo tanto, una verificación en el mismo tipo de clase fallará, así que no use:

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

en su lugar use:

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

que también es una buena práctica, como se explica en Implementación de iguales en Prácticas Java .

por la misma razón, al acceder directamente a los campos, puede que no funcione y devuelva un valor nulo, en lugar del valor subyacente, por lo que no use la comparación en las propiedades, sino use los captadores, ya que pueden desencadenarse para cargar los valores subyacentes.

stivlo
fuente
1
Esto funciona si está comparando objetos de clases concretas, lo que no funcionó en mi situación. Estaba comparando objetos de superclases, en cuyo caso este código funcionó para mí: obj1.getClass (). IsInstance (obj2)
Tad
6

Sí, es difícil. En mi proyecto, equals y hashCode se basan en la identificación del objeto. El problema de esta solución es que ninguno de ellos funciona si el objeto aún no se ha persistido, ya que la identificación la genera la base de datos. En mi caso, eso es tolerable ya que en casi todos los casos los objetos persisten de inmediato. Aparte de eso, funciona muy bien y es fácil de implementar.

Carlos
fuente
Lo que creo que hicimos fue usar la identidad del objeto en el caso de que no se haya generado la identificación
Kathy Van Stone
2
el problema aquí es que si persiste el objeto, su código hash cambia. Eso puede tener grandes resultados perjudiciales si el objeto ya es parte de una estructura de datos basada en hash. Por lo tanto, si termina usando la identidad del objeto, será mejor que continúe usando obj id hasta que el objeto esté completamente liberado (o elimine el objeto de cualquier estructura basada en hash, persista y luego vuelva a agregarlo). Personalmente, creo que sería mejor no usar id y basar el hash en propiedades inmutables del objeto.
Kevin Day
1

En la documentación de Hibernate 5.2, dice que es posible que no desee implementar hashCode y es igual en absoluto, dependiendo de su situación.

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

Generalmente, dos objetos cargados desde la misma sesión serán iguales si son iguales en la base de datos (sin implementar hashCode y equals).

Se complica si usa dos o más sesiones. En este caso, la igualdad de dos objetos depende de la implementación del método igual.

Además, se meterá en problemas si su método equals está comparando ID que solo se generan mientras persiste un objeto por primera vez. Es posible que aún no estén allí cuando se llame a iguales.

Nina
fuente
0

Hay un artículo muy bueno aquí: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Citando una línea importante del artículo:

Recomendamos implementar equals () y hashCode () utilizando la igualdad de claves comerciales. La igualdad de clave empresarial significa que el método equals () compara solo las propiedades que forman la clave empresarial, una clave que identificaría nuestra instancia en el mundo real (una clave candidata natural):

En lenguaje sencillo

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
fuente
0

Si anuló equals, asegúrese de cumplir con sus contratos: -

  • SIMETRÍA
  • REFLEXIVO
  • TRANSITIVO
  • CONSISTENTE
  • NO NULO

Y anular hashCode, ya que su contrato depende de la equalsimplementación.

Joshua Bloch (diseñador del marco de la colección) instó encarecidamente a seguir estas reglas.

  • elemento 9: anule siempre hashCode cuando anule igual

Hay efectos no deseados graves cuando no sigue estos contratos. Por ejemplo, List#contains(Object o)podría devolver un booleanvalor incorrecto ya que el contrato general no se cumplió.

Awan Biru
fuente