Hibernate, al igual que la vida, está lleno de sopresas. Hoy vamos a compartir una de estas sorpresas: ¿se dieron cuenta que Hibernate tiene dos métodos para cargar una entidad persistente de la base de datos? Estos dos métodos son get(Class, Serializable) y load(Class, Serializable), pertenecientes a la clase Session y sus variantes.
Curioso, ambos métodos tienen la misma firma. Más curioso, la descripción del API de ambos métodos empieza igual:
Retorna la instancia persistente de la clase dada con el identificador dado.
La mayoría de los desarrolladores los usan de forma indistinta. Y es un error, ya que cuando no se encuentra la entidad, el método get() devolverá null mientras que el método load() lanzará una excepción de Hibernate. Como bien lo explica el API del método load():
Retorna la instancia persistente de la clase dada con el identificador dado, asumiendo que la instancia existe. No se debe usar este método para determinar si una instancia existe (en cambio, usar get()). Sólo usar este método para recuperar una instancia que se asume existe, en donde la no existencia sería un error real.
La verdad es que la diferencia es otra: el método get() retorna una instancia, mientras que el método load() retorna un proxy. ¿No me creen? Prueben ejecutar el siguiente código:
Session session = factory.getCurrentSession(); Foo foo = (Foo) session.get(Foo.class, 1); // Verificamos la clase del objeto assertSame(foo.getClass(), Foo.class);
La prueba pasará con éxito, verificando que la clase de la variable foo es realmente de la clase Foo. Ahora, en otra sesión, prueben lo siguiente:
Session session = factory.getCurrentSession(); Foo foo = (Foo) session.load(Foo.class, 1); // Verificamos la clase del objeto assertNotSame(foo.getClass(), Foo.class);
Esta prueba también pasará, comprobando que la clase de la variable foo no es Foo. Si miran la variable foo con un debugger, verán que es una instancia proxy y que sus campos no están inicializados! Noten que en ambos casos pueden castear esta instancia a Foo. Al invocar los getter también obtendrán los valores esperados.
Entonces, ¿para qué invocar al método load()?. Porque como es un proxy, no llegará hasta la base de datos hasta que se invoque algún método.
Más aún, estas características también están disponible en el EntityManager de JPA, a través de los métodos find() y getReference().
Ambos comportamientos se modifican con el mecanismo de caché de Hibernate. Prueben lo siguiente:
// Cargar la referencia session.load(Foo.class, 1); Foo foo = (Foo) session.get(Foo.class, 1);
De acuerdo a lo dicho anteriormente, la clase de la variable foo debería ser un objeto Foo. ¡Error! Como invocamos previamente al método load(), el método get() buscará en el caché de la Session (el caché de primer nivel) y retornará un proxy.
Este comportamiento es simétrico, lo vemos con la siguiente prueba:
// Obtener el objeto session.get(Foo.class, 1); // Carga la referencia, pero la busca en el caché y encuentra la entidad real // que se obtuvo en la linea anterior. Foo foo = (Foo) session.load(Foo.class, 1); // Verificar la clase del objeto assertSame(foo.getClass(), Foo.class);
Conclusión: Hibernate hace un excelente trabajo en simplificar el mapeo de objetos a bases de datos relacionados. Y sin embargo, no es un framework simple: debemos tener en cuenta estos cambios de comportamiento, entendiendo el funcionamiento del framework.