Entidades Managed, Transient e Detached no Hibernate e JPA

Distinguir entre os estados de uma entidade no JPA/Hibernate é difícil no início. Um objeto é dito transiente quando não tem representação no banco de dados e nem o EntityManager o conhece, como abaixo:

Cliente c = new Cliente();

Aqui, qualquer mudança no objeto referido por c não gerará nenhum tipo de insert ou update no banco de dados. O oposto é quando o objeto existe no banco de dados e o EntityManager em questão possui uma referência para ele, essa entidade está managed, gerenciada pelo EntityManager. Considere em uma referência a um EntityManager no seguinte exemplo:

Cliente c = new Cliente(); // transiente
em.persist(c); // gerenciado

Ou ainda:

Cliente c = em.find(Cliente.class, 1); // gerenciado

Quando uma entidade está managed, qualquer mudança em seu estado (como uma chamada de setter) resultará em uma atualização no banco de dados no momento do commit.

O último caso é quando a entidade representa algo que possivelmente está no banco de dados, mas o EntityManager o desconhece: a entidade está fora do contexto, detached. Exemplo:

Cliente c = new Cliente();
c.setId(1);

Uma entidade também está detached quando o EntityManager de onde tiramos esse Cliente (por exemplo, quando fizemos um find ou vindo de uma Query) já não está mais aberta. Qualquer mudança nessa referência obviamente não surtirá efeito no banco de dados. Para que essa mudança faça efeito, isto é, para reattach o entidade, antes precisamos amarrá-la ao contexto de persistência. Repare que no EntityManager já pode existir uma entidade Cliente com esse mesmo id, imagine então o que aconteceria se tivéssemos um método que se chamasse reattach ou update?

Por isso o método é o merge. Ele junta a possível entidade com mesmo id que se encontra no EntityManager com a passada como argumento, e devolve a que está managed. O método merge não faz reattach. Então:

Cliente c = new Cliente();
c.setId(1);
em.merge(c);
c.setNome("Cliente com nome alterado");

Não surtirá efeito! Aqui você precisava antes ter pego o que o merge devolveu. Repare na pequena alteração:

Cliente c = new Cliente();
c.setId(1);
c = em.merge(c);
c.setNome("Cliente com nome alterado");

Pronto. Uma pequena introdução sobre o ciclo de vida de uma entidade em relação a um EntityManager: transient (a especificação chama de new), managed e detached! Ainda temos o estado removed, quando uma entidade está marcada para a remoção.

