Entrevista Spring Boot: Propagação de Transações

Domine a propagação de transações no Spring Boot: REQUIRED, REQUIRES_NEW, NESTED e mais. 12 perguntas de entrevista com código e armadilhas comuns.

Spring Boot Propagação de Transações: perguntas de entrevista e exemplos práticos

A propagação de transações representa um conceito fundamental no Spring Boot, regularmente avaliado em entrevistas técnicas. Compreender como as transações interagem entre métodos anotados com @Transactional ajuda a evitar bugs sutis em produção e permite projetar arquiteturas robustas.

Dica de preparação

Os entrevistadores avaliam a capacidade de escolher o nível de propagação correto conforme o contexto de negócio. Saber explicar por que REQUIRES_NEW em vez de REQUIRED em um caso específico faz a diferença.

Fundamentos da propagação de transações

Pergunta 1: O que é propagação de transações no Spring?

A propagação define o comportamento de um método transacional quando é chamado dentro do contexto de uma transação existente. Ela responde à pergunta: "O que acontece quando um método @Transactional chama outro método também anotado?"

OrderService.javajava
// Demonstrando o conceito de propagação
@Service
public class OrderService {

    private final PaymentService paymentService;
    private final OrderRepository orderRepository;

    // Transação pai - inicia uma nova transação
    @Transactional
    public void createOrder(OrderRequest request) {
        // Salva o pedido na transação atual
        Order order = orderRepository.save(new Order(request));

        // Chamada para outro método @Transactional
        // A propagação determina: mesma transação ou nova?
        paymentService.processPayment(order.getId(), request.getAmount());
    }
}

@Service
public class PaymentService {

    // Propagação padrão: REQUIRED
    // Junta-se à transação existente de createOrder()
    @Transactional
    public void processPayment(Long orderId, BigDecimal amount) {
        // Executa na MESMA transação que createOrder()
        // Se este método falhar, o pedido também é revertido
    }
}

O Spring fornece sete níveis de propagação, cada um adequado a necessidades de negócio específicas. A escolha impacta diretamente a consistência dos dados e o desempenho.

Pergunta 2: Descreva o comportamento de REQUIRED (propagação padrão)

REQUIRED é a propagação padrão. Se uma transação existir, o método junta-se a ela. Caso contrário, uma nova transação é criada. É o comportamento mais comum e intuitivo.

UserService.javajava
// REQUIRED: comportamento padrão
@Service
public class UserService {

    private final UserRepository userRepository;
    private final AuditService auditService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void updateUser(Long userId, UserUpdateRequest request) {
        // Inicia uma transação se nenhuma existir
        User user = userRepository.findById(userId).orElseThrow();
        user.setEmail(request.getEmail());
        userRepository.save(user);

        // A auditoria junta-se à mesma transação
        auditService.logUpdate(userId, "EMAIL_CHANGED");
    }
}

@Service
public class AuditService {

    private final AuditLogRepository auditLogRepository;

    @Transactional(propagation = Propagation.REQUIRED)
    public void logUpdate(Long userId, String action) {
        // Junta-se à transação de updateUser()
        // Commit ou rollback juntos
        auditLogRepository.save(new AuditLog(userId, action, Instant.now()));
    }
}

O diagrama abaixo ilustra o fluxo transacional:

text
updateUser() inicia TX-1
    ├── save(user)          → TX-1
    └── logUpdate()         → junta-se a TX-1 (REQUIRED)
           └── save(audit)  → TX-1

Se logUpdate() falhar → rollback TX-1 → user E audit cancelados

Pergunta 3: Quando usar REQUIRES_NEW em vez de REQUIRED?

REQUIRES_NEW suspende a transação existente e cria uma nova transação independente. Útil quando uma operação precisa ser persistida independentemente do resultado da transação pai.

PaymentService.javajava
// REQUIRES_NEW: transação independente
@Service
public class PaymentService {

    private final PaymentRepository paymentRepository;
    private final PaymentAuditService auditService;

    @Transactional
    public void processPayment(Long orderId, BigDecimal amount) {
        Payment payment = new Payment(orderId, amount);
        paymentRepository.save(payment);

        // Auditoria DEVE ser persistida mesmo se o pagamento falhar depois
        auditService.logPaymentAttempt(orderId, amount);

        // Simulando um erro após a auditoria
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            throw new InvalidAmountException("Negative amount");
        }
    }
}

@Service
public class PaymentAuditService {

    private final PaymentAuditRepository auditRepository;

