O documento explica os cinco estados possíveis de uma entidade JPA: Transiente, Gerenciado, Destacado, Removido e suas características. Descreve como os métodos do EntityManager afetam o estado da entidade e como isso impacta a sincronização com o banco de dados.
Ciclo de vida das entidades JPA: entenda os estados transient, managed, detached e removed
1. Entenda o ciclo de vida das entidades JPA
Para quem trabalha com a especificação JPA (Java Persistence API), é muito importante
dominar o ciclo de vida das entidades e o estado que cada uma pode assumir. Neste artigo,
explicaremos esses conceitos para evitar que você cometa erros que podem gerar
comportamentos indesejados (e perigosos) no sistema.
Para explicarmos os conceitos, vamos considerar a entidade abaixo:
@Entity
public class Produto {
@Id
@GeneratedValue
private Long id;
private String nome;
//métodos getters e setters
}
e vamos considerar, também, o seguinte esquema:
Estado Transient (Transitório)
Quando damos um new em uma entidade anotada com @Entity (objeto-entidade), ela ainda
não é reconhecida pelo JPA porque ela nunca passou pelo gerenciador de
persistência EntityManager. A entidade também não possui um ID e, provavelmente, não existe
no banco de dados. Neste caso, não há garantia alguma de que os dados existentes no objeto-
entidade serão persistidos no banco quando o processamento do sistema terminar. Este
estado é conhecido como Transient.
Abaixo apresentamos um código onde o objeto-entidade (variável "p") está Transient. Nada
ocorre ao final da execução desse código.
EntityManager em = //Obtém o entity manager
em.getTransaction().begin();
//Estado Transient
Produto p = new Produto();
p.setNome("Macarrão");
em.getTransaction().commit();
Graficamente, podemos representar esse estado da seguinte forma:
Estado Managed (Gerenciado)
O estado Managed é o estado onde o JPA reconhece a existência de um objeto-entidade e,
consequentemente, o JPA garante que esse objeto terá representação idêntica no banco de
dados. Isso significa que qualquer alteração que você fizer nesse objeto será automaticamente
persistido no banco. Um detalhe importante: a fluxo de sincronização parte sempre do objeto
para o banco de dados, nunca ao contrário. Isso significa que o JPA não detecta alterações
realizadas diretamente no banco de dados.
De acordo com a figura sobre o ciclo de vida das entidades (1ª figura), para que um objeto-
2. entidade vá para o estado Managed, os métodos persist, merge, find ou getReference devem
ser invocados para avisar aoEntityManager sobre a existência do objeto e para que este seja
controlado por aquele.
Abaixo temos um código que coloca a variável "p" sob gestão do EntityManager.
EntityManager em = //Obtém o entity manager
em.getTransaction().begin();
//Estado Transient
Produto p = new Produto();
p.setNome("Macarrão");
//Estado Managed
em.persist(p); //momento 1
p.setNome("Macarrão Integral"); //momento 2
em.getTransaction().commit(); //momento 3
No "momento 1" (execução do método persist), o JPA irá realizar um INSERT no banco de
dados com o nome "Macarrão", irá recuperar o ID gerado pelo banco (por causa da
anotação @GeneratedValue), irá atribuir esse valor à propriedade anotada com
o @Id (propriedade "id") e irá colocar o objeto "p" sob gestão do EntityManager (o estado muda
para Managed).
No "momento 2" (apenas a execução de um set), houve uma alteração na propriedade nome.
Como o objeto já está sendo gerenciado pelo EntityManager, o objeto é marcado como "sujo"
para ser atualizado futuramente. Observe que não é necessário um novo persist
(ou merge) porque o objeto já está sendo gerenciado pelo EntityManager.
No "momento 3" ocorre o commit. O JPA dispara um UPDATE no banco de dados para manter
uma representação idêntica no banco - o nome do produto passa a ser "Macarrão Integral".
É importante ressaltar que a execução do persist dispara, imediatamente, um INSERT para o
banco de dados (ocorre na hora). Por outro lado, por questões de economia, quando um objeto
gerenciado sofre uma alteração em uma propriedade (um set), o UPDATE para o banco de
dados é adiado até a execução de umcommit (o UPDATE não ocorre na hora).
Graficamente, podemos representar o "momento 1", o "momento 2" e o "momento 3" da
seguinte forma:
Abaixo apresentamos um outro exemplo de código que obtém um Produto através do
método find e atualiza o seu nome para "Macarrão de Ovos". Observe que, após a alteração do
nome do produto, não é necessário fazer uma chamada merge para que os dados sejam
persistidos porque o método find já coloca o objeto-entidade "p" em estado gerenciado.
Novamente, o JPA irá garantir uma fiel representação desse objeto no banco e realiza um
UPDATE automaticamente após o commit.
EntityManager em = //Obtém o entity manager
em.getTransaction().begin();
//Estado Managed
Produto p = em.find(Produto.class, 1);
p.setNome("Macarrão de Ovos");
em.getTransaction().commit();
A característica do estado Managed é que o objeto-entidade possui um ID, é conhecido
pelo EntityManagere possui a informação no banco de dados.
3. Estado Detached (Destacado)
Imagine que você tenha uma funcionalidade com várias telas organizadas em abas
(estilo Wizard) e que o usuário precisa acrescentar dados em todas elas até uma confirmação
final. Imagine também que você tenha um objeto-entidade gerenciado que esteja sendo
utilizado nessas abas. Durante a navegação, esse objeto-entidade sofre vários sets em seus
atributos (e cada aba tem um commit) até a confirmação final (última aba). Como o objeto está
gerenciado pelo EntityManager, cada tela concluída irá disparar um UPDATE no banco,
aumentando a quantidade de round trips para o banco e prejudicando o desempenho do
sistema.
O objetivo do método detach é retirar, temporariamente, o objeto-entidade gerenciado para um
estado não gerenciado para que problemas descritos no cenário acima não ocorram. A
característica desse estado é que os objetos-gerenciados possuem um ID, provavelmente
possui a informação no banco, mas não está sendo gerenciado pelo EntityManager. Se você
também dar um new em um objeto-entidade e atribuir manualmente um ID a ele, ele também
torna-se um objeto destacado.
Abaixo temos um código que retira a variável "p" da gestão do EntityManager.
EntityManager em = //Obtém o entity manager
em.getTransaction().begin();
//Estado Managed
Produto p = em.find(Produto.class, 1);
//Estado Detached
em.detach(p);
p.setNome("Macarrão Instantâneo");
em.getTransaction().commit();
Graficamente, podemos representar o resultado do código acima da seguinte forma:
Quando o usuário finalizar todo o processo clicando no botão salvar da última aba, desejamos
sincronizar as diferenças de "p" com o banco de dados chamando o método merge. Aqui tem
uma pegadinha que devemos tomar cuidado: ao executar esse método, o JPA faz um SELECT
no banco, cria um outro objeto-entidade chamada "p1" com os valores encontrados no banco;
coloca esse objeto em estado gerenciado (Managed); atribui as diferença de valores de "p" a
"p1" e; como "p1" está gerenciado e houve alteração de propriedade, ele torna-se "sujo" para
um UPDATE futuro (após um commit). Observe que "p" continua destacado e qualquer
alteração que você realizar sobre ele não surtirá efeito no banco. Observe o código abaixo:
EntityManager em = //Obtém o entity manager
em.getTransaction().begin();
//Estado Managed
Produto p = em.find(Produto.class, 1);
//Estado Detached
em.detach(p);
p.setNome("Macarrão Instantâneo");
//Momento 1 e 2.
em.merge(p);
//Momento 3.
p.setNome("Macarrão de Microondas");
4. //Momento 4
em.getTransaction().commit();
Veja abaixo a explicação dos momentos 1, 2, 3 e 4:
É um erro comum o programador achar que, após um merge, o objeto-entidade volta ao estado
gerenciado. Como consequência, as informações inseridas no objeto-entidade após
o merge serão perdidas, como ocorreu com a informação "Macarrão de Microondas". Para
resolver esse problema, devemos atribuir a "p" o resultado o método merge, que retorna a
instância "p1" gerenciada e recém criada. Segue o código correto:
EntityManager em = //Obtém o entity manager
em.getTransaction().begin();
//Estado Managed
Produto p = em.find(Produto.class, 1);
//Estado Detached
em.detach(p);
p.setNome("Macarrão Instantâneo");
//Dados sincronizados no banco.
p = em.merge(p);
//Agora "p" está gerenciado (Managed)
e a alteração abaixo é salva no banco
p.setNome("Macarrão de Microondas");
em.getTransaction().commit();
Há também os métodos clear e close que também colocam os objetos-entidades em
estado Detached. A diferença entre eles é que o clear coloca todos os objetos-entidades
gerenciados pelo EntityManager para o estado Detached sem matar o gerenciador (em outras
palavras, podemos dizer que o clear "bota todo mundo pra fora" sem fechar a firma). O
método close, além de "botar todo mundo pra fora", ele mata oEntityManager (a firma fecha).
Estado Removed (Removido)
O último estado, Removed, é quando queremos excluir um objeto-entidade do banco. A
característica de um objeto removido é que ele possui um ID, não está mais gerenciado
pelo EntityManager e não está mais no banco de dados. Observe na figura sobre o ciclo de
vida das entidades (1ª figura) que o objeto-entidade só pode ser removido se estiver, primeiro,
em um estado Gerenciado.
Abaixo ocorre um erro se você tentar executar o seguinte código:
EntityManager em = //Obtém o entity manager
em.getTransaction().begin();
//Estado Transient
Produto p = new Produto();
//Estado Detached
p.setId(1);
em.remove(p); //ERRO: "Detached" não passa para "Removed".
em.getTransaction().commit();
5. Por fim, o correto deveria ser:
EntityManager em = //Obtém o entity manager
em.getTransaction().begin();
//Estado Transient
Produto p = new Produto();
//Estado Detached
p.setId(1);
em.remove(em.merge(p)); //OK: merge coloca "p" em estado "Managed".
em.getTransaction().commit();
Concluímos nosso primeiro estudo sobre Persistência considerando a especificação JPA e
esperamos que o assunto abordado facilite o seu dia-a-dia nesse complexo mundo Object-
Relational Mapping (ORM).