Como esta es una pregunta muy común, escribí
este artículo , en el que se basa esta respuesta.
Estados de la entidad
JPA define los siguientes estados de entidad:
Nuevo (transitorio)
Se considera que un objeto recién creado que nunca se ha asociado con un Hibernate Session
(también conocido como Persistence Context
) y que no está asignado a ninguna fila de la tabla de la base de datos está en el estado Nuevo (Transitorio).
Para ser persistente, necesitamos llamar explícitamente el EntityManager#persist
método o hacer uso del mecanismo de persistencia transitiva.
Persistente (Gestionado)
Una entidad persistente se ha asociado con una fila de la tabla de la base de datos y está siendo administrada por el Contexto de persistencia actualmente en ejecución. Cualquier cambio realizado en dicha entidad se detectará y propagará a la base de datos (durante el tiempo de descarga de la sesión).
Con Hibernate, ya no tenemos que ejecutar instrucciones INSERT / UPDATE / DELETE. Hibernate emplea un estilo de trabajo transaccional de reescritura y los cambios se sincronizan en el último momento responsable, durante el Session
tiempo de descarga actual .
Separado
Una vez que el contexto de persistencia actualmente en ejecución se cierra, todas las entidades administradas previamente se separan. Los cambios sucesivos ya no se rastrearán y no se realizará una sincronización automática de la base de datos.
Transiciones de estado de entidad
Puede cambiar el estado de la entidad utilizando varios métodos definidos por la EntityManager
interfaz.
Para comprender mejor las transiciones de estado de la entidad JPA, considere el siguiente diagrama:
Al usar JPA, para volver a asociar una entidad separada a una activa EntityManager
, puede usar la operación de fusión .
Al usar la API nativa de Hibernate, además de merge
, puede volver a conectar una entidad separada a una sesión de Hibernate activa utilizando los métodos de actualización, como se demuestra en el siguiente diagrama:
Fusionar una entidad separada
La fusión va a copiar el estado de la entidad separada (fuente) en una instancia de entidad administrada (destino).
Considere que hemos persistido en la siguiente Book
entidad, y ahora la entidad está separada ya EntityManager
que la que se usó para persistir la entidad se cerró:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
Mientras la entidad está en estado separado, la modificamos de la siguiente manera:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Ahora, queremos propagar los cambios a la base de datos, para que podamos llamar al merge
método:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
E Hibernate ejecutará las siguientes instrucciones SQL:
SELECT
b.id,
b.author AS author2_0_,
b.isbn AS isbn3_0_,
b.title AS title4_0_
FROM
book b
WHERE
b.id = 1
-- Merging the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Si la entidad fusionada no tiene equivalente en la actual EntityManager
, se obtendrá una instantánea de entidad nueva de la base de datos.
Una vez que hay una entidad administrada, JPA copia el estado de la entidad separada en la que está administrada actualmente, y durante el Contexto de persistenciaflush
, se generará una ACTUALIZACIÓN si el mecanismo de verificación sucio encuentra que la entidad administrada ha cambiado.
Entonces, cuando se usa merge
, la instancia del objeto separado continuará siendo separada incluso después de la operación de fusión.
Volver a unir una entidad separada
Hibernate, pero no JPA admite la reconexión a través del update
método.
Un Hibernate Session
solo puede asociar un objeto de entidad para una fila de base de datos dada. Esto se debe a que el contexto de persistencia actúa como un caché en memoria (caché de primer nivel) y solo un valor (entidad) está asociado con una clave dada (tipo de entidad e identificador de base de datos).
Una entidad se puede volver a unir solo si no hay otro objeto JVM (que coincida con la misma fila de la base de datos) ya asociado con la Hibernación actual Session
.
Teniendo en cuenta que hemos persistido en la Book
entidad y que la hemos modificado cuando la Book
entidad estaba en estado separado:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Podemos volver a conectar la entidad separada de esta manera:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
E Hibernate ejecutará la siguiente instrucción SQL:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
El update
método requiere que unwrap
la EntityManager
de una hibernación Session
.
A diferencia merge
, la entidad separada proporcionada se volverá a asociar con el contexto de persistencia actual y se programa una ACTUALIZACIÓN durante el vaciado, ya sea que la entidad se haya modificado o no.
Para evitar esto, puede usar la @SelectBeforeUpdate
anotación Hibernate que activará una instrucción SELECT que obtuvo el estado cargado que luego utiliza el mecanismo de verificación sucio.
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Cuidado con la excepción de objeto único
Un problema con el que puede ocurrir update
es si el contexto de persistencia ya contiene una referencia de entidad con la misma identificación y del mismo tipo que en el siguiente ejemplo:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
try {
doInJPA(entityManager -> {
Book book = entityManager.find(
Book.class,
_book.getId()
);
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
} catch (NonUniqueObjectException e) {
LOGGER.error(
"The Persistence Context cannot hold " +
"two representations of the same entity",
e
);
}
Ahora, al ejecutar el caso de prueba anterior, Hibernate arrojará un NonUniqueObjectException
porque el segundo EntityManager
ya contiene una Book
entidad con el mismo identificador al que pasamos update
, y el Contexto de persistencia no puede contener dos representaciones de la misma entidad.
org.hibernate.NonUniqueObjectException:
A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)
Conclusión
Es merge
preferible el método si está utilizando un bloqueo optimista, ya que le permite evitar actualizaciones perdidas. Para obtener más detalles sobre este tema, consulte este artículo .
El update
es bueno para actualizaciones por lotes, ya que puede evitar que la instrucción SELECT adicional generado por la merge
operación, por lo tanto, reduciendo el tiempo de ejecución por lotes de actualización.
refresh()
en entidades separadas. Mirando a través de la especificación 2.0 no veo ninguna justificación; solo que no está permitido.