    // REQUIRES_NEW: faz commit independente da transação pai
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logPaymentAttempt(Long orderId, BigDecimal amount) {
        // Nova transação TX-2 criada
        // TX-1 (processPayment) está suspensa
        auditRepository.save(new PaymentAudit(orderId, amount, "ATTEMPTED"));
        // TX-2 faz commit aqui, independentemente de TX-1
    }
}

O fluxo transacional com REQUIRES_NEW:

text
processPayment() inicia TX-1
    ├── save(payment)              → TX-1
    ├── logPaymentAttempt()        → TX-1 SUSPENSA
    │       └── inicia TX-2        → nova transação
    │       └── save(audit)        → TX-2
    │       └── COMMIT TX-2        → audit persistido
    │       └── TX-1 RETOMA
    └── throw InvalidAmountException
        └── ROLLBACK TX-1          → pagamento cancelado, mas audit preservado
Cuidado com deadlocks

REQUIRES_NEW pode causar deadlocks se a nova transação acessar os mesmos recursos bloqueados pela transação suspensa. Evite usar REQUIRES_NEW para modificar as mesmas tabelas que a transação pai.

Pergunta 4: Explique a propagação NESTED e como ela difere de REQUIRES_NEW

NESTED cria um savepoint dentro da transação atual. Se o método falhar, apenas as modificações desde o savepoint são revertidas, e não toda a transação pai.

BatchProcessingService.javajava
// NESTED: savepoint dentro da transação pai
@Service
public class BatchProcessingService {

    private final ItemRepository itemRepository;
    private final ItemProcessor itemProcessor;

    @Transactional
    public BatchResult processBatch(List<Item> items) {
        BatchResult result = new BatchResult();

        for (Item item : items) {
            try {
                // Cada item é processado com um savepoint
                itemProcessor.processItem(item);
                result.addSuccess(item.getId());
            } catch (ProcessingException e) {
                // Rollback apenas deste item, não do batch inteiro
                result.addFailure(item.getId(), e.getMessage());
            }
        }

        return result; // Commit dos itens bem-sucedidos
    }
}

@Service
public class ItemProcessor {

    private final ItemRepository itemRepository;

    // NESTED: cria um savepoint, rollback parcial possível
    @Transactional(propagation = Propagation.NESTED)
    public void processItem(Item item) {
        item.setStatus("PROCESSING");
        itemRepository.save(item);

        // Validação de negócio
        if (!isValid(item)) {
            throw new ProcessingException("Invalid item");
            // Rollback até o savepoint → apenas este item
        }

        item.setStatus("COMPLETED");
        itemRepository.save(item);
    }
}

Comparação NESTED vs REQUIRES_NEW:

text
NESTED:
├── Usa um savepoint dentro da TX pai
├── Em caso de falha → rollback ao savepoint
├── Se a TX pai fizer rollback → NESTED também reverte
└── Mais performático (sem nova conexão)

REQUIRES_NEW:
├── Cria uma transação completamente independente
├── Em caso de falha → rollback apenas da TX filha
├── Se a TX pai fizer rollback → TX filha JÁ FEZ COMMIT
└── Requer uma nova conexão
Suporte NESTED

A propagação NESTED requer suporte a savepoints JDBC. A maioria dos bancos modernos (PostgreSQL, MySQL, Oracle) suporta isso. Verifique a compatibilidade antes de usar.

Tipos de propagação avançados

Pergunta 5: Quando usar SUPPORTS e NOT_SUPPORTED?

SUPPORTS executa dentro da transação existente, se houver, caso contrário sem transação. NOT_SUPPORTED suspende qualquer transação existente e executa sem transação.

ReportingService.javajava
// SUPPORTS: transação opcional
@Service
public class ReportingService {

    private final ReportRepository reportRepository;

    // SUPPORTS: funciona com ou sem transação
    // Útil para leituras que não precisam de garantias transacionais
    @Transactional(propagation = Propagation.SUPPORTS)
    public Report generateReport(Long reportId) {
        // Se chamado de um método @Transactional → usa sua TX
        // Se chamado diretamente → sem transação (leitura OK)
        return reportRepository.generateComplexReport(reportId);
    }
}

@Service
public class ExternalNotificationService {

    private final ExternalApiClient apiClient;

