JPA y las herramientas de mapeo objeto-relacional nos permiten abstraernos de lo que pasa en la base de datos y trabajar sólo con objetos. Pero ¿qué relación exactamente mantienen los objetos con la base de datos?

El caso del setter misterioso

Supongamos que tenemos una entidad llamada Persona. Para obtener, guardar y borrar Personas de la base de datos, lo hacemos a través de un EntityManager. Por ejemplo, si queremos buscar por id podemos utilizar el método find:

// Comienzo una transacción
Persona persona = entityManager.find(Persona.class, 1); // Buscar la Persona con id 1
System.out.println(persona.getNombre());
// Mostrará "José"
// Commit de la transacción

Hasta ahora todo normal, pero ¿qué pasaría si le cambiamos el valor a un atributo de la Persona que obtuvimos?:

// Comienzo una transacción
Persona persona = entityManager.find(Persona.class, 1);
persona.setNombre("Carlos"); // Le cambiamos el nombre // Commit de la transacción

Nos encontraremos con que en la base de datos cambió el nombre de la persona a Carlos. Pero, si la clase Persona la escribimos nosotros, y setNombre es un setter común y corriente ¿quién persistió esto en la base de datos y en qué momento?

El contexto de persistencia

El conjunto de los objetos entidad manejados por un EntityManager se llama contexto de persistencia.

El EntityManager cada tanto sincroniza los cambios del contexto de persistencia a la base de datos, en un proceso llamado flush.

La trampa del ejemplo anterior está en que cuando hacemos find (y también cuando hacemos consultas JPQL o Criteria) el EntityManager nos devuelve un objeto de su contexto de persistencia ¡y es por eso que los cambios que hicimos terminaron en la base de datos!

Ahora sabemos que el EntityManager es el que persistió los cambios que hicimos. Pero ¿cuándo lo hizo?

Momentos en los que ocurre el flush

La especificación de JPA menciona dos situaciones en las que el EntityManager está obligado a hacer flush:

  • Al llamar explícitamente a su método flush
  • Al ocurrir el commit de la transacción (lo que ocurre en el ejemplo del principio)

Sin embargo, si el EntityManager tiene configurado el modo de flush FlushModeType.AUTO (que es el que viene por defecto) también hará flush cuando hagamos una consulta y tengamos cambios pendientes en el contexto de persistencia que puedan afectar el resultado de esa consulta, como en este ejemplo:

// Comienzo una transacción
Persona persona = entityManager.find(Persona.class, 1);
System.out.println(persona.getNombre());
// Mostrará "José"
persona.setNombre("Carlos"); entityManager.createQuery(“FROM Persona”).getResultList(); // Ocurre el flush, y la persona con id 1 tendrá el cambio que hicimos // Commit de la transacción

Existe también el FlushModeType.COMMIT para evitar el flush antes de las consultas y obtener los datos tal cual están en la base de datos.

Cabe aclarar que JPA no le prohibe al proveedor de persistencia hacer flush en otro momento (aunque Hibernate no lo hace), así que es importante leer la documentación del proveedor respecto a esto.

La moraleja

Aprendimos que entre nosotros y la base de datos hay un contexto de persistencia, y que el EntityManager sincroniza ese contexto a la base de datos en ciertos momentos clave. También vimos que en él se guardan los objetos que obtenemos con find y consultas y por lo tanto los cambios sobre ellos se persisten.

Sin embargo, esquivamos descaradamente el tema de las transacciones mediante comentarios como "comienzo una transacción". Hacemos esto porque es común que las transacciones las maneje un framework como Spring y no esté explícito dónde empiezan y dónde terminan. Para esos casos sería sano investigar cómo el framework delimita las transacciones ya que en esos momentos es cuando se guardarán realmente nuestros cambios (los que usen Spring y piensen que lo saben deberían leer este genial artículo).

Inspiración.

"Si tú tienes una manzana y yo tengo una manzana e intercambiamos las manzanas, entonces tanto tú como yo seguiremos teniendo una manzana cada uno. Pero si tú tienes una idea y yo tengo una idea, e intercambiamos las ideas, entonces ambos tendremos dos ideas"

Bernard Shaw