Contexto de persistencia JPA / Hibernate

1. Información general

Los proveedores de persistencia como Hibernate utilizan el contexto de persistencia para administrar el ciclo de vida de la entidad en una aplicación.

En este tutorial, comenzaremos con la introducción del contexto de persistencia, luego veremos por qué es importante. Finalmente, veremos la diferencia entre el contexto de persistencia de alcance de transacción y el contexto de persistencia de alcance extendido con ejemplos.

2. Contexto de persistencia

Echemos un vistazo a la definición oficial del contexto de persistencia:

Una instancia de EntityManager está asociada con un contexto de persistencia. Un contexto de persistencia es un conjunto de instancias de entidad en las que para cualquier identidad de entidad persistente hay una instancia de entidad única. Dentro del contexto de persistencia, se gestionan las instancias de entidad y su ciclo de vida. La API EntityManager se utiliza para crear y eliminar instancias de entidades persistentes, para encontrar entidades por su clave principal y para consultar entidades.

La declaración anterior puede parecer un poco compleja en este momento, pero tendrá mucho sentido a medida que avancemos. El contexto de persistencia es la caché de primer nivel donde todas las entidades se obtienen de la base de datos o se guardan en la base de datos . Se encuentra entre nuestra aplicación y el almacenamiento persistente.

El contexto de persistencia realiza un seguimiento de los cambios realizados en una entidad gestionada. Si algo cambia durante una transacción, la entidad se marca como sucia. Cuando se completa la transacción, estos cambios se vacían en el almacenamiento persistente.

El EntityManager es la interfaz que nos permite interactuar con el contexto de persistencia. Siempre que usamos EntityManager , en realidad estamos interactuando con el contexto de persistencia .

Si cada cambio realizado en la entidad hace una llamada al almacenamiento persistente, podemos imaginar cuántas llamadas se realizarán. Esto tendrá un impacto en el rendimiento porque las llamadas de almacenamiento persistentes son costosas.

3. Tipo de contexto de persistencia

Los contextos de persistencia están disponibles en dos tipos:

  • Contexto de persistencia con alcance de transacción
  • Contexto de persistencia de alcance extendido

Echemos un vistazo a cada uno.

3.1 Contexto de persistencia con ámbito de transacción

El contexto de persistencia de la transacción está vinculado a la transacción. Tan pronto como finalice la transacción, las entidades presentes en el contexto de persistencia se descargarán en el almacenamiento persistente.

Cuando realizamos cualquier operación dentro de la transacción, EntityManager busca un contexto de persistencia . Si existe, se utilizará. De lo contrario, creará un contexto de persistencia.

El tipo de contexto de persistencia predeterminado es PersistenceContextType.TRANSACTION . Para decirle al EntityManager que use el contexto de persistencia de la transacción, simplemente lo anotamos con @PersistenceContext :

@PersistenceContext private EntityManager entityManager;

3.2 Contexto de persistencia de alcance extendido

Un contexto de persistencia extendido puede abarcar varias transacciones. Podemos conservar la entidad sin la transacción, pero no podemos eliminarla sin una transacción.

Para decirle a EntityManager que use el contexto de persistencia de alcance extendido, debemos aplicar el atributo de tipo de @PersistenceContext :

@PersistenceContext(type = PersistenceContextType.EXTENDED) private EntityManager entityManager;

En el bean de sesión sin estado, el contexto de persistencia extendido en un componente desconoce por completo cualquier contexto de persistencia de otro componente . Esto es cierto incluso si ambos están en la misma transacción.

Digamos que conservamos alguna entidad en un método del Componente A , que se ejecuta en una transacción. Luego llamamos a algún método de Componente B . En el Componente B contexto de persistencia método, no vamos a encontrar que la entidad de persistencia en el método de componente A .

4. Ejemplo de contexto de persistencia

Ahora que sabemos lo suficiente sobre el contexto de persistencia, es hora de sumergirnos en un ejemplo. Haremos diferentes casos de uso con contexto de persistencia de transacciones y contexto de persistencia extendido.

Primero, creemos nuestra clase de servicio, TransctionPersistenceContextUserService :

@Component public class TransctionPersistenceContextUserService { @PersistenceContext private EntityManager entityManager; @Transactional public User insertWithTransaction(User user) { entityManager.persist(user); return user; } public User insertWithoutTransaction(User user) { entityManager.persist(user); return user; } public User find(long id) { return entityManager.find(User.class, id); } }

La siguiente clase, ExtendedPersistenceContextUserService , es muy similar a la anterior, excepto por la anotación @PersistenceContext . Esta vez pasamos PersistenceContextType.EXTENDED al parámetro de tipo de su anotación @PersistenceContext :

@Component public class ExtendedPersistenceContextUserService { @PersistenceContext(type = PersistenceContextType.EXTENDED) private EntityManager entityManager; // Remaining code same as above }

5. Casos de prueba

Ahora que tenemos nuestras clases de servicio configuradas, es hora de crear diferentes casos de uso con contexto de persistencia de transacciones y contexto de persistencia extendido.

5.1 Prueba del contexto de persistencia de transacciones

Persistamos una entidad de usuario utilizando un contexto de persistencia con alcance de transacción. La entidad se guardará en un almacenamiento persistente. Luego verificamos haciendo una llamada de búsqueda utilizando EntityManager de nuestro contexto de persistencia extendido :

User user = new User(121L, "Devender", "admin"); transctionPersistenceContext.insertWithTransaction(user); User userFromTransctionPersistenceContext = transctionPersistenceContext .find(user.getId()); assertNotNull(userFromTransctionPersistenceContext); User userFromExtendedPersistenceContext = extendedPersistenceContext .find(user.getId()); assertNotNull(userFromExtendedPersistenceContext);

When we try to insert a User entity without a transaction, then TransactionRequiredException will be thrown:

@Test(expected = TransactionRequiredException.class) public void testThatUserSaveWithoutTransactionThrowException() { User user = new User(122L, "Devender", "admin"); transctionPersistenceContext.insertWithoutTransaction(user); }

5.2 Testing Extended Persistence Context

Next, let's persist the user with an extended persistence context and without a transaction. The User entity will be saved in the persistence context (cache) but not in persistent storage:

User user = new User(123L, "Devender", "admin"); extendedPersistenceContext.insertWithoutTransaction(user); User userFromExtendedPersistenceContext = extendedPersistenceContext .find(user.getId()); assertNotNull(userFromExtendedPersistenceContext); User userFromTransctionPersistenceContext = transctionPersistenceContext .find(user.getId()); assertNull(userFromTransctionPersistenceContext);

In the persistence context for any persistent entity identity, there will be a unique entity instance. If we try to persist another entity with the same identifier:

@Test(expected = EntityExistsException.class) public void testThatPersistUserWithSameIdentifierThrowException() { User user1 = new User(126L, "Devender", "admin"); User user2 = new User(126L, "Devender", "admin"); extendedPersistenceContext.insertWithoutTransaction(user1); extendedPersistenceContext.insertWithoutTransaction(user2); }

We'll see EntityExistsException:

javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session

Extended persistence context within a transaction saves the entity in persistent storage at the end of the transaction:

User user = new User(127L, "Devender", "admin"); extendedPersistenceContext.insertWithTransaction(user); User userFromDB = transctionPersistenceContext.find(user.getId()); assertNotNull(userFromDB);

El contexto de persistencia extendido vacía las entidades almacenadas en caché en el almacenamiento persistente cuando se usa dentro de la transacción . Primero, mantenemos la entidad sin una transacción. A continuación, persistimos otra entidad en la transacción:

User user1 = new User(124L, "Devender", "admin"); extendedPersistenceContext.insertWithoutTransaction(user1); User user2 = new User(125L, "Devender", "admin"); extendedPersistenceContext.insertWithTransaction(user2); User user1FromTransctionPersistenceContext = transctionPersistenceContext .find(user1.getId()); assertNotNull(user1FromTransctionPersistenceContext); User user2FromTransctionPersistenceContext = transctionPersistenceContext .find(user2.getId()); assertNotNull(user2FromTransctionPersistenceContext);

6. Conclusión

En este tutorial, obtuvimos una buena comprensión del contexto de persistencia.

Primero, analizamos el contexto de persistencia de la transacción, que existe durante toda la vida de la transacción. Luego, analizamos el contexto de persistencia extendido, que puede abarcar múltiples transacciones.

Como siempre, el código de muestra está disponible en GitHub.