    // NOT_SUPPORTED: nunca dentro de uma transação
    // Evita bloquear a TX durante uma chamada externa lenta
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void sendExternalNotification(String message) {
        // TX pai suspensa durante a chamada
        apiClient.send(message); // Chamada HTTP potencialmente lenta
        // TX pai retoma depois
    }
}
OrderService.javajava
// Exemplo de uso combinado
@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final ReportingService reportingService;
    private final ExternalNotificationService notificationService;

    @Transactional
    public void completeOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.setStatus("COMPLETED");
        orderRepository.save(order);

        // Geração do relatório na mesma TX (SUPPORTS)
        Report report = reportingService.generateReport(orderId);

        // Notificação externa FORA da transação (NOT_SUPPORTED)
        // Evita timeout da TX se a API externa estiver lenta
        notificationService.sendExternalNotification(
            "Order " + orderId + " completed"
        );
    }
}

Pergunta 6: Explique MANDATORY e NEVER

MANDATORY exige uma transação existente e lança uma exceção caso contrário. NEVER exige a ausência de transação e lança uma exceção se uma existir.

AuditService.javajava
// MANDATORY: deve ser chamado de dentro de uma transação
@Service
public class AuditService {

    private final AuditRepository auditRepository;

    // MANDATORY: recusa-se a executar sem uma transação existente
    // Garante que a auditoria é sempre atômica com a operação auditada
    @Transactional(propagation = Propagation.MANDATORY)
    public void logCriticalAction(String action, Long entityId) {
        // Se chamado sem transação → IllegalTransactionStateException
        auditRepository.save(new AuditLog(action, entityId, Instant.now()));
    }
}

// CacheService.java
// NEVER: nunca deve estar em uma transação
@Service
public class CacheService {

    private final CacheManager cacheManager;

    // NEVER: o cache não deve participar de transações
    // Evita inconsistências entre cache e BD após rollback
    @Transactional(propagation = Propagation.NEVER)
    public void invalidateCache(String cacheKey) {
        // Se chamado de um @Transactional → IllegalTransactionStateException
        cacheManager.getCache("entities").evict(cacheKey);
    }
}
SecurityService.javajava
// Uso correto de MANDATORY
@Service
public class SecurityService {

    private final AuditService auditService;

    @Transactional
    public void changeUserPassword(Long userId, String newPassword) {
        // Operação sensível...
        updatePassword(userId, newPassword);

        // A auditoria DEVE estar na mesma transação
        // MANDATORY impõe esta restrição arquitetural
        auditService.logCriticalAction("PASSWORD_CHANGE", userId);
    }

    // ERRO: chamada direta sem transação
    public void badUsage() {
        // Lança IllegalTransactionStateException porque não há TX
        auditService.logCriticalAction("TEST", 1L);
    }
}

Pronto para mandar bem nas entrevistas de Spring Boot?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Armadilhas comuns em entrevistas

Pergunta 7: Por que @Transactional não funciona em chamadas internas?

Uma das armadilhas mais comuns. Chamadas internas a métodos (self-invocation) ignoram o proxy do Spring, desativando o gerenciamento transacional.

BrokenService.javajava
// ERRO CLÁSSICO: self-invocation
@Service
public class BrokenService {

    private final ItemRepository itemRepository;

    public void processItems(List<Long> itemIds) {
        for (Long id : itemIds) {
            // ARMADILHA: chamada interna → ignora o proxy
            // @Transactional em processItem() é IGNORADO
            processItem(id);
        }
    }

    @Transactional
    public void processItem(Long itemId) {
        // Esta transação NUNCA é criada em chamadas internas
        Item item = itemRepository.findById(itemId).orElseThrow();
        item.setStatus("PROCESSED");
        itemRepository.save(item);
    }
}

Soluções para evitar esta armadilha:

java
// Solução 1: Auto-injeção
@Service
public class FixedServiceWithSelfInjection {

    private final ItemRepository itemRepository;

    @Lazy
    @Autowired
    private FixedServiceWithSelfInjection self; // Auto-injeção

    public void processItems(List<Long> itemIds) {
        for (Long id : itemIds) {
            // Chamada via proxy → @Transactional funciona
            self.processItem(id);
        }
    }

    @Transactional
    public void processItem(Long itemId) {
        // Transação corretamente criada
        Item item = itemRepository.findById(itemId).orElseThrow();
        item.setStatus("PROCESSED");
        itemRepository.save(item);
    }
}

// Solução 2: Separar em dois serviços (recomendado)
@Service
public class ItemOrchestrator {

    private final ItemProcessor processor;

    public void processItems(List<Long> itemIds) {
        for (Long id : itemIds) {
            // Chamada para outro bean → o proxy funciona
            processor.processItem(id);
        }
    }
}

