404 Not Found

404 Not Found


nginx

A Camada de Serviço e Gerenciamento de Transações

Transações são as guardiãs da consistência de dados—fazer um pedido e deduzir estoque devem ser completados atomicamente; ou toda a transação é bem-sucedida ou toda a transação é revertida.

1. O Que Você Vai Aprender


2. Uma História Real de um Desenvolvedor de Negócio

(1) Ponto de Dor: Inconsistências nas Deduções de Estoque Ao Fazer Pedido

Alice descobriu um bug sério no OrderFlow: após um usuário fazer um pedido com sucesso, o estoque do produto não era deduzido. Isso ocorria porque criar um pedido e deduzir estoque são duas operações separadas; quando um pedido é criado com sucesso mas a dedução de estoque falha, inconsistências de dados ocorrem. Charlie relatou que já havia 5.000 pedidos com overselling, resultando em uma perda de mais de 10.000 USD para a empresa.

(2) Soluções para Transações Declarativas

A anotação @Transactional do Spring torna o gerenciamento de transações algo de uma linha:

JAVA
@Transactional
public Order createOrder(CreateOrderRequest request) {
    Product product = productRepository.findById(request.productId()).orElseThrow();
    product.deductStock(request.quantity());    // Deduzir estoque
    Order order = new Order(product, request.quantity());
    return orderRepository.save(order);         // Criar pedido
}

Falha ao deduzir estoque? A transação inteira é revertida automaticamente, e o pedido não é criado.

(3) Resultado

Depois que Alice adicionou @Transactional, as deduções de estoque ao fazer pedido garantiram atomicidade, eliminando completamente o problema de overselling. As reclamações de Charlie caíram para zero.


3. Padrões de Design na Camada de Serviço

(1) Arquitetura em Camadas

100%
graph TD
    A["Controller<br/>Request/Response"] --> B["Service<br/>Lógica de Negócio<br/>@Transactional"]
    B --> C["Repository<br/>Acesso a Dados"]
    C --> D["Database"]
Camada Responsabilidades Observações Convenção de Nomenclatura
Controller Recebe requisições, valida parâmetros, retorna respostas @RestController *Controller
Service Lógica de Negócio, Orquestração de Transações @Service *Service / *ServiceImpl
Repository Acesso a Dados @Repository *Repository

(1) ▶ Exemplo: Interface e Implementação de Service

JAVA
// OrderService.java (interface)
package com.orderflow.service;

import com.orderflow.model.Order;
public interface OrderService {
    Order createOrder(Long productId, Integer quantity);
    Order cancelOrder(Long orderId);
    Order getOrder(Long orderId);
}

// OrderServiceImpl.java (implementação)
package com.orderflow.service;

import com.orderflow.model.*;
import com.orderflow.repository.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderServiceImpl implements OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    public OrderServiceImpl(OrderRepository orderRepository,
                            ProductRepository productRepository) {
        this.orderRepository = orderRepository;
        this.productRepository = productRepository;
    }

    @Override
    @Transactional
    public Order createOrder(Long productId, Integer quantity) {
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new RuntimeException("Product not found"));
        product.deductStock(quantity);
        Order order = new Order();
        order.addItem(product, quantity);
        return orderRepository.save(order);
    }

    @Override
    @Transactional(readOnly = true)
    public Order getOrder(Long orderId) {
        return orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found"));
    }
}

Saída:

TEXT
// Execução bem-sucedida

4. Comportamento de Propagação do @Transactional

(1) Sete Tipos de Comportamentos de Comunicação

Comportamento de Comunicação Significado Cenários Típicos
REQUIRED (padrão) As transações são integradas, não criadas A grande maioria dos métodos de negócio
REQUIRES_NEW Sempre criar uma nova transação e suspender a transação atual Entradas de log (não afetadas por rollbacks de nível externo)
NESTED Transações aninhadas (savepoint): Se a transação externa for revertida, a interna também é Sub-operações podem ser revertidas independentemente
SUPPORTS As transações são envolvidas; execução não transacional sem transação Métodos de Consulta
NOT_SUPPORTED Execução não transacional; suspende a transação atual Operações que não requerem transação
MANDATORY Deve ser chamado dentro de uma transação; caso contrário, uma exceção é lançada Métodos que requerem transação
NEVER Deve ser uma chamada não transacional; caso contrário, uma exceção será lançada Operações transacionais não são permitidas
100%
sequenceDiagram
    participant C as Controller
    participant S1 as createOrder (REQUIRED)
    participant S2 as recordAudit (REQUIRES_NEW)
    participant S3 as deductStock (REQUIRED)

    C->>S1: Begin Tx1
    S1->>S3: Join Tx1
    S3-->>S1: OK
    S1->>S2: Suspend Tx1, Begin Tx2
    S2-->>S1: Commit Tx2, Resume Tx1
    alt Tx1 rollback
        S1-->>C: Rollback Tx1 (Tx2 not affected)
    end

(1) ▶ Exemplo: REQUIRES_NEW garante logs de auditoria

JAVA
@Service
public class AuditService {

    private final AuditLogRepository auditLogRepository;

    public AuditService(AuditLogRepository auditLogRepository) {
        this.auditLogRepository = auditLogRepository;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logAudit(String action, String detail) {
        AuditLog log = new AuditLog(action, detail, Instant.now());
        auditLogRepository.save(log);
    }
}

Saída:

TEXT
// Execução bem-sucedida
📌 Ponto-Chave: REQUIRES_NEW Comita logs de auditoria em uma transação separada para que os logs não sejam perdidos mesmo que a transação externa seja revertida.


5. Regras de Rollback de Transação

(1) Comportamento de rollback padrão

Por padrão, transações Spring só revertem RuntimeException e Error; elas não revertem devido a exceções verificadas.

(1) ▶ Exemplo: configuração rollbackFor

JAVA
@Service
public class OrderServiceImpl implements OrderService {

    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(Long productId, Integer quantity) throws Exception {
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new RuntimeException("Product not found"));
        product.deductStock(quantity);
        Order order = new Order();
        order.addItem(product, quantity);
        return orderRepository.save(order);
    }
}

Saída:

TEXT
// Execução bem-sucedida
Configuração Significado Cenários Aplicáveis
rollbackFor = Exception.class Reverter todas as exceções Configuração padrão recomendada
rollbackFor = BusinessException.class Reverter apenas exceção personalizada Controle refinado
noRollbackFor = ValidationException.class Não reverter para exceções especificadas Certas exceções não acionam rollback

6. Otimização de Transações Somente Leitura

(1) ▶ Exemplo: Transações Somente Leitura

JAVA
@Service
public class OrderQueryService {

    private final OrderRepository orderRepository;

    public OrderQueryService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Transactional(readOnly = true)
    public Order getOrder(Long id) {
        return orderRepository.findById(id).orElseThrow();
    }

    @Transactional(readOnly = true)
    public Page<Order> listOrders(String status, Pageable pageable) {
        return orderRepository.findByStatus(status, pageable);
    }
}

Saída:

TEXT
// Execução bem-sucedida
Dimensão Transações de Leitura-Escrita Transações Somente Leitura
Verificação Suja Habilitada (Comparação Campo a Campo) Ignorada
Desempenho Mais lento 20-30% mais rápido
Operação de Escrita Permitida Proibida (escrita lançará exceção)
Aplicável Operações CUD Operações de Consulta
💡 Dica: Todos os métodos de consulta devem incluir @Transactional(readOnly = true); esta é uma otimização de desempenho sem custo.


7. Transação Atômica para Fazer Pedido e Deduzir Estoque

(1) ▶ Exemplo: O Processo Completo de Pedidos

JAVA
@Service
public class OrderServiceImpl implements OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;
    private final AuditService auditService;

    public OrderServiceImpl(OrderRepository orderRepository,
                            ProductRepository productRepository,
                            AuditService auditService) {
        this.orderRepository = orderRepository;
        this.productRepository = productRepository;
        this.auditService = auditService;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(Long productId, Integer quantity) {
        // 1. Encontrar produto
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new RuntimeException("Product not found: " + productId));

        // 2. Verificar estoque
        if (product.getStock() < quantity) {
            throw new RuntimeException("Insufficient stock: available="
                + product.getStock() + ", requested=" + quantity);
        }

        // 3. Deduzir estoque (na mesma transação)
        product.deductStock(quantity);

        // 4. Criar pedido com item de pedido
        Order order = new Order();
        order.addItem(product, quantity);
        Order saved = orderRepository.save(order);

        // 5. Log de auditoria (em transação separada via REQUIRES_NEW)
        auditService.logAudit("CREATE_ORDER",
            "Order " + saved.getId() + " created for product " + productId);

        return saved;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Order cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found: " + orderId));

        if (!"PENDING".equals(order.getStatus())) {
            throw new RuntimeException("Cannot cancel order with status: " + order.getStatus());
        }

        // Restaurar estoque para cada item
        for (OrderItem item : order.getItems()) {
            item.getProduct().addStock(item.getQuantity());
        }

        order.setStatus("CANCELLED");
        auditService.logAudit("CANCEL_ORDER", "Order " + orderId + " cancelled");
        return order;
    }
}

Saída:

TEXT
// Execução bem-sucedida

8. Exemplo Abrangente: Implementação Completa da Camada de Serviço do OrderFlow

JAVA
// ProductService.java
package com.orderflow.service;

import com.orderflow.model.Product;
import com.orderflow.repository.ProductRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;

