Como expliqué en este artículo , debe preferir los métodos JPA la mayor parte del tiempo y las update
tareas de procesamiento por lotes.
Una entidad JPA o Hibernate puede estar en uno de los siguientes cuatro estados:
- Transitoria (nueva)
- Gestionado (persistente)
- Separado
- Eliminado (eliminado)
La transición de un estado a otro se realiza a través de los métodos EntityManager o Session.
Por ejemplo, el JPA EntityManager
proporciona los siguientes métodos de transición de estado de entidad.
Hibernate Session
implementa todos los EntityManager
métodos JPA y proporciona algunos métodos adicionales de transición de estado de entidad como save
, saveOrUpdate
y update
.
Persistir
Para cambiar el estado de una entidad de Transitoria (Nueva) a Administrada (Persistente), podemos usar el persist
método ofrecido por el JPA EntityManager
que también es heredado por el Hibernate Session
.
El persist
método activa un PersistEvent
que es manejado por el DefaultPersistEventListener
oyente de eventos Hibernate.
Por lo tanto, al ejecutar el siguiente caso de prueba:
doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
LOGGER.info(
"Persisting the Book entity with the id: {}",
book.getId()
);
});
Hibernate genera las siguientes instrucciones SQL:
CALL NEXT VALUE FOR hibernate_sequence
-- Persisting the Book entity with the id: 1
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
Observe que id
se asigna antes de adjuntar la Book
entidad al contexto de persistencia actual. Esto es necesario porque las entidades administradas se almacenan en una Map
estructura donde la clave está formada por el tipo de entidad y su identificador y el valor es la referencia de la entidad. Esta es la razón por la cual el JPA EntityManager
y el Hibernate Session
se conocen como el caché de primer nivel.
Al llamar persist
, la entidad solo se adjunta al contexto de persistencia actualmente en ejecución, y el INSERT puede posponerse hasta que flush
se llame.
La única excepción es el generador de IDENTIDAD que activa el INSERTAR de inmediato, ya que esa es la única forma en que puede obtener el identificador de la entidad. Por esta razón, Hibernate no puede insertar por lotes para entidades que usan el generador IDENTITY. Para obtener más detalles sobre este tema, consulte este artículo .
Salvar
El save
método específico de Hibernate es anterior a JPA y ha estado disponible desde el comienzo del proyecto Hibernate.
El save
método activa un SaveOrUpdateEvent
que es manejado por el DefaultSaveOrUpdateEventListener
oyente de eventos Hibernate. Por lo tanto, el save
método es equivalente a los métodos update
y saveOrUpdate
.
Para ver cómo funciona el save
método, considere el siguiente caso de prueba:
doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
Long id = (Long) session.save(book);
LOGGER.info(
"Saving the Book entity with the id: {}",
id
);
});
Al ejecutar el caso de prueba anterior, Hibernate genera las siguientes instrucciones SQL:
CALL NEXT VALUE FOR hibernate_sequence
-- Saving the Book entity with the id: 1
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
Como puede ver, el resultado es idéntico a la persist
llamada al método. Sin embargo, a diferencia persist
, el save
método devuelve el identificador de entidad.
Para más detalles, mira este artículo .
Actualizar
El update
método específico de Hibernate está destinado a evitar el mecanismo de verificación sucio y forzar una actualización de la entidad en el momento del vaciado.
El update
método activa un SaveOrUpdateEvent
que es manejado por el DefaultSaveOrUpdateEventListener
oyente de eventos Hibernate. Por lo tanto, el update
método es equivalente a los métodos save
y saveOrUpdate
.
Para ver cómo funciona el update
método, considere el siguiente ejemplo que persiste una Book
entidad en una transacción, luego la modifica mientras la entidad está en el estado desconectado, y fuerza la ACTUALIZACIÓN de SQL utilizando la update
llamada al método.
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
LOGGER.info("Modifying the Book entity");
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
Al ejecutar el caso de prueba anterior, Hibernate genera las siguientes instrucciones SQL:
CALL NEXT VALUE FOR hibernate_sequence
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
-- Modifying the Book entity
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Observe que UPDATE
se ejecuta durante el vaciado del contexto de persistencia, justo antes de la confirmación, y es por eso que el Updating the Book entity
mensaje se registra primero.
Utilizando @SelectBeforeUpdate
para evitar actualizaciones innecesarias
Ahora, la ACTUALIZACIÓN siempre se ejecutará, incluso si la entidad no se cambió mientras estaba en el estado desconectado. Para evitar esto, puede usar la @SelectBeforeUpdate
anotación Hibernate que activará una SELECT
declaración que se obtiene y loaded state
que luego utiliza el mecanismo de verificación sucio.
Entonces, si anotamos la Book
entidad con la @SelectBeforeUpdate
anotación:
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Y ejecute el siguiente caso de prueba:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
});
Hibernate ejecuta las siguientes instrucciones SQL:
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
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
Tenga en cuenta que, esta vez, no se UPDATE
ejecuta desde que el mecanismo de comprobación sucia de Hibernate ha detectado que la entidad no se modificó.
SaveOrUpdate
El saveOrUpdate
método específico de Hibernate es solo un alias para save
y update
.
El saveOrUpdate
método activa un SaveOrUpdateEvent
que es manejado por el DefaultSaveOrUpdateEventListener
oyente de eventos Hibernate. Por lo tanto, el update
método es equivalente a los métodos save
y saveOrUpdate
.
Ahora, puede usar saveOrUpdate
cuando desea persistir una entidad o forzar una UPDATE
como se ilustra 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");
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
Cuidado con el NonUniqueObjectException
Un problema que puede ocurrir con save
, update
y saveOrUpdate
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 va a lanzar un NonUniqueObjectException
porque el segundoEntityManager
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)
Unir
Para evitarlo NonUniqueObjectException
, debe usar elmerge
método ofrecido por JPA EntityManager
y heredado por Hibernate Session
también.
Como se explica en este artículo , elmerge
obtiene una nueva instantánea de entidad de la base de datos si no se encuentra ninguna referencia de entidad en el Contexto de persistencia, y copia el estado de la entidad separada que se pasó al merge
método.
El merge
método desencadena unMergeEvent
que es manejado por el DefaultMergeEventListener
oyente de eventos Hibernate.
Para ver cómo funciona el merge
método, considere el siguiente ejemplo que persiste a una Book
entidad en una transacción, luego la modifica mientras la entidad está en el estado separado y pasa la entidad separada amerge
en un contexto de persistencia posterior.
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
LOGGER.info("Modifying the Book entity");
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
Al ejecutar el caso de prueba anterior, Hibernate ejecutó las siguientes instrucciones SQL:
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
-- Modifying the Book entity
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
Observe que la referencia de entidad devuelta por merge
es diferente de la separada que pasamos al merge
método.
Ahora, aunque debería preferir usar JPA merge
al copiar el estado de la entidad separada, el extra SELECT
puede ser problemático al ejecutar una tarea de procesamiento por lotes.
Por esta razón, debería preferir usar update
cuando esté seguro de que no hay una referencia de entidad ya asociada al Contexto de persistencia actualmente en ejecución y que la entidad separada se ha modificado.
Para obtener más detalles sobre este tema, consulte este artículo. .
Conclusión
Para persistir una entidad, debe usar el persist
método JPA . Para copiar el estado de la entidad separada, se merge
debe preferir. El update
método es útil solo para tareas de procesamiento por lotes. Los save
y saveOrUpdate
son solo alias update
y probablemente no debería usarlos en absoluto.
Algunos desarrolladores llaman save
incluso cuando la entidad ya está administrada, pero esto es un error y desencadena un evento redundante ya que, para las entidades administradas, la ACTUALIZACIÓN se maneja automáticamente en el momento de descarga del contexto de Persistencia.
Para más detalles, mira este artículo .