@Service
public class ItemProcessor {

    private final ItemRepository itemRepository;

    @Transactional
    public void processItem(Long itemId) {
        // Transação corretamente gerenciada
        Item item = itemRepository.findById(itemId).orElseThrow();
        item.setStatus("PROCESSED");
        itemRepository.save(item);
    }
}

Pergunta 8: Como tratar exceções e rollback?

Por padrão, o Spring só faz rollback em RuntimeException e Error. Exceções checked NÃO disparam rollback automático.

TransactionRollbackDemo.javajava
// Comportamento de rollback conforme o tipo de exceção
@Service
public class TransactionRollbackDemo {

    private final OrderRepository orderRepository;

    // RuntimeException → ROLLBACK automático
    @Transactional
    public void methodWithRuntimeException() {
        orderRepository.save(new Order());
        throw new RuntimeException("Error"); // ROLLBACK
    }

    // Checked Exception → SEM rollback por padrão
    @Transactional
    public void methodWithCheckedException() throws IOException {
        orderRepository.save(new Order());
        throw new IOException("File error"); // Faz COMMIT mesmo assim!
    }

    // Forçar rollback em checked exception
    @Transactional(rollbackFor = IOException.class)
    public void methodWithRollbackFor() throws IOException {
        orderRepository.save(new Order());
        throw new IOException("Error"); // ROLLBACK graças a rollbackFor
    }

    // Excluir uma RuntimeException do rollback
    @Transactional(noRollbackFor = BusinessException.class)
    public void methodWithNoRollbackFor() {
        orderRepository.save(new Order());
        throw new BusinessException("Warning"); // COMMIT apesar da exceção
    }
}

Configuração recomendada para casos de negócio:

BaseTransactionalService.javajava
// Configuração transacional consistente
@Service
public abstract class BaseTransactionalService {

    // Rollback em todas as exceções (checked e unchecked)
    @Transactional(rollbackFor = Exception.class)
    protected void executeInTransaction(Runnable operation) {
        operation.run();
    }
}

// PaymentService.java
@Service
public class PaymentService extends BaseTransactionalService {

    private final PaymentRepository paymentRepository;

    @Transactional(
        rollbackFor = Exception.class,
        noRollbackFor = InsufficientFundsException.class
    )
    public PaymentResult processPayment(PaymentRequest request) {
        Payment payment = new Payment(request);
        paymentRepository.save(payment);

        if (request.getAmount().compareTo(getBalance()) > 0) {
            // NÃO faz rollback - quer manter o registro da tentativa
            throw new InsufficientFundsException("Insufficient balance");
        }

        return new PaymentResult(payment.getId(), "SUCCESS");
    }
}

Pergunta 9: Como o isolamento de transações funciona com a propagação?

Isolamento e propagação são complementares. O isolamento determina a visibilidade dos dados entre transações concorrentes.

IsolationDemo.javajava
// Combinando isolamento + propagação
@Service
public class IsolationDemo {

    private final AccountRepository accountRepository;

    // READ_COMMITTED: vê dados commitados por outras transações
    @Transactional(
        isolation = Isolation.READ_COMMITTED,
        propagation = Propagation.REQUIRED
    )
    public BigDecimal getAccountBalance(Long accountId) {
        // Pode ver valores diferentes se relido durante a transação
        return accountRepository.findById(accountId)
            .map(Account::getBalance)
            .orElse(BigDecimal.ZERO);
    }

    // REPEATABLE_READ: garante a mesma leitura durante a transação
    @Transactional(
        isolation = Isolation.REPEATABLE_READ,
        propagation = Propagation.REQUIRED
    )
    public void transferWithConsistentRead(Long fromId, Long toId, BigDecimal amount) {
        Account from = accountRepository.findById(fromId).orElseThrow();
        Account to = accountRepository.findById(toId).orElseThrow();

        // Mesmo que outra transação modifique 'from' nesse meio tempo,
        // sempre vemos o valor inicial (snapshot)
        from.debit(amount);
        to.credit(amount);

        accountRepository.save(from);
        accountRepository.save(to);
    }

    // SERIALIZABLE: isolamento máximo, sem concorrência
    @Transactional(
        isolation = Isolation.SERIALIZABLE,
        propagation = Propagation.REQUIRES_NEW
    )
    public void criticalOperation(Long accountId) {
        // Bloqueia qualquer outra transação sobre estes dados
        // Usar com moderação - impacto no desempenho
        Account account = accountRepository.findById(accountId).orElseThrow();
        account.performCriticalUpdate();
        accountRepository.save(account);
    }
}

