¿Cuáles son las diferencias entre los distintos métodos de guardado en Hibernate?
Hibernate tiene varios métodos que, de una forma u otra, toman su objeto y lo colocan en la base de datos. ¿Cuáles son las diferencias entre ellos, cuándo usar cuál y por qué no existe un solo método inteligente que sepa cuándo usar qué?
Los métodos que he identificado hasta ahora son:
save()
update()
saveOrUpdate()
saveOrUpdateCopy()
merge()
persist()
Aquí está mi comprensión de los métodos. Sin embargo, principalmente se basan en la API , ya que no los uso todos en la práctica.
saveOrUpdate Llama a guardar o actualizar dependiendo de algunas comprobaciones. Por ejemplo, si no existe ningún identificador, se llama a guardar. De lo contrario, se llama a la actualización.
guardar Persiste una entidad. Asignará un identificador si no existe ninguno. Si uno lo hace, esencialmente está haciendo una actualización. Devuelve el ID generado de la entidad.
update Intenta conservar la entidad utilizando un identificador existente. Si no existe ningún identificador, creo que se lanza una excepción.
saveOrUpdateCopy Esto está en desuso y ya no debería usarse. En cambio hay...
fusionar Ahora aquí es donde mi conocimiento comienza a fallar. Lo importante aquí es la diferencia entre entidades transitorias, separadas y persistentes. Para obtener más información sobre los estados de los objetos, consulte aquí . Con guardar y actualizar, se trata de objetos persistentes. Están vinculados a una sesión para que Hibernate sepa qué ha cambiado. Pero cuando tienes un objeto transitorio, no hay sesión involucrada. En estos casos, debe utilizar la combinación para realizar actualizaciones y persistir para guardar.
persistir Como se mencionó anteriormente, esto se usa en objetos transitorios. No devuelve el ID generado.
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
║ METHOD ║ TRANSIENT ║ DETACHED ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ sets id if doesn't ║ sets new id even if object ║
║ save() ║ exist, persists to db, ║ already has it, persists ║
║ ║ returns attached object ║ to DB, returns attached object ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ sets id on object ║ throws ║
║ persist() ║ persists object to DB ║ PersistenceException ║
║ ║ ║ ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ ║ ║
║ update() ║ Exception ║ persists and reattaches ║
║ ║ ║ ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ copy the state of object in ║ copy the state of obj in ║
║ merge() ║ DB, doesn't attach it, ║ DB, doesn't attach it, ║
║ ║ returns attached object ║ returns attached object ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ ║ ║
║saveOrUpdate()║ as save() ║ as update() ║
║ ║ ║ ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
Consulte el Foro de Hibernate para obtener una explicación de las sutiles diferencias entre persistir y guardar. Parece que la diferencia es el momento en que finalmente se ejecuta la instrucción INSERT. Dado que save devuelve el identificador, la instrucción INSERT debe ejecutarse instantáneamente independientemente del estado de la transacción (lo cual generalmente es malo). Persist no ejecutará ninguna declaración fuera de la transacción actualmente en ejecución solo para asignar el identificador. Guardar/Persistir funcionan en instancias transitorias , es decir, instancias que aún no tienen un identificador asignado y, como tales, no se guardan en la base de datos.
Tanto Update como Merge funcionan en instancias separadas , es decir, instancias que tienen una entrada correspondiente en la base de datos pero que actualmente no están adjuntas (ni administradas por) una sesión. La diferencia entre ellos es lo que sucede con la instancia que se pasa a la función. La actualización intenta volver a adjuntar la instancia, eso significa que no debe haber ninguna otra instancia de la entidad persistente adjunta a la sesión en este momento; de lo contrario, se genera una excepción. merge , sin embargo, simplemente copia todos los valores en una instancia persistente en la sesión (que se cargará si no está cargada actualmente). El objeto de entrada no se modifica. Por lo tanto , fusionar es más general que actualizar , pero puede utilizar más recursos.
Debería preferir los métodos JPA la mayor parte del tiempo y los métodos update
para tareas de procesamiento por lotes.
Una entidad JPA o Hibernate puede estar en uno de los siguientes cuatro estados:
- Transitorio (nuevo)
- Gestionado (persistente)
- Separado
- Eliminado (eliminado)
La transición de un estado a otro se realiza mediante los métodos EntityManager o Session.
Por ejemplo, la JPA EntityManager
proporciona los siguientes métodos de transición del estado de la entidad.
Hibernate Session
implementa todos los EntityManager
métodos JPA y proporciona algunos métodos adicionales de transición de estado de entidad save
como saveOrUpdate
y update
.
Persistir
Para cambiar el estado de una entidad de Transitorio (Nuevo) a Administrado (Persistente), podemos usar el persist
método que ofrece JPA EntityManager
que también es heredado por Hibernate Session
.
El
persist
método activa unPersistEvent
que es manejado por elDefaultPersistEventListener
detector de eventos de 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 declaraciones 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 gestionadas 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 que JPA EntityManager
e Hibernate Session
se conocen como caché de primer nivel.
Al llamar persist
, la entidad solo se adjunta al contexto de persistencia que se está ejecutando actualmente, y el INSERT se puede posponer hasta que se flush
llame.
La única excepción es la IDENTITY
que activa INSERT de inmediato, ya que esa es la única forma en que puede obtener el identificador de entidad. Por este motivo, Hibernate no puede realizar inserciones por lotes para entidades que utilicen el IDENTITY
generador.
Ahorrar
El método específico de Hibernate save
es anterior a JPA y ha estado disponible desde el comienzo del proyecto Hibernate.
El
save
método activa unSaveOrUpdateEvent
que es manejado por elDefaultSaveOrUpdateEventListener
detector de eventos de Hibernate. Por tanto, elsave
método es equivalente a los métodosupdate
ysaveOrUpdate
.
Para ver cómo save
funciona el 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 declaraciones 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 de persist
, el save
método devuelve el identificador de la entidad.
Actualizar
El método específico de Hibernate update
está destinado a omitir el mecanismo de verificación sucia y forzar una actualización de la entidad en el momento del vaciado.
El
update
método activa unSaveOrUpdateEvent
que es manejado por elDefaultSaveOrUpdateEventListener
detector de eventos de Hibernate. Por tanto, elupdate
método es equivalente a los métodossave
ysaveOrUpdate
.
Para ver cómo update
funciona el 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 declaraciones 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.
Usar @SelectBeforeUpdate
para evitar actualizaciones innecesarias
Ahora, la ACTUALIZACIÓN siempre se ejecutará incluso si la entidad no se modificó mientras estaba en el estado desconectado. Para evitar esto, puede usar la @SelectBeforeUpdate
anotación Hibernate, que activará una SELECT
declaración que se obtuvo loaded state
y luego será utilizada por el mecanismo de verificación sucia.
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 declaraciones 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
Observe que, esta vez, no se UPDATE
ejecuta ya que el mecanismo de verificación sucia de Hibernate ha detectado que la entidad no fue modificada.
Guardar o actualizar
El método específico de Hibernate saveOrUpdate
es solo un alias para save
y update
.
El
saveOrUpdate
método activa unSaveOrUpdateEvent
que es manejado por elDefaultSaveOrUpdateEventListener
detector de eventos de Hibernate. Por tanto, elupdate
método es equivalente a los métodossave
ysaveOrUpdate
.
Ahora, puede utilizarlo saveOrUpdate
cuando desee conservar una entidad o forzarla, 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 elNonUniqueObjectException
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 arrojará un NonUniqueObjectException
porque el segundo EntityManager
ya contiene una Book
entidad con el mismo identificador que el 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 utilizar el merge
método ofrecido por JPA y heredado también EntityManager
por Hibernate .Session
Obtiene merge
una nueva instantánea de la 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 pasada al merge
método.
El
merge
método activa unMergeEvent
que es manejado por elDefaultMergeEventListener
detector de eventos de Hibernate.
Para ver cómo merge
funciona el 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 pasa la entidad separada a merge
una subsecuencia de contexto de persistencia.
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);
});
When running the test case above, Hibernate executed the following SQL statements:
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
Notice that the entity reference returned by merge
is different than the detached one we passed to the merge
method.
Now, although you should prefer using JPA merge
when copying the detached entity state, the extra SELECT
can be problematic when executing a batch processing task.
For this reason, you should prefer using update
when you are sure that there is no entity reference already attached to the currently running Persistence Context and that the detached entity has been modified.
Conclusion
To persist an entity, you should use the JPA persist
method. To copy the detached entity state, merge
should be preferred. The update
method is useful for batch processing tasks only. The save
and saveOrUpdate
are just aliases to update
and you should not probably use them at all.
Some developers call save
even when the entity is already managed, but this is a mistake and triggers a redundant event since, for managed entities, the UPDATE is automatically handled at the Persistence context flush time.