27 Comentários

  1. Luca Bastos 24/11/2006 at 06:01 #

    Sua palestra tão elogiada será disponibilizada?

  2. Edufa 24/11/2006 at 07:22 #

    Olá apesar da grande quantidade de textos sobre hibernate/JPA, eu acho extremamente válido, é só ver no guj, hibernate responde por uma fatia considerável das dúvidas diárias (até acho q deveria ter um tópico só para hibernate, hehe), então qq ajuda é sempre bem vinda nesse assunto.

    Continuem assim!

  3. Diego Pires Plentz 26/11/2006 at 13:24 #

    “Apesar de terem reclamado que isto está parecendo um livro de Hibernate, vou escrever aqui a introdução mais curta da história da JPA, baseado na palestra que dei sobre esse assunto no ConexãoJava”

    Muito boa, por sinal. Pena que não peguei desde o começo. PS: A Caelum já ta com curso baseada nela né? Não? Tão esperando o que?

  4. Fabio Kung 29/11/2006 at 05:58 #

    No curso as entidades são mapeadas com as anotações do javax.persistence, mas ainda usamos Session/SessionFactory ! 😀

  5. Pamela 10/03/2007 at 09:33 #

    Só fiquei com uma dúvida. Você está mostrando que, para recuperar um objeto detached, você usou o merge, mas o merge também pode ser utilizado para fazer um update em um objeto, portanto, ao utilizarmos para recuperar, como ele vai saber que não deve ser feito também o update?

  6. Jean Michel Baldessar 27/05/2008 at 17:35 #

    Muito boa introdução, parabens!

    Amigo eu gostaria de saber o que eu devo fazer quando quero recuperar um objeto que já foi alterado mas ainda não foi enviado para o banco, da forma que ele está no banco. Ou seja: eu quero comparar o objeto da memória com o que está no banco, mas quando eu faço uma consulta no entityManager ele me retorna uma referencia para o objeto da memorio… assim eu não consigo acessar o que está no banco… como eu faço isso??

  7. Paulo Silveira 27/05/2008 at 17:40 #

    jean, chame o .clear() no seu entitymanager, assim na proxima invocacao ele nao vai tirar do 1st level cache

  8. bunny 24/11/2008 at 00:29 #

    massa d+++++ !!!!!!!!!!!!!! para que serve msm ???

    achei bonito o codigo,,, só isso

  9. diego 17/03/2009 at 08:05 #

    Como dúvida:

    Cliente c = em.find(Cliente.class, 1);
    c.setNome(“Cliente com nome alterado”);

    Não faz a função do merge?

  10. Paulo Silveira 17/03/2009 at 08:09 #

    Diego, infelizmente nao faz. Toda informação que estava nao objeto o qual a referência Cliente c apontava antes do find seria perdidade nesse caso.

  11. Diego 17/03/2009 at 12:15 #

    Sim sim, o que eu quis dizer não é a referência antiga, e sim trazer um novo cliente gerenciado e alterar o nome, já que só foi isso alterado..

  12. Diego 17/03/2009 at 12:32 #

    Algo como

    Cliente c = [1, “Diego”, 22] ( id = 1, nome = “Diego”, idade = 22]

    Desejo alterar o nome para Diego Raphael;

    Cliente client = em.find(Cliente.class, 1);
    client.setNome(“Diego Raphael”);

    Não é o mesmo que:

    Cliente cliente = new Cliente();
    cliente.setId(1);
    cliente = em.merge(cliente);
    cliente.setNome(“Diego Raphael”);

  13. Paulo Silveira 19/03/2009 at 02:06 #

    Oi, sem duvida Diego. É o mesmo sim.

    Acho que faltou eu dar a motivacao para mostrar a necessidade do merge, e ele só vai ser realmente necessario quando tivermos uma instancia detached com informacoes novas e precisamos refleti-las no BD. Imagine que o cliente estivesse com mais atributos ja populados do que apenas o ID, como por exemplo o endereco, mas voce nem sabe mais quais dados estao novos e quais nao (no caso de vir de um wizard na web, isso acontece sempre):


    Cliente cliente = em.find(Cliente.class, 1);
    cliente.setEndereco(" endereco novo" );

    // depois de alguns requests na web, preenchendo o wizard, voce quer atualiza-lo e ainda alterar mais alguns dados.
    // lembre-se que o cliente aqui ja esta detached ja que usamos Open-Session-In-View

    cliente = em.merge(cliente);
    cliente.setNome(”Diego Raphael”);

    Agora ele estara com endereço e nome atualizados. Se voce fizesse find novamente, perderia o endereço, claro.

  14. Diego Raphael 23/03/2009 at 02:07 #

    Entendi Paulo,

    Se faz útil a utilização do merge no caso de um POJO vinculado ao ManagedBean, onde será alterado as informações mostrada pelo jsf, ou algo do gênero.

    Abraços!

  15. Paulo Silveira 23/03/2009 at 02:55 #

    Diego, exemplo perfeito, ja que se o managed bean estiver configurado como session scope, e seu ciclo de vida do hibernate for request, as entidades referenciadas pelo managed bean estarao detached na proxima requisicao, sendo necessario um merge, caso contrario voce perdera as informacoes atualizadas na requisicao anterior!

  16. Mayara 17/09/2009 at 18:36 #

    Otima explicação, de todas que li hoje essa foi a que realmente me ajudou com os conceitos.

    Obrigada e parabens pelo post resumido
    ehehhe

  17. Thiago 11/11/2009 at 23:15 #

    Como faço para salvar um objeto transient que já existe no banco?

    Digamos que na tabela cliente eu tenha o seguinte registro:

    ID = 2
    Nome = “Nome antigo do cliente”

    Cliente c = new Cliente();
    c.setId(2);
    c.setNome(“Nome novo do cliente”);

    Qual comando devo executar para que o Hibernate gere o seguinte comando SQL

    UPDATE Cliente SET Nome=’Nome novo do cliente’ WHERE ID=2

    Ou algo similar?

    Qualquer ajuda será bem-vinda!

    Grato

  18. Paulo Silveira 12/11/2009 at 11:33 #

    oi Thiago!

    É exatamente esse caso que voce pode usar o .merge()!

    abracos

  19. Leandro 16/10/2011 at 17:41 #

    Qual seria a melhor forma de contornar esse problema:

    Utilizando em uma aplicação SpringRoo e JSF 2.0, como o SpringRoo trabalha com JPA, em uma lista “DataTable”
    mostro os clientes cadastrados ao clicar no botão alterar carrego os dados do cliente em um formulario para alteração ao clicar no botão salvar mesmo o cliente ja possuindo um id o metodo merge esta inserindo um novo cliente ao inves de alterar, se mudar o scope do controller jsf de request para session funcionar normal, como devo proceder utilizando o scope request?

    obrigado

  20. Vinícius 04/12/2011 at 21:36 #

    Excelente artigo! Acabou de resolver horas de pesquisa em forums e sites!

    A solução abaixo resolveu o problema:

    Cliente c = new Cliente();
    c.setId(1);
    c = em.merge(c);
    c.setNome(“Cliente com nome alterado”);

  21. bruno 19/06/2012 at 16:13 #

    a minha dúvida é a seguinte, se eu tenho entidade Cliente com as propriedades id, nome e endereco. A entidade vem preenchida da minha página com somente id e nome, se eu fizer:
    em.merge(cliente);
    e a entidade do bando tiver um endereco preenchido, o endereco ficará null?

  22. Paulo Silveira 19/06/2012 at 18:14 #

    oi Bruno. Ficará sim null, infelizmente. Você é o responsável por cuidar disso. O hibernate/jpa não tem como saber, posi talvez você quisesse mesmo substituir o endereço velho por null. Como distinguir? então fica com você. As pessoas costumam criar objetos copiers, ou então usar o objeto buscado do banco de dados (via getReference, para nao ter de executar o select) e popular esse objeto, garantindo que dados velhos nao serao nulificados por dados nao alterados.

  23. ItaloBoss 01/10/2012 at 14:28 #

    Olá! Parabéns, excelente artigo!
    Tenho uma dúvida quanto ao “merge” de objetos mapeado com “foreign key”.

    Assim, tenho um classe X que está mapeada de 1-n com a classe Y e a X possui uma Collection ( que possui CascadeType.ALL ). E dessa mesma maneira a classe Y possui uma mesma relação da classe Z.

    A minha pergunta é: Se eu alterar um objeto Y ou Z e usar o “merge” em um objeto X relacionado e salvar X, as alterações de Y ou Z tbm serão salvas?

    X objX = new X();

    salva o objX e suas relações

    altera um objeto objZ relacionado ao objX

    objX = em.merge(objX);

    Funciona bem?
    Desde já, muito obrigado!

  24. Gabriel 29/11/2015 at 11:49 #

    Existe diferença / desvantagem para quem usa em.find ao invés de em.merge para fazer o reatach?

Deixe uma resposta