|

O Bug Silencioso do JPA: Quando uma Simples Query Faz UPDATE Sem Você Perceber

Quem trabalha com JPA há algum tempo provavelmente já passou por uma situação estranha como esta:

Você altera uma entidade:

cliente.setNome("João");

Depois executa uma query aparentemente inocente:

repository.buscarPedidos();

E então, sem chamar:

  • save(),
  • merge(),
  • persist(),
  • nem commit(),

o banco recebe um:

UPDATE cliente SET nome = 'João' WHERE id = 1;

A primeira reação normalmente é:

“Mas eu não mandei salvar nada.”

E é justamente aí que muitos desenvolvedores começam a descobrir que o JPA possui comportamentos automáticos extremamente poderosos — e às vezes perigosos.

Um dos mais importantes (e menos compreendidos) é o flush automático da Persistence Context.

Esse comportamento existe tanto no Hibernate quanto no EclipseLink e pode causar:

  • persistências involuntárias;
  • efeitos colaterais difíceis de rastrear;
  • bugs silenciosos;
  • inconsistências de negócio;
  • comportamento “mágico” inesperado.

Neste artigo vamos entender profundamente:

  • o que está acontecendo;
  • por que isso acontece;
  • como o JPA pensa internamente;
  • e como evitar dores de cabeça.

O Que É a Persistence Context?

Antes de entender o flush automático, precisamos entender a peça central do JPA:

A Persistence Context

A Persistence Context é basicamente um ambiente onde o JPA mantém entidades sendo monitoradas.

Quando você busca uma entidade:

Cliente cliente = entityManager.find(Cliente.class, 1L);

essa entidade passa a ser:

  • gerenciada;
  • monitorada;
  • acompanhada pelo EntityManager.

O JPA agora sabe:

  • quais campos a entidade possui;
  • quais valores foram carregados;
  • quais propriedades mudaram.

É aqui que nasce o famoso:

Dirty Checking

O JPA compara:

  • estado original da entidade;
  • estado atual em memória.

Se houver diferença:

  • a entidade é considerada “dirty”;
  • ou seja, alterada.

Exemplo:

cliente.setNome("João");

O JPA detecta:

  • antes: "Maria"
  • depois: "João"

A entidade agora está marcada internamente como modificada.


O Que É Flush?

Flush é o processo de sincronizar:

  • o estado das entidades em memória;
  • com o banco de dados.

Em outras palavras:

O JPA pega as mudanças detectadas e envia SQL para o banco.

Exemplo:

UPDATE cliente
SET nome = 'João'
WHERE id = 1;

Importante:

Flush NÃO é Commit

Muita gente confunde isso.

Flush

Envia SQL para o banco.

Commit

Confirma definitivamente a transação.

Isso significa que:

  • o UPDATE pode ser enviado;
  • mas ainda estar dentro da transação;
  • podendo sofrer rollback depois.

O Comportamento Que Surpreende Todo Mundo

Agora chegamos ao ponto crítico.

Imagine este código:

@Transactional
public void processar() {

Cliente cliente = entityManager.find(Cliente.class, 1L);

cliente.setNome("João");

pedidoRepository.buscarPedidos();

}

Muitos desenvolvedores imaginam:

  1. entidade alterada em memória;
  2. query executada;
  3. commit no final.

Mas frequentemente o que acontece é:

UPDATE cliente SET nome='João' WHERE id=1;
SELECT * FROM pedido;

O UPDATE acontece ANTES da query.

Por quê?


O Flush Automático Antes da Query

Por padrão, o JPA utiliza:

FlushModeType.AUTOFlushModeType.AUTOFlushModeType.AUTO

Esse modo diz ao provider:

“Antes de executar queries, sincronize o estado pendente das entidades com o banco.”

O objetivo disso é manter consistência.

Imagine este cenário:

cliente.setNome("João");

Depois:

SELECT c FROM Cliente c WHERE c.nome = 'João'

Se o flush não acontecesse:

  • o banco ainda teria "Maria";
  • a query retornaria resultado inconsistente;
  • o estado em memória e o banco divergiriam.

Então o JPA faz:

  1. flush automático;
  2. UPDATE no banco;
  3. execução da query.

Exemplo Completo com Spring Boot

Entidade

@Entity
public class Cliente {

@Id
private Long id;

private String nome;

// getters e setters
}

Repository

@Repository
public class PedidoRepository {

@PersistenceContext
private EntityManager entityManager;

public List<Pedido> buscarPedidos() {
return entityManager
.createQuery("SELECT p FROM Pedido p", Pedido.class)
.getResultList();
}
}

Serviço

@Service
public class ClienteService {

@PersistenceContext
private EntityManager entityManager;

@Autowired
private PedidoRepository pedidoRepository;

@Transactional
public void exemploFlushAutomatico() {

Cliente cliente =
entityManager.find(Cliente.class, 1L);

cliente.setNome("João");

pedidoRepository.buscarPedidos();
}
}

SQL Gerado

Muitos devs esperariam:

SELECT * FROM pedido;

Mas o log frequentemente mostra:

UPDATE cliente
SET nome = 'João'
WHERE id = 1;

SELECT *
FROM pedido;

O Que Aconteceu Internamente?

Internamente o fluxo foi:

  1. entidade carregada;
  2. entidade ficou gerenciada;
  3. alteração detectada;
  4. entidade marcada como dirty;
  5. query executada;
  6. JPA realizou flush automático;
  7. UPDATE enviado;
  8. SELECT executado.

