Unit of Work
Definição (Fowler)
Seção intitulada “Definição (Fowler)”“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.
O problema que resolve
Seção intitulada “O problema que resolve”- 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 JPA é um Unit of Work
Seção intitulada “O JPA é um Unit of Work”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.
@Servicepublic 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.
O bug clássico que o UoW explica
Seção intitulada “O bug clássico que o UoW explica”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.
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- 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.
Relações
Seção intitulada “Relações”- Família de acesso a dados do PoEAA, ao lado de DAO - Data Access Object e Gateway.
- A fronteira do UoW é a transação do Application Service / Service Layer.
- Explica o custo do mapeamento domínio↔JPA em Repositórios e o exemplo de Registry.
- O Domain Model rico muda estado em memória; o UoW leva essas mudanças ao banco.
Perguntas de revisão
Seção intitulada “Perguntas de revisão”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.