Como essa é uma pergunta muito comum, escrevi
este artigo , no qual essa resposta se baseia.
Estados da entidade
A JPA define os seguintes estados da entidade:
Novo (transitório)
Um objeto recém-criado que nunca foi associado a um Hibernate Session
(aka Persistence Context
) e não está mapeado para nenhuma linha da tabela de banco de dados é considerado no estado Novo (Transitório).
Para nos tornarmos persistentes, precisamos chamar explicitamente o EntityManager#persist
método ou fazer uso do mecanismo de persistência transitiva.
Persistente (gerenciado)
Uma entidade persistente foi associada a uma linha da tabela de banco de dados e está sendo gerenciada pelo Contexto de Persistência atualmente em execução. Qualquer alteração feita em uma entidade será detectada e propagada no banco de dados (durante o tempo de liberação da sessão).
Com o Hibernate, não precisamos mais executar instruções INSERT / UPDATE / DELETE. O Hibernate emprega um estilo de trabalho de write-behind transacional e as alterações são sincronizadas no último momento responsável, durante o Session
tempo de liberação atual .
Independente
Depois que o contexto de persistência em execução no momento é fechado, todas as entidades gerenciadas anteriormente ficam desanexadas. As alterações sucessivas não serão mais rastreadas e nenhuma sincronização automática de banco de dados ocorrerá.
Transições de estado da entidade
Você pode alterar o estado da entidade usando vários métodos definidos pela EntityManager
interface.
Para entender melhor as transições de estado da entidade JPA, considere o seguinte diagrama:
Ao usar o JPA, para reassociar uma entidade desanexada a uma ativa EntityManager
, você pode usar a operação de mesclagem .
Ao usar a API nativa do Hibernate, além de merge
, você pode reconectar uma entidade desanexada a uma sessão ativa do Hibernate usando os métodos de atualização, conforme demonstrado no diagrama a seguir:
Mesclando uma entidade desanexada
A mesclagem copiará o estado da entidade desanexada (origem) para uma instância da entidade gerenciada (destino).
Considere que persistimos a Book
entidade a seguir e agora a entidade é desanexada, pois o EntityManager
que foi usado para persistir a entidade foi fechada:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
Enquanto a entidade está no estado desanexado, nós a modificamos da seguinte maneira:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
Agora, queremos propagar as alterações no banco de dados, para que possamos chamar o merge
método:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
E o Hibernate irá executar as seguintes instruções 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
Se a entidade mesclada não tiver equivalente no atual EntityManager
, uma nova captura instantânea da entidade será buscada no banco de dados.
Uma vez que existe uma entidade gerenciada, a JPA copia o estado da entidade desanexada para a que atualmente é gerenciada e, durante o Contexto de Persistênciaflush
, um UPDATE será gerado se o mecanismo de verificação sujo descobrir que a entidade gerenciada foi alterada.
Portanto, ao usar merge
, a instância do objeto desanexado continuará desanexada, mesmo após a operação de mesclagem.
Anexando novamente uma entidade desanexada
O Hibernate, mas não o JPA, oferece suporte à recolocação através do update
método.
Um Hibernate Session
pode associar apenas um objeto de entidade para uma determinada linha do banco de dados. Isso ocorre porque o contexto de persistência atua como um cache na memória (cache de primeiro nível) e apenas um valor (entidade) é associado a uma determinada chave (tipo de entidade e identificador de banco de dados).
Uma entidade pode ser reconectada apenas se não houver outro objeto da JVM (correspondente à mesma linha do banco de dados) já associado ao Hibernate atual Session
.
Considerando que mantivemos a Book
entidade e a modificamos quando a Book
entidade estava no estado desanexado:
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 reconectar a entidade desanexada assim:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
E o Hibernate executará a seguinte instrução SQL:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
O update
método requer que você entre unwrap
no EntityManager
Hibernate Session
.
Diferentemente merge
, a entidade desanexada fornecida será reassociada ao contexto de persistência atual e um UPDATE é agendado durante a liberação, independentemente de a entidade ter modificado ou não.
Para evitar isso, você pode usar a @SelectBeforeUpdate
anotação Hibernate, que acionará uma instrução SELECT que buscou o estado carregado, que é usado pelo mecanismo de verificação suja.
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
Cuidado com a NonUniqueObjectException
Um problema que pode ocorrer update
é se o contexto de persistência já contiver 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)
Conclusão
O merge
método deve ser preferido se você estiver usando o bloqueio otimista, pois permite evitar atualizações perdidas. Para mais detalhes sobre este tópico, consulte este artigo .
A update
é bom para atualizações em lote como ele pode impedir que a instrução SELECT adicional gerado pela merge
operação, reduzindo assim o tempo de execução de atualização em lote.
refresh()
entidades desanexadas? Examinando a especificação 2.0, não vejo justificativa; apenas que não é permitido.