EntityManager.merge()
pode inserir novos objetos e atualizar os existentes.
Por que alguém iria querer usar persist()
(que só pode criar novos objetos)?
EntityManager.merge()
pode inserir novos objetos e atualizar os existentes.
Por que alguém iria querer usar persist()
(que só pode criar novos objetos)?
Respostas:
De qualquer forma, você adicionará uma entidade a um PersistenceContext, a diferença está no que você faz com a entidade posteriormente.
Persist pega uma instância de entidade, adiciona-a ao contexto e torna essa instância gerenciada (ou seja, futuras atualizações na entidade serão rastreadas).
A mesclagem retorna a instância gerenciada à qual o estado foi mesclado. Ele retorna algo que existe no PersistenceContext ou cria uma nova instância da sua entidade. De qualquer forma, ele copiará o estado da entidade fornecida e retornará a cópia gerenciada. A instância que você enviar não será gerenciada (quaisquer alterações que você fizer não farão parte da transação - a menos que você ligue para mesclar novamente). Você pode usar a instância retornada (gerenciada).
Talvez um exemplo de código ajude.
MyEntity e = new MyEntity();
// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database
// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)
Os cenários 1 e 3 são aproximadamente equivalentes, mas há algumas situações em que você deseja usar o cenário 2.
merge
a cópia completa de um objeto antes de gerenciá-lo tenha um impacto no desempenho?
@GeneratedId
posso obtê-lo no cenário 2?
Persistir e mesclar são para dois propósitos diferentes (eles não são alternativas).
(editado para expandir informações sobre diferenças)
persistir:
mesclar:
eficiência persist ():
semântica persist ():
Exemplo:
{
AnyEntity newEntity;
AnyEntity nonAttachedEntity;
AnyEntity attachedEntity;
// Create a new entity and persist it
newEntity = new AnyEntity();
em.persist(newEntity);
// Save 1 to the database at next flush
newEntity.setValue(1);
// Create a new entity with the same Id than the persisted one.
AnyEntity nonAttachedEntity = new AnyEntity();
nonAttachedEntity.setId(newEntity.getId());
// Save 2 to the database at next flush instead of 1!!!
nonAttachedEntity.setValue(2);
attachedEntity = em.merge(nonAttachedEntity);
// This condition returns true
// merge has found the already attached object (newEntity) and returns it.
if(attachedEntity==newEntity) {
System.out.print("They are the same object!");
}
// Set 3 to value
attachedEntity.setValue(3);
// Really, now both are the same object. Prints 3
System.out.println(newEntity.getValue());
// Modify the un attached object has no effect to the entity manager
// nor to the other objects
nonAttachedEntity.setValue(42);
}
Dessa maneira, existe apenas 1 objeto anexado para qualquer registro no gerenciador de entidades.
mesclar () para uma entidade com um ID é algo como:
AnyEntity myMerge(AnyEntity entityToSave) {
AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
if(attached==null) {
attached = new AnyEntity();
em.persist(attached);
}
BeanUtils.copyProperties(attached, entityToSave);
return attached;
}
Embora se conectado ao MySQL merge () possa ser tão eficiente quanto persist () usando uma chamada para INSERT com a opção ON DUPLICATE KEY UPDATE, o JPA é uma programação de nível muito alto e você não pode supor que esse seja o caso em todos os lugares.
em.persist(x)
com x = em.merge(x)
?
merge()
também pode jogar um #EntityExistsException
RuntimeException
, mas não é mencionado no Javadoc.
Se você estiver usando o gerador designado, usar mesclagem em vez de persistir pode causar uma instrução SQL redundante , afetando o desempenho.
Além disso, chamar a mesclagem para entidades gerenciadas também é um erro, pois as entidades gerenciadas são gerenciadas automaticamente pelo Hibernate e seu estado é sincronizado com o registro do banco de dados pelo mecanismo de verificação suja ao liberar o Contexto de Persistência .
Para entender como tudo isso funciona, primeiro você deve saber que o Hibernate muda a mentalidade do desenvolvedor de instruções SQL para transições de estado de entidade .
Depois que uma entidade for gerenciada ativamente pelo Hibernate, todas as alterações serão propagadas automaticamente para o banco de dados.
O Hibernate monitora as entidades atualmente conectadas. Mas, para que uma entidade seja gerenciada, ela deve estar no estado correto da entidade.
Para entender melhor as transições de estado da JPA, é possível visualizar o seguinte diagrama:
Ou se você usa a API específica do Hibernate:
Conforme ilustrado pelos diagramas acima, uma entidade pode estar em um dos quatro estados a seguir:
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 se tornar persistente, 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á.
Para associar uma entidade desanexada a uma sessão de hibernação ativa, você pode escolher uma das seguintes opções:
Recolocação
O Hibernate (mas não o JPA 2.1) oferece suporte à recolocação através do método de atualização Session #. Uma sessão do Hibernate pode associar apenas um objeto de entidade a 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 (correspondendo à mesma linha do banco de dados) já associado à sessão atual do Hibernate.
Mesclando
A mesclagem copiará o estado da entidade desanexada (origem) para uma instância da entidade gerenciada (destino). Se a entidade mesclada não tiver equivalente na Sessão atual, uma será buscada no banco de dados. A instância do objeto desanexado continuará desanexada mesmo após a operação de mesclagem.
Removido
Embora o JPA exija que apenas as entidades gerenciadas possam ser removidas, o Hibernate também pode excluir entidades desanexadas (mas apenas por meio de uma chamada de método de exclusão da Sessão #). Uma entidade removida está agendada apenas para exclusão e a instrução DELETE real do banco de dados será executada durante o tempo de liberação da Sessão.
Percebi que, quando usei em.merge
, recebi uma SELECT
declaração para cada um INSERT
, mesmo quando não havia um campo que o JPA estava gerando para mim - o campo da chave primária era um UUID que eu mesmo defini. Eu mudei para em.persist(myEntityObject)
e recebi apenas INSERT
declarações então.
merge()
. Eu tinha o banco de dados PostgreSQL com visão complicada : a visão agregava dados de várias tabelas (as tabelas tinham estrutura idêntica, mas com nomes diferentes). Então, o JPA tentou fazer merge()
, mas, na verdade, o JPA fez primeiro SELECT
(o banco de dados devido às configurações de exibição poderia retornar vários registros com a mesma chave primária de tabelas diferentes!), Depois o JPA (o Hibernate foi uma implementação) falhou: há vários registros com a mesma chave ( org.hibernate.HibernateException: More than one row with the given identifier was found
) No meu caso persist()
me ajudou.
A especificação JPA diz o seguinte sobre persist()
.
Se X for um objeto desanexado, ele
EntityExistsException
poderá ser lançado quando a operação persistir for invocada ou aEntityExistsException
outraPersistenceException
poderá ser lançada no momento da liberação ou confirmação.
Portanto, o uso persist()
seria adequado quando o objeto não deveria ser um objeto desanexado. Você pode preferir que o código lance o código PersistenceException
para que ele falhe rapidamente.
Embora a especificação não seja clara , persist()
pode definir o @GeneratedValue
@Id
para um objeto. merge()
no entanto, deve ter um objeto com o @Id
já gerado.
merge()
no entanto, deve ter um objeto com o @Id
já gerado . ". Sempre que o EntityManager não encontra um valor para o campo do ID do objeto, ele é persistido (inserido) no banco de dados.
Mais alguns detalhes sobre a mesclagem que ajudarão você a usar a mesclagem persistir:
Retornar uma instância gerenciada que não seja a entidade original é uma parte crítica do processo de mesclagem. Se uma instância de entidade com o mesmo identificador já existir no contexto de persistência, o provedor substituirá seu estado pelo estado da entidade que está sendo mesclada, mas a versão gerenciada que já existia deverá ser retornada ao cliente para que possa ser usava. Se o provedor não atualizar a instância Employee no contexto de persistência, quaisquer referências a essa instância se tornarão inconsistentes com o novo estado que está sendo mesclado.
Quando merge () é chamado em uma nova entidade, ele se comporta de maneira semelhante à operação persist (). Ele adiciona a entidade ao contexto de persistência, mas, em vez de adicionar a instância da entidade original, cria uma nova cópia e gerencia essa instância. A cópia criada pela operação merge () é mantida como se o método persist () tivesse sido chamado nela.
Na presença de relacionamentos, a operação merge () tentará atualizar a entidade gerenciada para apontar para versões gerenciadas das entidades referenciadas pela entidade desanexada. Se a entidade tiver um relacionamento com um objeto que não tem identidade persistente, o resultado da operação de mesclagem será indefinido. Alguns provedores podem permitir que a cópia gerenciada aponte para o objeto não persistente, enquanto outros podem lançar uma exceção imediatamente. A operação merge () pode ser opcionalmente em cascata nesses casos para impedir que uma exceção ocorra. Abordaremos a cascata da operação merge () posteriormente nesta seção. Se uma entidade que está sendo mesclada aponta para uma entidade removida, uma exceção IllegalArgumentException será lançada.
Os relacionamentos de carregamento lento são um caso especial na operação de mesclagem. Se um relacionamento de carregamento lento não foi acionado em uma entidade antes de ser desanexado, esse relacionamento será ignorado quando a entidade for mesclada. Se o relacionamento foi acionado enquanto gerenciado e, em seguida, definido como nulo enquanto a entidade foi desanexada, a versão gerenciada da entidade também terá o relacionamento limpo durante a mesclagem. "
Todas as informações acima foram obtidas do "Pro JPA 2 Mastering the Java ™ Persistence API" por Mike Keith e Merrick Schnicariol. Capítulo 6. Desanexação e Mesclagem de Seção. Este livro é na verdade um segundo livro dedicado à JPA pelos autores. Este novo livro tem muitas informações novas e mais antigas. Eu realmente recomendo ler este livro para aqueles que se envolverão seriamente com a JPA. Sinto muito por postar anonimamente minha primeira resposta.
Existem mais algumas diferenças entre merge
e persist
(vou enumerar novamente as que já foram postadas aqui):
D1 merge
não torna a entidade transmitida gerenciada, mas retorna outra instância que é gerenciada. persist
por outro lado, fará com que a entidade transmitida seja gerenciada:
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
D2 Se você remover uma entidade e decidir persistir com a entidade de volta, poderá fazê-lo apenas com persist (), porque merge
lançará um IllegalArgumentException
.
D3 Se você decidiu cuidar manualmente de seus IDs (por exemplo, usando UUIDs), uma merge
operação acionará SELECT
consultas subseqüentes para procurar entidades existentes com esse ID, embora persist
possam não ser necessárias.
D4 Há casos em que você simplesmente não confia no código que o chama e, para garantir que nenhum dado seja atualizado, mas inserido, você deve usá-lo persist
.
Eu estava recebendo exceções de LazyLoading na minha entidade porque estava tentando acessar uma coleção carregada de Lazy que estava em sessão.
O que eu faria era em uma solicitação separada, recuperava a entidade da sessão e tentava acessar uma coleção na minha página jsp, o que era problemático.
Para aliviar isso, atualizei a mesma entidade no meu controlador e a passei para o meu jsp, embora eu imagine que, quando eu salvei novamente na sessão, ele também estará acessível SessionScope
e não lançará uma LazyLoadingException
, uma modificação do exemplo 2:
O seguinte funcionou para mim:
// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"
//access e from jsp and it will work dandy!!
Eu achei esta explicação dos documentos do Hibernate esclarecedora, porque eles contêm um caso de uso:
O uso e a semântica de merge () parecem confusos para novos usuários. Em primeiro lugar, desde que você não esteja tentando usar o estado do objeto carregado em um gerenciador de entidades em outro novo gerenciador de entidades, não será necessário usar merge () . Alguns aplicativos inteiros nunca usarão esse método.
Normalmente, merge () é usado no seguinte cenário:
- O aplicativo carrega um objeto no primeiro gerenciador de entidades
- o objeto é passado para a camada de apresentação
- algumas modificações são feitas no objeto
- o objeto é passado de volta para a camada de lógica de negócios
- o aplicativo persiste essas modificações chamando merge () em um segundo gerenciador de entidades
Aqui está a semântica exata de merge ():
- se houver uma instância gerenciada com o mesmo identificador atualmente associado ao contexto de persistência, copie o estado do objeto fornecido na instância gerenciada
- se não houver uma instância gerenciada atualmente associada ao contexto de persistência, tente carregá-la no banco de dados ou crie uma nova instância gerenciada
- a instância gerenciada é retornada
- a instância fornecida não se associa ao contexto de persistência, permanece desanexada e geralmente é descartada
De: http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
Analisando as respostas, faltam alguns detalhes sobre a geração em cascata e identificação. Ver pergunta
Além disso, vale ressaltar que você pode ter Cascade
anotações separadas para mesclar e persistir: Cascade.MERGE
e Cascade.PERSIST
que serão tratadas de acordo com o método usado.
A especificação é sua amiga;)
O JPA é indiscutivelmente uma grande simplificação no domínio de aplicativos corporativos criados na plataforma Java. Como desenvolvedor que teve que lidar com as complexidades dos beans de entidade antigos no J2EE, vejo a inclusão do JPA entre as especificações do Java EE como um grande salto em frente. No entanto, ao me aprofundar nos detalhes da JPA, encontro coisas que não são tão fáceis. Neste artigo, trato da comparação dos métodos de mesclagem e persistência do EntityManager, cujo comportamento sobreposto pode causar confusão não apenas a um novato. Além disso, proponho uma generalização que vê ambos os métodos como casos especiais de um método mais geral combinados.
Entidades persistentes
Em contraste com o método de mesclagem, o método persist é bastante direto e intuitivo. O cenário mais comum do uso do método persist pode ser resumido da seguinte forma:
"Uma instância recém-criada da classe de entidade é passada para o método persistente. Depois que esse método retorna, a entidade é gerenciada e planejada para inserção no banco de dados. Isso pode acontecer antes ou depois da confirmação da transação ou quando o método de liberação é chamado. Se a entidade referenciar outra entidade por meio de um relacionamento marcado com a estratégia em cascata PERSIST, esse procedimento também será aplicado a ela. "
A especificação entra mais em detalhes, no entanto, lembrá-los não é crucial, pois esses detalhes abrangem apenas situações mais ou menos exóticas.
Entidades de fusão
Em comparação com a persistência, a descrição do comportamento da mesclagem não é tão simples. Não há cenário principal, como no caso de persistir, e um programador deve lembrar de todos os cenários para escrever um código correto. Parece-me que os designers da JPA queriam ter algum método cuja principal preocupação fosse lidar com entidades desanexadas (como o oposto do método persistente que lida principalmente com entidades recém-criadas.) A principal tarefa do método de mesclagem é transferir o estado de um entidade não gerenciada (passada como argumento) para sua contraparte gerenciada no contexto de persistência. Essa tarefa, no entanto, divide-se ainda mais em vários cenários que pioram a inteligibilidade do comportamento geral do método.
Em vez de repetir parágrafos da especificação JPA, preparei um diagrama de fluxo que descreve esquematicamente o comportamento do método de mesclagem:
Então, quando devo usar persistir e quando mesclar?
persistir
fundir
Cenário X:
Tabela: Spitter (Um), Tabela: Spittles (Muitos) (Spittles é o proprietário do relacionamento com um FK: spitter_id)
Esse cenário resulta em economia: O Spitter e os dois Spittles como se pertencessem ao Mesmo Spitter.
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.addSpittle(spittle3); // <--persist
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Cenário Y:
Isso salvará o Spitter, salvará os 2 Spittles Mas eles não farão referência ao mesmo Spitter!
Spitter spitter=new Spitter();
Spittle spittle3=new Spittle();
spitter.setUsername("George");
spitter.setPassword("test1234");
spittle3.setSpittle("I love java 2");
spittle3.setSpitter(spitter);
dao.save(spittle3); // <--merge!!
Spittle spittle=new Spittle();
spittle.setSpittle("I love java");
spittle.setSpitter(spitter);
dao.saveSpittle(spittle); //<-- merge!!
Outra observação:
merge()
só se preocupará com um ID gerado automaticamente (testado IDENTITY
e SEQUENCE
) quando um registro com esse ID já existir em sua tabela. Nesse caso merge()
, tentará atualizar o registro. Se, no entanto, um ID estiver ausente ou não corresponder a nenhum registro existente, merge()
o ignorará completamente e solicitará ao banco de dados que aloque um novo. Às vezes, isso é fonte de muitos bugs. Não use merge()
para forçar um ID para um novo registro.
persist()
por outro lado, nunca permitirá que você passe um ID para ele. Falhará imediatamente. No meu caso, é:
Causado por: org.hibernate.PersistentObjectException: entidade desanexada passada para persistir
O hibernate-jpa javadoc tem uma dica:
Lança : javax.persistence.EntityExistsException - se a entidade já existir. (Se a entidade já existir, a EntityExistsException poderá ser lançada quando a operação persistir for invocada ou a EntityExistsException ou outra PersistenceException poderá ser lançada no momento da liberação ou confirmação.)
persist()
não reclamará que possui um ID, apenas reclama quando algo com o mesmo ID já estiver no banco de dados.
Você pode ter vindo aqui para obter conselhos sobre quando usar persistir e quando usar mesclagem . Eu acho que depende da situação: qual a probabilidade de você precisar criar um novo registro e qual a dificuldade de recuperar dados persistentes.
Vamos supor que você possa usar uma chave / identificador natural.
Os dados precisam persistir, mas de vez em quando existe um registro e é necessária uma atualização. Nesse caso, você pode tentar persistir e, se ele gerar uma EntityExistsException, procure e combine os dados:
tente {entityManager.persist (entity)}
catch (exceção EntityExistsException) {/ * recuperar e mesclar * /}
Os dados persistentes precisam ser atualizados, mas de vez em quando ainda não há registro para os dados. Nesse caso, você procura e persiste se a entidade estiver ausente:
Entidade = entityManager.find (chave);
if (entity == null) {entityManager.persist (entity); }
else {/ * mesclar * /}
Se você não tiver chave / identificador natural, será mais difícil descobrir se a entidade existe ou não, ou como procurá-la.
As mesclagens também podem ser tratadas de duas maneiras:
persist (entidade) deve ser usado com entidades totalmente novas, para adicioná-las ao DB (se a entidade já existir no DB, haverá lançamento de EntityExistsException).
a mesclagem (entidade) deve ser usada para colocar a entidade de volta ao contexto de persistência se a entidade foi desanexada e foi alterada.
Provavelmente persistir está gerando INSERT sql e mesclar UPDATE sql (mas não tenho certeza).