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 Persona
s 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).