Uso correto de flush () em JPA / Hibernate

110

Eu estava coletando informações sobre o método flush (), mas não estou muito claro quando usá-lo e como usá-lo corretamente. Pelo que li, meu entendimento é que o conteúdo do contexto de persistência será sincronizado com o banco de dados, ou seja, a emissão de declarações pendentes ou atualização de dados da entidade.

Agora peguei o seguinte cenário com duas entidades Ae B(em um relacionamento um-para-um, mas não imposto ou modelado por JPA). Apossui um PK composto, que é definido manualmente, e também possui um campo IDENTITY gerado automaticamente recordId. Isso recordIddeve ser gravado na entidade Bcomo uma chave estrangeira para A. Estou economizando Ae Bem uma única transação. O problema é que o valor gerado automaticamente A.recordIdnão está disponível dentro da transação, a menos que eu fazer uma chamada explícita de em.flush()depois de chamar em.persist()on A. (Se eu tiver um IDENTITY PK gerado automaticamente, o valor será atualizado diretamente na entidade, mas esse não é o caso aqui.)

Pode em.flush()causar algum dano ao usá-lo em uma transação?

MicSim
fonte

Respostas:

148

Provavelmente, os detalhes exatos de em.flush()dependem da implementação. De qualquer forma, os provedores JPA como o Hibernate podem armazenar em cache as instruções SQL que devem enviar ao banco de dados, geralmente até que você realmente confirme a transação. Por exemplo, você chama em.persist(), o Hibernate lembra que tem que fazer um banco de dados INSERT, mas não executa a instrução até que você confirme a transação. Afaik, isso é feito principalmente por motivos de desempenho.

Em alguns casos, você deseja que as instruções SQL sejam executadas imediatamente; geralmente quando você precisa do resultado de alguns efeitos colaterais, como uma chave gerada automaticamente ou um acionador de banco de dados.

O que em.flush()faz é esvaziar o cache interno de instruções SQL e executá-lo imediatamente no banco de dados.

Resumindo: nenhum dano foi causado, apenas você poderia ter um (menor) impacto no desempenho, uma vez que está substituindo as decisões do provedor JPA com relação ao melhor momento para enviar instruções SQL para o banco de dados.

Flavio
fonte
1
O que ele disse. O comportamento de em.flush () ecoa java.io.Flushable.flush () onde todos os dados em buffer são enviados para qualquer destino apropriado.
Erik
4
se flush () envia dados para o banco de dados? o que acontece se uma exceção for lançada depois disso? O gerenciador de entidade reverterá tudo? até mesmo os dados gravados na primeira descarga?
Jaime Hablutzel
101
flush () envia instruções SQL para o banco de dados como INSERT, UPDATE etc. Ele não enviará um COMMIT, então se você tiver uma exceção após flush (), você ainda pode ter um rollback completo.
Flavio
17
Você pode reverter o banco de dados, mas não irá reverter nenhuma mudança nos objetos, por exemplo, 'versão' auto-incrementada, ID gerada automaticamente, etc. Além disso, o gerenciador de entidade será fechado após uma reversão. Apenas tome cuidado, pois se você tentar 'mesclar' o objeto com outra sessão, a 'versão' auto-incrementada em particular pode causar uma OptimisticLockException.
Peter Davis
11
Além de desencadear efeitos colaterais, outro motivo para usar flush () é se você deseja ser capaz de ler os efeitos de uma operação no banco de dados usando JPQL / HQL (por exemplo, em um teste). JPA não pode usar dados em cache ao executar essas consultas, portanto, apenas o que está realmente no banco de dados será lido.
sleske de
2

Na verdade, em.flush()faça mais do que apenas enviar os comandos SQL armazenados em cache. Ele tenta sincronizar o contexto de persistência com o banco de dados subjacente. Isso pode causar um grande consumo de tempo em seus processos se seu cache contiver coleções a serem sincronizadas.

Cuidado ao usá-lo.

Ricardo Nakashima
fonte
1

O em.flush () pode causar algum dano ao usá-lo em uma transação?

Sim, ele pode manter bloqueios no banco de dados por mais tempo do que o necessário.

Geralmente, ao usar JPA, você delega o gerenciamento da transação ao contêiner (também conhecido como CMT - usando a anotação @Transactional nos métodos de negócios), o que significa que uma transação é iniciada automaticamente ao inserir o método e confirmada / revertida no final. Se você deixar o EntityManager lidar com a sincronização do banco de dados, a execução de instruções sql só será disparada um pouco antes do commit, levando a bloqueios de curta duração no banco de dados. Caso contrário, suas operações de gravação liberadas manualmente podem reter bloqueios entre a liberação manual e a confirmação automática, que pode ser longa de acordo com o tempo de execução do método restante.

Observa que alguma operação dispara automaticamente um flush: executar uma consulta nativa na mesma sessão (o estado EM deve ser liberado para ser acessado pela consulta SQL), inserir entidades usando ID gerado nativo (gerado pelo banco de dados, portanto, a instrução insert deve ser acionado, portanto, o EM é capaz de recuperar o id gerado e gerenciar adequadamente os relacionamentos)

Gab
fonte