Como expliquei neste artigo , você deve preferir os métodos JPA na maioria das vezes, e osupdate
tarefas de processamento em lote.
Uma entidade JPA ou Hibernate pode estar em um dos quatro estados a seguir:
- Transitório (novo)
- Gerenciado (persistente)
- Independente
- Removido (excluído)
A transição de um estado para outro é feita pelos métodos EntityManager ou Session.
Por exemplo, a JPA EntityManager
fornece os seguintes métodos de transição de estado da entidade.
O Hibernate Session
implementa todos os EntityManager
métodos JPA e fornece alguns métodos adicionais de transição de estado de entidade save
, como , saveOrUpdate
e update
.
Persistir
Para alterar o estado de uma entidade de Transitório (Novo) para Gerenciado (Persistido), podemos usar o persist
método oferecido pela JPA, EntityManager
que também é herdado pelo Hibernate Session
.
O persist
método dispara um PersistEvent
que é tratado pelo DefaultPersistEventListener
ouvinte de eventos do Hibernate.
Portanto, ao executar o seguinte caso de teste:
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()
);
});
O Hibernate gera as seguintes instruções 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 o id
é atribuído antes de anexar a Book
entidade ao contexto de persistência atual. Isso é necessário porque as entidades gerenciadas são armazenadas em uma Map
estrutura em que a chave é formada pelo tipo de entidade e seu identificador e o valor é a referência da entidade. Esta é a razão pela qual o JPA EntityManager
e o HibernateSession
são conhecidos como cache de primeiro nível.
Ao chamar persist
, a entidade é anexada apenas ao Contexto de Persistência em execução no momento, e o INSERT pode ser adiado até que oflush
seja chamado.
A única exceção é o gerador IDENTITY que aciona o INSERT imediatamente, pois é a única maneira de obter o identificador da entidade. Por esse motivo, o Hibernate não pode inserir em lotes para entidades usando o gerador de IDENTITY. Para mais detalhes sobre este tópico, consulte este artigo .
Salve
O save
método específico do Hibernate é anterior à JPA e está disponível desde o início do projeto Hibernate.
O save
método dispara um SaveOrUpdateEvent
que é tratado pelo DefaultSaveOrUpdateEventListener
ouvinte de eventos do Hibernate. Portanto, o save
método é equivalente aos métodos update
e saveOrUpdate
.
Para ver como o save
método funciona, considere o seguinte caso de teste:
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
);
});
Ao executar o caso de teste acima, o Hibernate gera as seguintes instruções 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 você pode ver, o resultado é idêntico à persist
chamada do método. No entanto, diferentemente persist
, o save
método retorna o identificador da entidade.
Para mais detalhes, consulte este artigo .
Atualizar
O update
método específico do Hibernate tem como objetivo ignorar o mecanismo de verificação suja e forçar uma atualização de entidade no momento da liberação.
O update
método dispara um SaveOrUpdateEvent
que é tratado pelo DefaultSaveOrUpdateEventListener
ouvinte de eventos do Hibernate. Portanto, o update
método é equivalente aos métodos save
e saveOrUpdate
.
Para ver como o update
método funciona, considere o exemplo a seguir, que persiste uma Book
entidade em uma transação, modifica-a enquanto a entidade está no estado desanexado e força o SQL UPDATE usando a update
chamada de 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");
});
Ao executar o caso de teste acima, o Hibernate gera as seguintes instruções 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 ele UPDATE
é executado durante a liberação do contexto de persistência, logo antes da confirmação, e é por isso que a Updating the Book entity
mensagem é registrada primeiro.
Usando @SelectBeforeUpdate
para evitar atualizações desnecessárias
Agora, o UPDATE sempre será executado, mesmo que a entidade não tenha sido alterada enquanto estiver no estado desanexado. Para evitar isso, você pode usar a @SelectBeforeUpdate
anotação Hibernate, que acionará uma SELECT
instrução que buscouloaded state
que é usada pelo mecanismo de verificação suja.
Portanto, se anotamos a Book
entidade com a @SelectBeforeUpdate
anotação:
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
E execute o seguinte caso de teste:
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);
});
O Hibernate executa as seguintes instruções 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, desta vez, não há UPDATE
execução, pois o mecanismo de verificação suja do Hibernate detectou que a entidade não foi modificada.
SaveOrUpdate
O saveOrUpdate
método específico do Hibernate é apenas um alias para save
e update
.
O saveOrUpdate
método dispara um SaveOrUpdateEvent
que é tratado pelo DefaultSaveOrUpdateEventListener
ouvinte de eventos do Hibernate. Portanto, o update
método é equivalente aos métodos save
e saveOrUpdate
.
Agora, você pode usar saveOrUpdate
quando quiser persistir em uma entidade ou forçar um UPDATE
conforme ilustrado pelo exemplo a seguir.
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 com o NonUniqueObjectException
Um problema que pode ocorrer com save
, update
e saveOrUpdate
é se o contexto de persistência já contém uma referência de entidade com o mesmo ID e do mesmo tipo como no exemplo a seguir:
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
);
}
Agora, ao executar o caso de teste acima, o Hibernate lançará um NonUniqueObjectException
porque o segundo EntityManager
já contém uma Book
entidade com o mesmo identificador que passamos update
, e o Contexto de Persistência não pode conter duas representações da mesma entidade.
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)
Mesclar
Para evitar isso NonUniqueObjectException
, você precisa usar o merge
método oferecido pela JPA EntityManager
e herdado pelo Hibernate Session
também.
Conforme explicado neste artigo , ele merge
busca um novo instantâneo de entidade do banco de dados se não houver uma referência de entidade encontrada no Contexto de Persistência e copia o estado da entidade desanexada passada para o merge
método.
O merge
método dispara um MergeEvent
que é tratado pelo DefaultMergeEventListener
ouvinte de eventos do Hibernate.
Para ver como o merge
método funciona, considere o exemplo a seguir, que persiste em umBook
entidade em uma transação, modificando-o enquanto a entidade está no estado desanexado e passando a entidade desanexada para merge
um Contexto de Persistência subsequente.
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);
});
Ao executar o caso de teste acima, o Hibernate executou as seguintes instruções 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 a referência da entidade retornada por merge
é diferente da referência desanexada que passamos para o merge
método.
Agora, embora você deva preferir usar o JPA merge
ao copiar o estado da entidade desconectada, o extra SELECT
pode ser problemático ao executar uma tarefa de processamento em lote.
Por esse motivo, você deve preferir usá- update
lo quando tiver certeza de que não há referência de entidade já anexada ao Contexto de Persistência atualmente em execução e que a entidade desanexada foi modificada.
Para mais detalhes sobre este tópico, consulte este artigo .
Conclusão
Para persistir em uma entidade, você deve usar o persist
método JPA . Para copiar o estado da entidade desanexada, merge
deve ser preferido. O update
método é útil apenas para tarefas de processamento em lote. O save
e saveOrUpdate
são apenas aliases paraupdate
e você provavelmente não deve usá-los.
Alguns desenvolvedores chamam save
mesmo quando a entidade já está gerenciada, mas isso é um erro e aciona um evento redundante, pois, para entidades gerenciadas, o UPDATE é tratado automaticamente no tempo de liberação do contexto de Persistência.
Para mais detalhes, consulte este artigo .