Hibernate: salvar, persistir, atualizar, mesclar, saveOrUpdate
1. Introdução
Neste artigo, discutiremos as diferenças entre vários métodos da interfaceSession:save,persist,update,merge,saveOrUpdate.
Esta não é uma introdução ao Hibernate e você já deve conhecer os conceitos básicos de configuração, mapeamento objeto-relacional e trabalho com instâncias de entidade. Para um artigo introdutório ao Hibernate, visite nosso tutorial emHibernate 4 with Spring.
Leitura adicional:
Procedimentos armazenados com o Hibernate
Este artigo discute brevemente como chamar procedimentos de armazenamento do Hibernate.
Uma visão geral dos identificadores no Hibernate
Aprenda a mapear identificadores de entidade com o Hibernate.
2. Implementação de Sessão como Contexto de Persistência
A interfaceSession possui vários métodos que eventualmente resultam em salvar dados no banco de dados:persist,save,update,merge,saveOrUpdate. Para entender a diferença entre esses métodos, devemos primeiro discutir o propósito doSession como um contexto de persistência e a diferença entre os estados das instâncias de entidade em relação aoSession.
Também devemos entender o histórico do desenvolvimento do Hibernate que levou a alguns métodos de API parcialmente duplicados.
2.1. Gerenciando Instâncias de Entidade
Além do próprio mapeamento objeto-relacional, um dos problemas que o Hibernate deveria solucionar é o problema de gerenciar entidades durante o tempo de execução. A noção de “contexto de persistência” é a solução do Hibernate para este problema. O contexto de persistência pode ser considerado como um contêiner ou um cache de primeiro nível para todos os objetos que você carregou ou salvou em um banco de dados durante uma sessão.
A sessão é uma transação lógica, cujos limites são definidos pela lógica comercial do seu aplicativo. Quando você trabalha com o banco de dados através de um contexto de persistência e todas as instâncias de sua entidade são anexadas a esse contexto, você deve sempre ter uma única instância de entidade para cada registro do banco de dados com o qual você interagiu durante a sessão.
No Hibernate, o contexto de persistência é representado pela instânciaorg.hibernate.Session. Para JPA, éjavax.persistence.EntityManager. Quando usamos o Hibernate como um provedor JPA e operamos via interfaceEntityManager, a implementação desta interface basicamente envolve o objetoSession subjacente. No entanto, o HibernateSession fornece uma interface mais rica com mais possibilidades, então às vezes é útil trabalhar comSession directly.
2.2. Estados de Instâncias de Entidade
Qualquer instância de entidade em seu aplicativo aparece em um dos três estados principais em relação ao contexto de persistênciaSession:
-
transient - esta instância não é, e nunca foi, anexada aSession; esta instância não possui linhas correspondentes no banco de dados; geralmente é apenas um novo objeto que você criou para salvar no banco de dados;
-
persistent - esta instância está associada a um objetoSession exclusivo; ao liberarSession para o banco de dados, essa entidade tem a garantia de ter um registro consistente correspondente no banco de dados;
-
detached - esta instância já foi anexada aSession (em um estadopersistent), mas agora não está mais; uma instância entra neste estado se você removê-la do contexto, limpar ou fechar a Sessão ou colocar a instância no processo de serialização / desserialização.
Aqui está um diagrama de estado simplificado com comentários sobre os métodosSession que fazem as transições de estado acontecerem.
Quando a instância da entidade está no estadopersistent, todas as alterações feitas nos campos mapeados desta instância serão aplicadas aos registros e campos do banco de dados correspondentes ao liberarSession. A instânciapersistent pode ser considerada “online”, enquanto a instânciadetached ficou “offline” e não é monitorada para alterações.
Isso significa que quando você altera os campos de um objetopersistent, você não precisa chamarsave,update ou qualquer um desses métodos para obter essas alterações no banco de dados: tudo que você precisa é confirmar a transação, ou liberar ou fechar a sessão, quando você terminar com ela.
2.3. Conformidade com a Especificação JPA
O Hibernate foi a implementação de Java ORM mais bem-sucedida. Não é de admirar que a especificação da API de persistência de Java (JPA) tenha sido fortemente influenciada pela API do Hibernate. Infelizmente, havia também muitas diferenças: algumas importantes, outras mais sutis.
Para atuar como uma implementação do padrão JPA, as APIs do Hibernate tiveram que ser revisadas. Vários métodos foram adicionados à interface Session para corresponder à interface do EntityManager. Esses métodos servem ao mesmo objetivo que os métodos "originais", mas estão em conformidade com a especificação e, portanto, apresentam algumas diferenças.
3. Diferenças entre as operações
É importante entender desde o início que todos os métodos (persist,save,update,merge,saveOrUpdate) não resultam imediatamente no instruções SQLUPDATE ouINSERT correspondentes. O salvamento real dos dados no banco de dados ocorre ao confirmar a transação ou liberarSession.
Os métodos mencionados basicamente gerenciam o estado das instâncias da entidade, fazendo a transição entre diferentes estados ao longo do ciclo de vida.
Como entidade de exemplo, usaremos uma entidade mapeada por anotação simplesPerson:
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String name;
// ... getters and setters
}
3.1. Persist
O métodopersist destina-se a adicionar uma nova instância de entidade ao contexto de persistência, ou seja, transição de uma instância do estado transiente parapersistent.
Geralmente chamamos isso quando queremos adicionar um registro ao banco de dados (persistir uma instância de entidade):
Person person = new Person();
person.setName("John");
session.persist(person);
O que acontece depois que o métodopersist é chamado? O objetoperson fez a transição do estadotransient parapersistent. O objeto está no contexto de persistência agora, mas ainda não foi salvo no banco de dados. A geração de instruçõesINSERT ocorrerá apenas ao confirmar a transação, liberar ou fechar a sessão.
Observe que o métodopersist tem o tipo de retornovoid. Ele opera no objeto passado "no lugar", alterando seu estado. A variávelperson faz referência ao objeto persistente real.
Este método é uma adição posterior à interface Session. A principal característica diferenciadora desse método é que ele está em conformidade com a especificação JSR-220 (persistência EJB). A semântica deste método é estritamente definida na especificação, que basicamente afirma que:
-
uma instânciatransient torna-sepersistent (e a operação cascata para todas as suas relações comcascade=PERSIST oucascade=ALL),
-
se uma instância já épersistent, então esta chamada não tem efeito para esta instância particular (mas ainda se desdobra em suas relações comcascade=PERSIST oucascade=ALL),
-
se uma instância fordetached, você deve esperar uma exceção, ao chamar este método ou ao confirmar ou liberar a sessão.
Observe que não há nada aqui relacionado ao identificador de uma instância. A especificação não afirma que o ID será gerado imediatamente, independentemente da estratégia de geração de ID. A especificação do métodopersist permite que a implementação emita instruções para gerar id no commit ou flush, e o id não é garantido como não nulo após chamar esse método, portanto, você não deve confiar nele.
Você pode chamar este método em uma instância jápersistent, e nada acontece. Mas se você tentar persistir uma instânciadetached, a implementação irá gerar uma exceção. No exemplo a seguir, nóspersist a entidade,evict do contexto para que se tornedetached, e então tentamospersist novamente. A segunda chamada parasession.persist() causa uma exceção, portanto, o código a seguir não funcionará:
Person person = new Person();
person.setName("John");
session.persist(person);
session.evict(person);
session.persist(person); // PersistenceException!
3.2. Save
O métodosave é um método “original” do Hibernate que não está em conformidade com a especificação JPA.
Seu propósito é basicamente o mesmo quepersist, mas tem detalhes de implementação diferentes. A documentação para esse método afirma estritamente que persiste na instância, “primeiro atribuindo um identificador gerado”. O método tem garantia de retornar o valorSerializable desse identificador.
Person person = new Person();
person.setName("John");
Long id = (Long) session.save(person);
O efeito de salvar uma instância já persistida é o mesmo que compersist. A diferença surge quando você tenta salvar uma instânciadetached:
Person person = new Person();
person.setName("John");
Long id1 = (Long) session.save(person);
session.evict(person);
Long id2 = (Long) session.save(person);
A variávelid2 será diferente deid1. A chamada de salvar em uma instânciadetached cria uma nova instânciapersistent e atribui a ela um novo identificador, o que resulta em um registro duplicado em um banco de dados ao confirmar ou descarregar.
3.3. Merge
A intenção principal do métodomerge é atualizar uma instância de entidadepersistent com novos valores de campo de uma instância de entidadedetached.
Por exemplo, suponha que você tenha uma interface RESTful com um método para recuperar um objeto serializado JSON por seu ID para o chamador e um método que recebe uma versão atualizada desse objeto do chamador. Uma entidade que passou por tal serialização / desserialização aparecerá em um estadodetached.
Após desserializar esta instância de entidade, você precisa obter uma instância de entidadepersistent de um contexto de persistência e atualizar seus campos com novos valores desta instânciadetached. Portanto, o métodomerge faz exatamente isso:
-
localiza uma instância de entidade pelo ID obtido do objeto passado (uma instância de entidade existente do contexto de persistência é recuperada ou uma nova instância carregada do banco de dados);
-
copia campos do objeto passado para esta instância;
-
retorna uma instância atualizada recentemente.
No exemplo a seguir, nósevict (separamos) a entidade salva do contexto, alteramos o camponame e, em seguida,merge a entidadedetached.
Person person = new Person();
person.setName("John");
session.save(person);
session.evict(person);
person.setName("Mary");
Person mergedPerson = (Person) session.merge(person);
Observe que o métodomerge retorna um objeto - é o objetomergedPerson que foi carregado no contexto de persistência e atualizado, não o objetoperson que você passou como argumento. Esses são dois objetos diferentes, e o objetoperson geralmente precisa ser descartado (de qualquer maneira, não conte com ele sendo anexado ao contexto de persistência).
Assim como acontece com o métodopersist, o métodomerge é especificado pelo JSR-220 para ter certas semânticas nas quais você pode confiar:
-
se a entidade fordetached, ela será copiada em uma entidadepersistent existente;
-
se a entidade fortransient, ela será copiada em uma entidadepersistent recém-criada;
-
esta operação em cascata para todas as relações com mapeamentocascade=MERGE oucascade=ALL;
-
se a entidade forpersistent, essa chamada de método não terá efeito sobre ela (mas o cascateamento ainda ocorrerá).
3.4. Update
Assim como compersistesave, o métodoupdate é um método “original” do Hibernate que estava presente muito antes do métodomerge ser adicionado. Sua semântica difere em vários pontos-chave:
-
ele atua sobre o objeto passado (seu tipo de retorno évoid); o métodoupdate faz a transição do objeto passado do estadodetached parapersistent;
-
este método lança uma exceção se você passar uma entidadetransient.
No exemplo a seguir,save o objeto, entãoevict (desanexamos) do contexto, então mudamos seunamee chamamosupdate. Observe que não colocamos o resultado da operaçãoupdate em uma variável separada, porqueupdate ocorre no próprio objetoperson. Basicamente, estamos reanexando a instância de entidade existente ao contexto de persistência - algo que a especificação JPA não nos permite fazer.
Person person = new Person();
person.setName("John");
session.save(person);
session.evict(person);
person.setName("Mary");
session.update(person);
Tentar chamarupdate em uma instânciatransient resultará em uma exceção. O seguinte não funcionará:
Person person = new Person();
person.setName("John");
session.update(person); // PersistenceException!
3.5. SaveOrUpdate
Este método aparece apenas na API do Hibernate e não possui sua contraparte padronizada. Semelhante aupdate, ele também pode ser usado para reanexar instâncias.
Na verdade, a classe internaDefaultUpdateEventListener que processa o métodoupdate é uma subclasse deDefaultSaveOrUpdateListener, apenas substituindo algumas funcionalidades. A principal diferença do métodosaveOrUpdate é que ele não lança exceção quando aplicado a uma instânciatransient; em vez disso, torna essa instânciatransientpersistent. O código a seguir manterá uma instância recém-criada dePerson:
Person person = new Person();
person.setName("John");
session.saveOrUpdate(person);
Você pode pensar neste método como uma ferramenta universal para fazer um objetopersistent, independentemente de seu estado, sejatransient oudetached.
4. O que usar?
Se você não tem requisitos especiais, como regra geral, você deve se ater aos métodospersistemerge, porque eles são padronizados e garantidos em conformidade com a especificação JPA.
Eles também são portáteis no caso de você decidir mudar para outro provedor de persistência, mas às vezes podem não parecer tão úteis quanto os métodos “originais” do Hibernate,save,updateesaveOrUpdate.
5. Conclusão
Discutimos o propósito de diferentes métodos de Sessão do Hibernate em relação ao gerenciamento de entidades persistentes em tempo de execução. Aprendemos como esses métodos transistem nas instâncias de entidade ao longo de seus ciclos de vida e por que alguns desses métodos têm funcionalidade duplicada.
O código-fonte do artigo éavailable on GitHub.