Tabela resumo dos níveis de isolamento:

text
| Isolamento       | Dirty Read | Non-Repeatable | Phantom |
|------------------|------------|----------------|---------|
| READ_UNCOMMITTED | Possível   | Possível       | Possível|
| READ_COMMITTED   | Não        | Possível       | Possível|
| REPEATABLE_READ  | Não        | Não            | Possível|
| SERIALIZABLE     | Não        | Não            | Não     |
Impacto no desempenho

Quanto mais rigoroso o isolamento, mais o desempenho pode ser afetado por locks. SERIALIZABLE pode causar contenção significativa em produção de alto tráfego.

Padrões avançados

Pergunta 10: Como implementar o padrão "transactional outbox"?

O padrão outbox garante consistência entre as modificações no banco de dados e o envio de mensagens/eventos, mesmo em caso de falha.

OutboxService.javajava
// Padrão Transactional Outbox
@Service
public class OutboxService {

    private final OutboxRepository outboxRepository;

    // Salva o evento na mesma transação que a entidade de negócio
    @Transactional(propagation = Propagation.MANDATORY)
    public void saveEvent(String aggregateType, Long aggregateId, String eventType, String payload) {
        OutboxEvent event = OutboxEvent.builder()
            .aggregateType(aggregateType)
            .aggregateId(aggregateId)
            .eventType(eventType)
            .payload(payload)
            .status("PENDING")
            .createdAt(Instant.now())
            .build();
        outboxRepository.save(event);
    }
}

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final OutboxService outboxService;

    @Transactional
    public Order createOrder(OrderRequest request) {
        // Criar o pedido
        Order order = new Order(request);
        orderRepository.save(order);

        // Evento outbox na MESMA transação (MANDATORY)
        // Se o commit der certo → ambos são persistidos
        // Se houver rollback → nenhum é persistido
        outboxService.saveEvent(
            "ORDER",
            order.getId(),
            "ORDER_CREATED",
            toJson(new OrderCreatedEvent(order))
        );

        return order;
    }
}

// OutboxPublisher.java
// Processo separado que publica eventos
@Service
public class OutboxPublisher {

    private final OutboxRepository outboxRepository;
    private final MessageBroker messageBroker;

    // Transação independente para cada publicação
    @Scheduled(fixedDelay = 1000)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void publishPendingEvents() {
        List<OutboxEvent> events = outboxRepository
            .findByStatusOrderByCreatedAt("PENDING");

        for (OutboxEvent event : events) {
            try {
                messageBroker.publish(event.getEventType(), event.getPayload());
                event.setStatus("PUBLISHED");
                event.setPublishedAt(Instant.now());
            } catch (Exception e) {
                event.setStatus("FAILED");
                event.setError(e.getMessage());
            }
            outboxRepository.save(event);
        }
    }
}

Pergunta 11: Como testar diferentes comportamentos de propagação?

Testes de propagação requerem atenção especial para verificar o comportamento transacional esperado.

TransactionPropagationTest.javajava
// Testando comportamentos de propagação
@SpringBootTest
@Transactional
class TransactionPropagationTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void required_shouldShareTransaction() {
        // Given
        Order order = orderService.createOrder(new OrderRequest());

        // When - pagamento na mesma transação (REQUIRED)
        paymentService.processPayment(order.getId(), BigDecimal.TEN);

        // Then - ambos visíveis antes do commit
        entityManager.flush();
        assertThat(entityManager.find(Order.class, order.getId())).isNotNull();
        assertThat(entityManager.find(Payment.class, order.getId())).isNotNull();
    }

    @Test
    void requiresNew_shouldCommitIndependently() {
        // Given
        Long orderId = null;

        try {
            // When - audit em REQUIRES_NEW
            orderId = orderService.createOrderWithAudit(new OrderRequest());
            throw new RuntimeException("Simulated failure after audit");
        } catch (RuntimeException e) {
            // Transação principal faz rollback
        }

        // Then - audit (REQUIRES_NEW) ainda está commitado
        assertThat(findAuditLog(orderId)).isNotNull();
        // Mas o pedido foi revertido
        assertThat(findOrder(orderId)).isNull();
    }

    @Test
    void mandatory_shouldThrowWithoutTransaction() {
        // Given - nenhuma transação ativa

        // When/Then - deve lançar uma exceção
        assertThatThrownBy(() -> auditService.logCriticalAction("TEST", 1L))
            .isInstanceOf(IllegalTransactionStateException.class)
            .hasMessageContaining("No existing transaction");
    }
}
PropagationIntegrationTest.javajava
// Teste de integração com rollback e commit reais
@SpringBootTest
class PropagationIntegrationTest {