@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @Transactional(readOnly = true)
    public Product getProduct(Long id) {
        return productRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Product not found"));
    }

    @Transactional(readOnly = true)
    public Page<Product> listProducts(Pageable pageable) {
        return productRepository.findAll(pageable);
    }

    @Transactional(readOnly = true)
    public List<Product> searchProducts(String keyword) {
        return productRepository.findByNameContaining(keyword);
    }

    @Transactional
    public Product createProduct(String name, BigDecimal price, Integer stock) {
        return productRepository.save(new Product(name, price, stock));
    }

    @Transactional
    public Product updateProduct(Long id, String name, BigDecimal price, Integer stock) {
        Product product = getProduct(id);
        product.setName(name);
        product.setPrice(price);
        product.setStock(stock);
        return product;
    }
}

// OrderService.java
package com.orderflow.service;

import com.orderflow.model.Order;
import com.orderflow.model.Product;
import com.orderflow.repository.OrderRepository;
import com.orderflow.repository.ProductRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    private final OrderRepository orderRepository;
    private final ProductRepository productRepository;

    public OrderService(OrderRepository orderRepository,
                        ProductRepository productRepository) {
        this.orderRepository = orderRepository;
        this.productRepository = productRepository;
    }

    @Transactional(rollbackFor = Exception.class)
    public Order createOrder(Long productId, Integer quantity) {
        Product product = productRepository.findById(productId)
            .orElseThrow(() -> new RuntimeException("Product not found"));
        if (product.getStock() < quantity) {
            throw new RuntimeException("Insufficient stock");
        }
        product.deductStock(quantity);
        Order order = new Order();
        order.addItem(product, quantity);
        return orderRepository.save(order);
    }

    @Transactional(rollbackFor = Exception.class)
    public Order cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found"));
        order.getItems().forEach(item -> item.getProduct().addStock(item.getQuantity()));
        order.setStatus("CANCELLED");
        return order;
    }

    @Transactional(readOnly = true)
    public Order getOrder(Long orderId) {
        return orderRepository.findByIdWithItems(orderId)
            .orElseThrow(() -> new RuntimeException("Order not found"));
    }
}

❓ Perguntas Frequentes

P A anotação @Transactional deve ser aplicada à interface ou à classe de implementação?
R É recomendado aplicá-la aos métodos da classe de implementação. Quando aplicada a uma interface, proxies baseados em CGLIB não podem interceptar anotações em interfaces. O Spring também recomenda oficialmente aplicá-la em classes concretas.
P A anotação @Transactional entrará em vigor quando métodos dentro da mesma classe chamam uns aos outros?
R Não. Transações Spring são baseadas em proxy; quando métodos dentro da mesma classe chamam uns aos outros, o proxy não é invocado, então a transação não entra em vigor. Soluções: 1) Dividi-los em serviços diferentes; 2) Injetar seu próprio proxy @Lazy private OrderService self;.
P O que acontece se uma operação de escrita for realizada quando readOnly=true?
R Depende da configuração do banco de dados e do pool de conexões. Hibernate lançará uma exceção, enquanto alguns drivers de banco de dados irão ignorá-lo. Não realize operações de escrita dentro de transações somente leitura.
P Qual é a diferença entre NESTED e REQUIRES_NEW?
R NESTED é uma transação aninhada (savepoint); quando a transação externa é revertida, a interna também é revertida. REQUIRES_NEW é uma transação independente; reverter a externa não afeta a interna. Use REQUIRES_NEW para commits independentes, e use NESTED para rollbacks parciais.
P Quais são as causas comuns de falhas de transação?
R 1) Chamadas mútuas entre métodos do mesmo tipo; 2) Métodos não são public; 3) Exceções são engolidas por um bloco catch; 4) Por padrão, apenas RuntimeExceptions são revertidas; exceções verificadas não são revertidas.
P Como posso verificar se uma transação entrou em vigor?
R 1) Habilite spring.jpa.show-sql=true para monitorar a execução SQL; 2) Defina logging.level.org.springframework.transaction=DEBUG para visualizar o log de transações; 3) Lance intencionalmente uma exceção para verificar o rollback.

📖 Resumo


📝 Exercícios

  1. Exercício Básico (Dificuldade ⭐): Adicione a anotação @Transactional ao ProductService e OrderService do OrderFlow para verificar a atomicidade da dedução de estoque durante o pedido—lance intencionalmente uma exceção após a dedução de estoque para confirmar que o pedido não será criado.

  2. Exercício Avançado (Dificuldade: ⭐⭐): Crie um AuditService que use o comportamento de propagação REQUIRES_NEW para garantir que logs de auditoria não sejam perdidos mesmo que a transação de criação de pedido seja revertida.

  3. Questão Desafio (Dificuldade: ⭐⭐⭐): Simule um cenário de pedidos concorrentes (duas requisições simultaneamente tentando comprar um item com apenas uma unidade em estoque), observe o problema de overselling, e considere como resolvê-lo usando locking otimista (@Version) ou locking pessimista (SELECT FOR UPDATE).

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%