Buscar este blog

miércoles, 4 de julio de 2012

Hibernate, OneToOne y falsos Lazy

Hace poco estaba extrañada con la lentitud con que se ejecutaba un proceso en una aplicación que utiliza Hibernate, por lo que me puse a investigar un poco las consultas que se estaban ejecutando y como estaban hechos los mapeos, por ver si encontraba algo raro.
Comprobé que todas las relaciones estaban puestas con lazy (bien, primer paso comprobado) y entonces pasé a ver las sql que se estaban lanzando, y cual fue mi sorpresa al ver que, en una relación OneToOne, aunque estuviera especificado que la carga fuera lazy, Hibernate estaba lanzando una consulta para cargar esa entidad por cada registro encontrado.
Googleleando descubrí que Hibernate tiene un problema con las relaciones OneToOne y que si en la tabla hija de una relación OneToOne le dices que quieres carga lazy, él te dice que bien, que vale, que te da la opción de ponerlo, pero la ignora finamente.
Por poner un ejemplo, si tenemos estas dos clases:
@Entity
public class Persona {

 @OneToOne(optional = false)
 private Casa casa;

 public Casa getCasa() {
  return casa;
 }

 public void setCasa(Casa casa) {
  this.casa = casa;
 }
}

@Entity
public class Casa {

 @OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "animal")
 private Persona persona;

 public Persona getPersona() {
  return persona;
 }

 public void setPersona(Persona persona) {
  this.persona = persona;
 }
 
}
Si hacemos una consulta sobre "Casa", la idea es que la persona no se cargue hasta que queramos acceder a ella, pero esto no es así, Hibernate realizará una consulta extra por cada registro de casa para cargar las personas (lo que, si se encadena en cascada, puede afectar gravemente al rendimiento) . Este comportamiento, según Hibernate, se basa en que, sin realizar la consulta, Hibernate no sabe si debe dejar la persona a null o con el proxy.
Y, ante esto, ¿que hacer? Pues la mejor solución que he encontrado es "engañar" a Hibernate para que crea que ha cargado la entidad, y que de esta forma no haga los select extra. Para ello, en el modelo donde se produce el problema (en nuestro ejemplo, Casa) se tiene que implementar la interfaz  FieldHandled,  añadir el atributo FieldHandler fieldHandler y realizar los siguiente cambios en la definición y en los getters/setters de las propiedades afectadas por el problema lazy:
@Entity
public class Casa implements FieldHandled {
 private FieldHandler fieldHandler;

 @OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "animal")
 @LazyToOne(LazyToOneOption.NO_PROXY)
 private Persona persona;

 public Persona getPersona() {
  if (fieldHandler != null) {
            return (Persona) fieldHandler.readObject(this, "persona", persona);
  }
  return persona;
 }

 public void setPersona(Persona persona) {
  if (fieldHandler != null) {
            this.persona = (Persona) fieldHandler.writeObject(this, "persona", persona, persona);
            return;
        }
  this.persona = persona;
 }
 
}
De esta forma, hasta que no querramos a conciencia acceder a la Persona a través del get no se cargará el objeto (es decir, que será lazy de verdad :) ).
Enlace al artículo original en inglés de esta solución: Lazy one-to-one inverse relationships in Hibernate

6 comentarios:

  1. Tengo una pregunta ; que sentido tiene para ti hacer este blog mujer...?

    ResponderEliminar
  2. Pues el sentido en un principio era la de compartir conocimientos y experiencias, aunque la falta de tiempo (por razones que no vienen al caso) haya hecho que haya muy pocas entradas.
    Y una vez contestada la pregunta, hago yo otra: que sentido tiene para ti hacer esa pregunta persona humana.. ? (no pongo género porque al escribir como anónimo es imposible saberlo).

    ResponderEliminar
  3. Muchas gracias

    Has resuelto mi problema

    ResponderEliminar
  4. Muchas gracias a ti, porque tengo el blog completamente abandonado, y comentarios como ese me dan ganas de retomarlo :)

    ResponderEliminar
  5. Genial me has resulto tremendo problema, fíjate que tengo un diseño de BD que lo que más tiene es relaciones OneToOne y esto se me estaba convirtiendo en un problema debido al hibérnate. Gracias me ha servido de mucho
    Si tienes por ahí otros trucos como este no vendría mal que los publicaras
    saludos

    ResponderEliminar
  6. Tremenda respuesta con altura!
    Felicidades

    ResponderEliminar