Pular para o conteúdo

Unit of Work

“Maintains a list of objects affected by a business transaction and coordinates the writing out of changes.”

Em vez de cada alteração ir ao banco na hora (uma enxurrada de UPDATEs desordenados), o Unit of Work registra o que foi criado, alterado e removido durante a transação de negócio e, no fim, efetiva tudo junto: calcula os SQLs necessários, ordena por dependência e executa numa única transação.

  • Consistência: ou todas as mudanças da operação entram, ou nenhuma.
  • Eficiência: agrupa escrita, evita UPDATEs duplicados do mesmo objeto.
  • Rastreamento: o chamador não precisa lembrar de “salvar” cada objeto tocado — o UoW sabe o que mudou.

O persistence context (a sessão do Hibernate dentro de um @Transactional) implementa exatamente esse padrão:

  • Toda entidade carregada fica gerenciada: o contexto guarda um snapshot dela.
  • No commit (ou flush), o dirty checking compara o estado atual com o snapshot e gera os UPDATEs necessários — sem nenhum save() explícito.
  • INSERTs, UPDATEs e DELETEs são ordenados e enviados juntos.
@Service
public class AjustarLimiteService {
private final ContaJpaRepository contas;
public AjustarLimiteService(ContaJpaRepository contas) {
this.contas = contas;
}
@Transactional
public void executar(Long contaId, BigDecimal novoLimite) {
ContaJpaEntity conta = contas.findById(contaId).orElseThrow();
conta.ajustarLimite(novoLimite);
}
}

Não há save() — e o UPDATE acontece mesmo assim, no commit. A fronteira do Unit of Work coincide com a fronteira transacional que o Application Service demarca.

Mutar uma entidade fora de uma transação (contexto fechado, entidade detached) não persiste nada — a alteração se perde em silêncio. É exatamente a armadilha discutida no exemplo de Registry: repo.findById(id).orElseThrow().confirmar() sem @Transactional nem save muda um objeto que nenhum Unit of Work está observando.

  • Mutação fora da transação: alterar entidade detached e esperar persistência — perda silenciosa.
  • flush() manual espalhado: forçar escrita no meio da transação por hábito; deixe o UoW decidir, flush explícito é para casos raros (ex.: capturar violação de constraint cedo).
  • Transação/contexto longevo (Open Session in View): estender o UoW até a camada de view segura conexões e mascara lazy loading — mantenha a fronteira no caso de uso.
  • save() ritualístico em entidade gerenciada: inofensivo, mas denuncia que o autor não sabe quem está persistindo de verdade.

1. O que o Unit of Work rastreia e quando ele escreve no banco?

Resposta

Objetos criados, alterados e removidos durante a transação de negócio. A escrita acontece de uma vez, no commit/flush, com os SQLs calculados e ordenados pelo próprio UoW.

2. Por que save() é redundante para uma entidade carregada dentro de @Transactional?

Resposta

Porque a entidade está gerenciada pelo persistence context (o UoW do JPA): o dirty checking compara o estado com o snapshot e gera o UPDATE no commit automaticamente.

3. Por que, no estilo domínio-separado-do-JPA, o save() volta a ser necessário?

Resposta

Porque o adapter mapeia o agregado puro para uma entidade JPA nova/detached, que não está no persistence context. Sem save() (merge), nenhum Unit of Work observa o objeto e nada é persistido.

4. Que bug silencioso o Unit of Work explica?

Resposta

Mutar entidade fora de transação (detached): a mudança fica só em memória, pois não há contexto rastreando — o caso do exemplo “moderno” de Registry antes da correção com @Transactional + save.