Cuando tenemos relaciones entre objetos, los ORM al momento de traer a memoria un objeto también van a traer todos los objetos que se relacionan con el, y esto puede ser una ventaja o una desventaja dependiendo el contexto y del momento en que se haga la obtención de estos objetos. En este posteo vamos a ver los dos tipos de obtención que nos provee JPA y lo que sucede cuando usamos cada uno.
Los dos tipos de carga que tenemos con JPA, son los llamados Eager y Lazy. Estos nombres nos dan un indicio sobre que comportamiento tiene cada uno, pero vamos a definirlos seguido de unos ejemplos.
Eager: La carga de los objetos de la relación se produce en el mismo momento
Lazy: La carga de los objetos de la relación se producen a demanda, es decir, cuando un cliente los solicita.
Veamos el siguiente ejemplo, donde tenemos una clase Estudiante y una clase Legajo, con una relación Uno a Uno, donde un Estudiante tiene un solo Legajo, y ese Legajo pertenece a un solo Estudiante.
@Entity
public class Estudiante {
@Id @GeneratedValue
private Long id;
private String nombre;
@OneToOne()
private Legajo legajo;
@Entity
public class Legajo {
@Id @GeneratedValue
private Long id;
private String codigo;
En este caso, la relación dominante es el Estudiante, el cual tiene un atributo Legajo. Entonces, evidentemente cuando obtenemos un objeto Estudiante, debemos también obtener el objeto Legajo asociado. En nuestra base de datos, tenemos los siguientes registros:
Si queremos obtener un Estudiante solo para saber su nombre, y desde Java ejecutamos un simple caso de uso donde obtenemos desde la base de datos un Estudiante según su id, y mostramos lo obtenido, vamos a ver que se ejecuto una query que contiene un left join para traer también los datos del legajo.
Estudiante estudiante = entityManager.find(Estudiante.class, 2001L);
logger.info("Estudiante --> {}", estudiante);
Este caso es trivial, dado que es una relación One to One, pero en relaciones complejas con objetos complejos, con posiblemente mas relaciones asociadas, esto puede generar un problema de performance, además de traer información que aun no necesitamos.
En el ejemplo, aun no definimos el TypeFetch, pero por defecto JPA usa EAGER para la relación @OneToOne, pero en relaciones como @OneToMany y @ManyToMany, el tipo que utiliza por defecto es LAZY ya que del otro lado de la relación tenemos N cantidad de objetos a traer desde la base de datos, y eso nos puede disminuir el rendimiento.
Como ya vimos, la relación @OneToOne es por defecto EAGER, pero si quisiéramos hacerlo explicito, deberímos definirlo de la siguiente manera:
@OneToOne(fetch = FetchType.EAGER)
private Legajo legajo;
Y si quisieramos que el tipo de obtencion de los objetos sea LAZY, lo definimos asi:
@OneToOne(fetch = FetchType.LAZY)
private Legajo legajo;
De esta manera, estamos difiriendo la obtención del objeto Legajo relacionado a Estudiante. Si ejecutamos el caso de uso, vamos a notar que en este caso se obtiene el Estudiante sin su Legajo.
Estudiante estudiante = entityManager.find(Estudiante.class, 2001L);
logger.info("Estudiante --> {}", estudiante);
En la query que genera el ORM, vemos como solo hace un SELECT sobre Estudiante, sin necesidad de un JOIN sobre Legajo, ya que al ser de tipo LAZY no necesita traer a ese objeto.
Pero, ¿si queremos en algun momento obtener los datos del Legajo asociados a ese Estudiante?, es tan simple como utilizar el getter del atributo, y el ORM se encarga de traer el objeto especifico.
Estudiante estudiante = entityManager.find(Estudiante.class, 2001L);
logger.info("Estudiante --> {}", estudiante);
logger.info("Legajo --> {}", estudiante.getLegajo());
Ahora si, vemos como se ejecutan dos queries por separado, una para obtener el Estudiante, y otra para obtener el Legajo del estudiante en cuestión.
Dado esto podemos concluir en algunas ventajas y desventajas de ambos tipos de obtención de objetos.
El tipo LAZY conlleva un menor uso de memoria debido a que no obtiene el total de los objetos de la relación en el mismo momento, por lo que a su vez su tiempo de carga va a ser menor. Como desventaja, al diferir la obtención de los objetos si no lo manejamos con su debido cuidado podemos llevar a nuestro código a alguna excepción durante momentos no deseados.
En cuanto al tipo EAGER, nos aseguramos de tener todos los objetos de la relación, por lo que es menos probable que nos encontremos con algún tipo de excepción provocada por el mappeo. Como desventaja, tenemos un mayor tiempo de carga de los objetos en memoria, y vamos a tener demasiados objetos innecesarios que podrían afectar el rendimiento de la aplicación.