    @Autowired
    private OrderService orderService;

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private AuditLogRepository auditLogRepository;

    @Test
    void nested_shouldRollbackOnlyNestedOnFailure() {
        // Given
        int initialCount = orderRepository.findAll().size();

        // When - processamento batch com NESTED
        BatchResult result = orderService.processBatchWithNested(
            List.of(validItem(), invalidItem(), validItem())
        );

        // Then - apenas itens válidos são persistidos
        assertThat(result.getSuccessCount()).isEqualTo(2);
        assertThat(result.getFailureCount()).isEqualTo(1);
        assertThat(orderRepository.findAll().size()).isEqualTo(initialCount + 2);
    }
}

Pergunta 12: Quais são as melhores práticas para configuração de transações?

Uma configuração transacional consistente em nível de projeto evita surpresas e facilita a manutenção.

TransactionConfig.javajava
// Configuração transacional centralizada
@Configuration
@EnableTransactionManagement
public class TransactionConfig {

    // Timeout padrão para todas as transações
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager tm = new DataSourceTransactionManager(dataSource);
        tm.setDefaultTimeout(30); // 30 segundos máximo por transação
        return tm;
    }
}

// BaseService.java
// Anotações padrão para serviços
@Service
@Transactional(
    readOnly = true, // Apenas leitura por padrão
    rollbackFor = Exception.class // Rollback em qualquer exceção
)
public abstract class BaseService {
    // Métodos de leitura herdam readOnly = true
}

// OrderService.java
// Serviço com configuração consistente
@Service
public class OrderService extends BaseService {

    private final OrderRepository orderRepository;

    // Herda readOnly = true
    public Order findById(Long id) {
        return orderRepository.findById(id).orElseThrow();
    }

    // Override para escritas
    @Transactional(readOnly = false)
    public Order createOrder(OrderRequest request) {
        return orderRepository.save(new Order(request));
    }

    // Configuração explícita para casos críticos
    @Transactional(
        readOnly = false,
        propagation = Propagation.REQUIRES_NEW,
        isolation = Isolation.SERIALIZABLE,
        timeout = 10
    )
    public void criticalOperation(Long orderId) {
        // Operação com isolamento máximo e timeout curto
    }
}

Checklist de melhores práticas:

text
Configuração de Transação - Checklist

✅ readOnly = true por padrão, override explícito para escritas
✅ rollbackFor = Exception.class para incluir checked exceptions
✅ Timeout adequado conforme o tipo de operação
✅ Evitar chamadas internas (@Transactional ignorado)
✅ REQUIRES_NEW apenas quando commit independente é necessário
✅ MANDATORY para garantir contexto transacional
✅ Testes explícitos de comportamentos de rollback
✅ Monitorar transações de longa duração
✅ Documentar escolhas de propagação não padrão

Pronto para mandar bem nas entrevistas de Spring Boot?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Conclusão

A propagação de transações é um conceito fundamental avaliado em entrevistas Spring Boot. Pontos-chave a lembrar:

Tipos de propagação comuns:

  • ✅ REQUIRED (padrão): junta-se ou cria uma transação
  • ✅ REQUIRES_NEW: transação independente, commit separado
  • ✅ NESTED: savepoint para rollback parcial
  • ✅ MANDATORY: requer uma transação existente

Armadilhas a evitar:

  • ✅ Self-invocation: ignora o proxy, @Transactional ignorado
  • ✅ Checked exceptions: sem rollback por padrão
  • ✅ REQUIRES_NEW nos mesmos dados: risco de deadlock
  • ✅ Sem timeout: transações bloqueadas indefinidamente

Melhores práticas:

  • ✅ readOnly = true por padrão
  • ✅ rollbackFor = Exception.class sistematicamente
  • ✅ Serviços separados para evitar self-invocation
  • ✅ Testar explicitamente comportamentos de rollback

Dominar a propagação de transações demonstra compreensão profunda do Spring e do gerenciamento de dados. Esses conceitos são essenciais para projetar aplicações robustas e passar com sucesso em entrevistas técnicas.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

#spring boot
#transactions
#propagation
#java
#interview

Compartilhar

Artigos relacionados