El método equals(Object) es de gran importancia al usar las colecciones en Java. En la mayoría de las clases de colecciones Java es importante decidir si dos objetos son iguales y para ello se usa el método equals(Object). En Hibernate tiene aun mas importancia debido a que realmente dos objetos son iguales si hacen referencia a las misma fila de la base de datos aunque los objetos sean distintos. En este tema veremos como implementar correctamente el método equals(Object) y el método hashCode(), ya que éste último está íntimamente relacionado con equals(Object).
Como ya hemos comentado , al implementar los dos métodos debemos tener en cuenta que para Hibernate dos objetos serán iguales si hacen referencia a la misma fila de la base de datos. Esto va a hacer que no haya una solución fácil para implementar ambos métodos. Veamos algunas soluciones y sus problemas.
Una solución fácil sería comparan las claves primarias de los 2 objetos.
En la siguiente unidad está el tema dedicado a las Claves primarias, ahí veremos como hacer que se generen automáticamente pero con el problema de que se hace cuando se va a guardar el objeto, así que la solución de comparar las claves primarias no es buena ya que para nuevos objetos no podríamos compararlos aunque realmente hicieran referencia a la misma fila.
En el tema Claves primarias también hablaremos de las claves primarias naturales, también llamadas claves de negocio, como son el DNI, el login o el Código Cuenta Cliente. Aunque estas claves no son adecuadas para ser claves primarias si que nos resultarán útiles para ser usadas en los métodos equals(Object) y hashCode().
Las claves primarias naturales ,tienen la caracteristica de que hacen referencia a una única fila de la base de datos y por regla general se sabe su valor antes de crear la fila en la base de datos por lo que dos objetos que hagan referencia a los mismos datos nunca serán iguales.
¿Cual es el problema de las claves primarias naturales? Que quizás la entidad de Hibernate no posea una clave primaria natural con lo que esta técnica no funcionará, aun así es la mejor opción que tenemos 1)
Una última opción es simplemente no hacer nada. Si nuestra aplicación va a tener el cuidado de no añadir el mismo objeto dos veces a una colección o nunca va a hacer un equals
sino que es suficiene con ==
, etc, etc, entonces no es necesario hacer nada.
Para implementar los métodos equals
y hashCode
vamos a usar como ejemplo la clase Usuario
. Esta clase tiene el siguiente código Java:
public class Usuario implements Serializable { private int idUsuario; private String login; private String nombre; private String ape1; private String ape2; private String password; public Usuario() { } public Usuario(String login, String nombre, String ape1, String ape2, String password) { this.login = login; this.nombre = nombre; this.ape1 = ape1; this.ape2 = ape2; this.password = password; } @Override public String toString() { return getLogin() + "-" + getNombre() + " " + getApe1() + " " + getApe2(); } }
Usuario
no se han incluido los métodos get/set de cada propiedad para facilitar la lectura, pero deben estar en la clase Java.
El diagrama UML es el siguiente:
La clase Usuario
tiene la característica de que la propiedad login
es una clave primaria natural y tener valor antes de guardarse en la base de datos y cada valor de login
hace referencia una única fila de la base de datos, por lo tanto es una opción correcta para usarlo en los métodos equals
y hashCode
.
Veamos ahora como se implementan ambos métodos:
@Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (Hibernate.getClass(this) != Hibernate.getClass(obj)) { return false; } Usuario usuario = (Usuario) obj; Object dato1=getLogin() ; Object dato2=usuario.getLogin(); if (dato1 == null) { if (dato2==null) { return true; } else { return false; } } else if (dato1.equals(dato2) == true) { return true; } else { return false; } }
Pasemos ahora a explicar como funciona el método:
true
.null
, nunca serán iguales ya que éste no lo es, entonces retornamos false
.false
.login
.null
los objetos serán iguales y retornamos true
en otro caso es que son distintos y entonces retornamos false
.instanceof
sin embargo hay otra opción que es usar getClass()
. En la siguiente entrevista a Joshua Bloch instanceof versus getClass in equals Methods justifica su decisión indicando que una subclase de la clase también podría ser igual si tienen los mismos valores.
Mi opinión personal es que usando Hibernate al hacer una subclase lo normal es que se añadan campos , por lo tanto estaremos haciendo referencia a mas filas de la base de datos, por ello es preferible usar getClass()
. El problema de usar getClass()
es que Hibernate puede usar objetos proxy y el método getClass()
no funcionará. Por suerte Hibernate nos da la solución a este pequeño problema usando el método Hibernate.getClass(Object obj).
@Override public int hashCode() { Object dato1=getLogin() ; int resultado; resultado = 31 * resultado + (dato1 == null ? 0 : dato1.hashCode()); return resultado; }
El código de este método es bastante sencillo aunque el porqué de ello es mas complejo y no entraremos a explicarlo sin embargo en el libro Effective Java (2nd Edition) hay una explicación de ello.
login
.
Con toda esta explicación no debería ser difícil crear nuevas funciones equals
y hashCode
para otras clases que se usen en Hibernate.