Tudo isso sem:

  • save();
  • merge();
  • persist().

Por Que Isso Existe?

Porque o JPA trabalha com:

  • consistência transacional;
  • visão sincronizada dos dados;
  • unidade de trabalho.

A Persistence Context funciona como um “espelho vivo” do banco durante a transação.

Se existirem mudanças pendentes, o provider tenta evitar:

  • leituras inconsistentes;
  • resultados divergentes;
  • estado inválido.

Quando Isso Vira Problema

Na teoria faz sentido.

Na prática… pode causar caos.


1. Persistência Involuntária

Você altera uma entidade “temporariamente”:

cliente.setStatus("TESTE");

Executa uma query qualquer…

E pronto:

  • UPDATE enviado;
  • dado alterado sem intenção.

2. Bugs Difíceis de Rastrear

O pior tipo de bug:

  • silencioso;
  • sem stacktrace;
  • sem erro.

O desenvolvedor olha o código e pensa:

“Mas onde esse UPDATE aconteceu?”


3. Efeitos Colaterais em Métodos Grandes

Transações enormes aumentam muito esse risco.

Exemplo:

@Transactional
public void processar() {

// altera entidade

// chama serviço A

// chama serviço B

// executa query

// BOOM
}

Em sistemas grandes isso pode virar pesadelo.


4. Comportamentos Imprevisíveis

Muitos iniciantes pensam no JPA como:

“um monte de objetos Java”.

Mas entidades gerenciadas NÃO são POJOs comuns.

Elas possuem:

  • ciclo de vida;
  • estado;
  • monitoramento;
  • sincronização automática.

Flush vs Commit

Vamos reforçar isso porque é uma das maiores confusões.

Flush

Sincroniza com o banco.

Pode executar:

  • INSERT;
  • UPDATE;
  • DELETE.

Mas ainda dentro da transação.


Commit

Confirma definitivamente.

Se ocorrer rollback:

throw new RuntimeException();

o UPDATE enviado no flush ainda pode ser desfeito.


FlushModeType.AUTO

O comportamento padrão é:

FlushModeType.AUTOFlushModeType.AUTOFlushModeType.AUTO

Nesse modo:

  • queries podem disparar flush;
  • o provider decide quando sincronizar.

FlushModeType.COMMIT

Você também pode usar:

FlushModeType.COMMITFlushModeType.COMMITFlushModeType.COMMIT

Exemplo:

query.setFlushMode(FlushModeType.COMMIT);

Nesse modo:

  • o flush tende a ocorrer apenas no commit;
  • reduz sincronizações automáticas.

Mas cuidado:

  • isso pode gerar leituras inconsistentes;
  • dependendo do contexto.

Como Evitar Problemas

1. Entenda Que Entidades São Gerenciadas

Essa mudança mental é essencial.

Entidade JPA ≠ objeto comum.


2. Reduza Escopo Transacional

Evite:

@Transactional
public void metodoGigante() {
}

Quanto maior a transação:

  • maior chance de efeitos colaterais.

3. Use DTOs

Para leitura:

ClienteDTO

em vez de entidades gerenciadas.

Isso reduz alterações acidentais.


4. Use detach()

entityManager.detach(cliente);

A entidade deixa de ser monitorada.

Mudanças posteriores não geram flush automático.


5. Separe Leitura e Escrita

Misturar:

  • comandos;
  • consultas;
  • alterações;
  • relatórios;

na mesma transação costuma gerar problemas.


6. Evite “Mexer Por Mexer”

Isso aqui:

cliente.setNome(cliente.getNome());

pode parecer inocente.

Mas dependendo do provider e estratégia de dirty checking:

  • ainda pode marcar a entidade como alterada.

A Grande Armadilha Mental do JPA

Muitos iniciantes imaginam:

objeto Java == linha do banco

Mas o JPA é muito mais parecido com:

  • um contexto transacional inteligente;
  • um sistema de sincronização;
  • uma unidade de trabalho automatizada.

O EntityManager não é apenas:

  • “acesso ao banco”.

Ele gerencia:

  • estados;
  • identidade;
  • cache;
  • sincronização;
  • rastreamento de alterações.

ORM É Poderoso — E Perigoso

ORMs como:

  • Hibernate
  • EclipseLink

reduzem absurdamente a quantidade de código.

Mas também escondem muita complexidade.

O perigo não está apenas:

  • no SQL que você escreve;

mas também:

  • no SQL que o framework escreve automaticamente.

E muitas vezes o comportamento “mágico” do ORM só aparece:

  • em produção;
  • sob carga;
  • em transações complexas;
  • ou naquele bug estranho que ninguém consegue reproduzir facilmente.

Entender flush automático muda completamente a forma como você enxerga:

  • EntityManager;
  • entidades;
  • transações;
  • e o próprio JPA.

Depois que você entende isso, nunca mais olha para uma simples query da mesma forma.


Resumo Rápido

  • Entidades carregadas ficam gerenciadas pelo EntityManager;
  • O JPA monitora alterações automaticamente;
  • Isso é chamado dirty checking;
  • Queries podem disparar flush automático;
  • Flush envia SQL antes do commit;
  • O objetivo é manter consistência;
  • Isso pode causar UPDATEs inesperados;
  • Transações grandes aumentam o risco;
  • DTOs, detach e separação de leitura/escrita ajudam muito.

Posts Similares

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *