← Spring Boot®: aplicações, contratos e operação
05 / 10 · 60 MIN

Persistência e fronteiras de transação

Define unidades de trabalho, regras de rollback e participação em recursos; distingue chamadas Java de fronteiras transacionais efetivas.

Desenhar a unidade de trabalho

Uma reserva que reduz stock e cria um registo só está completa quando ambas as escritas representam a mesma decisão. Duas chamadas de repositório com transações independentes podem confirmar separadamente. Uma fronteira no serviço coordenador pode abranger a operação inteira, desde que as chamadas participem no mesmo recurso transacional.

Escreve o invariante antes da anotação: ou existem redução e reserva correspondentes, ou nenhuma das alterações desta tentativa fica confirmada. Identifica o gestor usado e o DataSource. Estar na mesma aplicação ou na mesma classe não prova que duas escritas partilham a transação física.

Interceção e autochamadas

No modo proxy, a configuração transacional é aplicada quando uma chamada elegível atravessa o proxy gerido. Uma anotação num objeto criado com new, ou num método chamado por this, não cria essa passagem. Se já existe uma transação externa, a autochamada pode continuar a usar os seus recursos sem aplicar a propagação interior declarada.

Por exemplo, this.audit() com REQUIRES_NEW não garante uma transação independente. Se a separação é realmente necessária, coloca a operação num bean chamado através do proxy ou usa uma fronteira programática explícita. Evita corrigir o sintoma apenas mudando o nome ou a visibilidade do método.

As exceções também fazem parte do contrato

Com os defaults indicados, RuntimeException e Error que saem do método provocam rollback; uma exceção verificada não o faz automaticamente. O curso distingue esse default de uma configuração global ALL_EXCEPTIONS ou de regras próprias. Usa rollbackFor com um tipo concreto quando uma exceção verificada deve reverter a unidade de trabalho.

Um catch pode impedir que a exceção chegue ao advice, mas não apaga uma marca rollback-only já colocada por um âmbito participante. Decide que resultado será devolvido e que estado realmente pode confirmar. Mensagens de log e respostas de sucesso não recuperam uma transação marcada para reversão.

Participação e independência

REQUIRED pode criar um âmbito lógico que participa na transação física externa. Uma marca rollback-only no interior impede a confirmação comum; uma tentativa externa de commit pode produzir UnexpectedRollbackException. Isso evita que o chamador interprete como commit uma operação que reverteu.

REQUIRES_NEW usa uma transação independente quando a interceção é aplicada. Um registo de tentativa pode confirmar e sobreviver ao rollback da encomenda, mas não prova sucesso dessa encomenda. Enquanto a transação externa mantém recursos, a interior pode precisar de outra ligação. A capacidade do pool e o significado do registo têm de acompanhar a decisão de propagação.

Limites do recurso participante

readOnly exprime intenção e pode permitir otimizações, mas não deve ser apresentado como uma proibição universal de escrita. O resultado depende do gestor, driver e base. Controlos de autorização e invariantes de dados continuam a precisar de implementação própria.

Também não se deve alargar uma transação local a efeitos externos por suposição. Se um fornecedor já aceitou uma chamada HTTP, o rollback JDBC não desfaz automaticamente essa aceitação. É necessário desenhar identidade, estado de entrega, reconciliação ou compensação conforme o contrato. A anotação não cria coordenação com um sistema que não participa no gestor.

Ler a reserva atómica

O exemplo define o template e inventoryTx sobre o mesmo DataSource e ativa gestão transacional. As tabelas e restrições são pré-requisitos, não são criadas pelo excerto. A chamada deve chegar ao serviço gerido através do proxy. O UPDATE combina a condição de capacidade e a redução numa instrução, usando valores ligados por parâmetros.

Se nenhuma linha satisfaz sku e capacidade, changed é zero e a reserva é rejeitada. Se a inserção posterior falha, a exceção não verificada sai do método e a redução também reverte. A unicidade de request_id deteta colisões, mas o código não transforma uma repetição em resposta idempotente: seria preciso comparar o pedido e devolver o resultado anterior de forma explícita.

Interpretar a cronologia

Compara três sequências ilustrativas: duas escritas e commit; primeira escrita seguida de exceção e rollback; auditoria independente confirmada seguida de rollback do negócio. O estado final esperado é diferente em cada caso. Regista a fronteira do erro, a participação dos recursos e o resultado observado sem tratar todos os registos de auditoria como confirmação comercial.

As perguntas pedem interpretar essas sequências, não executar uma base de dados. Numa revisão real, ter um método chamado reserve ou um teste que só verifica o retorno normal não demonstra a reversão da primeira escrita. A evidência precisa de cobrir o invariante e o ramo de falha pretendidos.

// Separate files inside the application scan root.
// One transactional JDBC DataSource; schema prepared separately.
// stock(sku UNIQUE, available); reservation(request_id PRIMARY KEY, sku, units).
// InventoryConfiguration.java
package com.acme.inventory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.JdbcTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement
public class InventoryConfiguration {
  @Bean
  JdbcTemplate inventoryJdbc(DataSource source) {
    return new JdbcTemplate(source);
  }
  @Bean("inventoryTx")
  JdbcTransactionManager inventoryTx(DataSource source) {
    return new JdbcTransactionManager(source);
  }
}

// ReservationService.java
package com.acme.inventory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ReservationService {
  private final JdbcTemplate jdbc;
  public ReservationService(JdbcTemplate jdbc) { this.jdbc = jdbc; }

  @Transactional(transactionManager = "inventoryTx")
  public void reserve(String requestId, String sku, int units) {
    if (requestId == null || requestId.isBlank() || sku == null || sku.isBlank()
        || units <= 0) throw new IllegalArgumentException("Invalid reservation input");
    int changed = jdbc.update(
        "update stock set available = available - ? where sku = ? and available >= ?",
        units, sku, units);
    if (changed != 1) throw new IllegalStateException("Reservation condition failed");
    int inserted = jdbc.update(
        "insert into reservation (request_id, sku, units) values (?, ?, ?)",
        requestId, sku, units);
    if (inserted != 1) throw new IllegalStateException("Reservation was not recorded");
  }
}
NA PRÁTICA

Lê reserve como chamada externa ao proxy, com tabelas transacionais e o mesmo DataSource no template e no gestor. Se a inserção falha com DataAccessException, a exceção sai do método e a redução de stock também reverte. O exemplo não implementa uma resposta idempotente para pedidos repetidos. Este é um exemplo JDBC separado da configuração JPA da aula seguinte.

Armadilhas comuns

Confiar apenas na anotação; supor que autochamadas ativam REQUIRES_NEW; capturar exceções sem rever rollback-only; usar readOnly como controlo de acesso; considerar efeitos HTTP parte automática do commit JDBC.

Tópicos relacionados: Contexto da aplicação e dependências · Consultas, concorrência e acesso a dados · Âmbito de testes e evidência de integração

Leva esta ideia contigo

A unidade de trabalho depende do percurso da chamada, do gestor e dos recursos participantes. Define o resultado a confirmar e as condições de rollback antes de escolher propagação ou tratamento de exceções.

Criar conta

Referência: Transactional annotation and proxy boundary · Spring Boot 4.1.1 / Spring Framework 7.0.9 / Java 25