2012-06-13 15 views
10

Tengo una BaseEntity que abstrae la propiedad del id y de la versión. esta clase también implementa hashcode e igual basado en la propiedad PK (id).Hibernate equals y proxy

BaseEntity{ 

    Long id; 
    Long version; 

public int hashCode() { 
    final int prime = 31; 
    int result = 1; 
    result = prime * result + ((id == null) ? 0 : id.hashCode()); 
    return result; 
} 

public boolean equals(Object obj) { 
    if (this == obj) 
     return true; 
    if (obj == null) 
     return false; 
    if (getClass() != obj.getClass()) 
     return false; 
    BaseEntity other = (BaseEntity) obj; 
    if (id == null) { 
     if (other.id != null) 
      return false; 
    } else if (!id.equals(other.id)) 
     return false; 
    return true; 
} 


} 

ahora dos entidad A y B se extiende BaseEntity como abajo

A extends BaseEntity{ 
    `B b` 
    B getB(){return b;) 
    void setB(B b){this.b=b;} 
} 

B extends BaseEntity{ 
} 

object b1; 
object a1; 
a1.set(b1); 
session.save(a1) //cascade save; 

cerca sesión carga una con perezoso b y tratar a1.getB(). Equals (b1) da falso pero si i compárese con a1.getB(). getId(). equals (b1.getId()) ¡entonces da verdadero extraño! Creo que es debido a objeto auxiliar de java assist, de todos modos para resolver esto?

Respuesta

15

Para poder cargar la asociación a.b, Hibernate configura el campo b en a en un proxy. El proxy es una instancia de una clase que extiende B, pero no es B. Por lo tanto, su método equals() siempre fallará al comparar una instancia B no proxy con una instancia proxy B, porque compara las clases de ambos objetos:

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

En el caso de entidades de Hibernate, debe reemplazar esto con

if (!(obj instanceof B)) { 
    return false; 
} 

Además, tenga en cuenta que

  • Hibernate no recomienda implementar equals() y hashCode() mediante el uso de la identificación, sino más bien mediante el uso de un identificador natural. Implementarlo con ID puede causar problemas porque las entidades no tienen una ID hasta que se guardan y se genera la ID
  • Al usar la herencia de la entidad, el problema es aún peor. Supongamos que B es una superclase de dos subentidades B1 y B2. Hiberante no puede saber qué tipo (B1 o B2) es a.b antes de cargarlo. Por lo tanto, a.b se inicializará a un proxy que es una subclase de B, pero no es una subclase de B1 o B2. Por lo tanto, los métodos hashCode() y equals() deben implementarse en B, pero no deben anularse en B1 y B2. Dos instancias B deben considerarse iguales si son instancias de B y tienen el mismo identificador.
+0

Gracias, lo tengo. acuerde con usted los problemas que pueden surgir debido a equals() y hashcode() basados ​​en Id. Creo que ahora voy a ir con la opción de instancia, ya que la introducción de la identificación natural en esta etapa de la aplicación sería difícil. –

-1

Esto es principalmente un efecto de la herencia estándar de Java.

a1.getB().equals(b1) usa Object.equals() (excepto si ha anulado equals() en su clase), que solo devuelve verdadero si a1.getB() y b1 son la misma instancia. No sé qué ha hecho exactamente (el formato del código está roto), pero parece que ha cargado a nuevamente en una sesión diferente, por lo que obtiene una nueva instancia para a y a.getB(), y en consecuencia Object.equals() devuelve falso .

a1.getB().getId().equals(b1.getId()) usa Long.equals(), que devuelve verdadero si los valores largos son los mismos (incluso para diferentes instancias del objeto Long), y estos valores son obviamente los mismos.

+0

formato de código corregido, según mi código que creo que debería invocar iguales en '' BaseEntity' en lugar de Object.equals() ', actualmente se está invocando' Object.equals () 'no estoy seguro de por qué –

+0

¿Puede agregar el código para A.getB() (¿qué tipo?), BaseEntity.equals() y para el mapeo de A (al menos id y el miembro b)? Quizás entonces puedo ver más. – Johanna

+0

código actualizado para getter setter. –

1

También puede hacer que funciona de esta manera, muy útil si usted no sabe instancia cuyo está B (que puede suceder si su equals está en una superclase)

if (HibernateProxyHelper.getClassWithoutInitializingProxy(this) != HibernateProxyHelper.getClassWithoutInitializingProxy(obj)) 
    return false 
+0

Por favor vea mi comentario a la respuesta de Ralph. También está acoplando sus clases de dominio con la dependencia de Hibernate. –

8

utilizo Hibernate.getClass durante muchos años y nunca he notado un problema:

@Override  
public boolean equals(final Object obj) { 
    if (this == obj) { 
     return true; 
    } 
    if (obj == null) { 
     return false; 
    } 
    if (Hibernate.getClass(this) != Hibernate.getClass(obj)) { 
     return false; 
    } 

    ... check for values 

    return true; 
} 
+3

Excepto que las clases de tu dominio ahora apenas están acopladas con Hibernate. Asuma que quiere compartir su dominio con alguna otra aplicación que no use hibernate en absoluto ... ahora está en un problema. No me malinterpretes, funcionará, pero huele muy mal. Además, si los chicos de hibernación deciden mover \ eliminar la utilidad de proxy y actualizar la hibernación - el dominio ya no se compilará. –