💻 Exemplos: Capítulo 08: Migrações de Banco com Flyway e Gestão de Transações (@Transactional)
Especialização em Backend com Java & Spring Boot • Demonstrações Práticas Compiláveis
💻 Código de Exemplo 01 — Scripts de Migração Versionados com Flyway
-- src/main/resources/db/migration/V1__criar_tabela_usuarios.sql
CREATE TABLE usuarios (
id BIGSERIAL PRIMARY KEY,
nome VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
criado_em TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- src/main/resources/db/migration/V2__adicionar_coluna_status_usuarios.sql
ALTER TABLE usuarios ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'ATIVO';
CREATE INDEX idx_usuarios_status ON usuarios (status);
# application.properties
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.jpa.hibernate.ddl-auto=validate
💻 Código de Exemplo 02 — @Transactional com Propagation.REQUIRED e rollbackFor
package com.empresa.api.service;
import com.empresa.api.model.Pedido;
import com.empresa.api.repository.PedidoRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PedidoService {
private final PedidoRepository repository;
private final EstoqueService estoqueService;
public PedidoService(PedidoRepository repository, EstoqueService estoqueService) {
this.repository = repository;
this.estoqueService = estoqueService;
}
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public Pedido finalizar(Long pedidoId) throws EstoqueInsuficienteException {
Pedido pedido = repository.findById(pedidoId)
.orElseThrow(() -> new IllegalArgumentException("Pedido não encontrado"));
estoqueService.reservarItens(pedido); // participa da mesma transação (REQUIRED)
pedido.marcarComoFinalizado();
return repository.save(pedido);
// Se EstoqueInsuficienteException (checada) for lançada, rollbackFor garante
// que o rollback ocorra mesmo não sendo RuntimeException.
}
}
package com.empresa.api.service;
public class EstoqueInsuficienteException extends Exception {
public EstoqueInsuficienteException(String mensagem) {
super(mensagem);
}
}
💻 Código de Exemplo 03 — Propagation.REQUIRES_NEW para Auditoria Isolada de Rollback
O log de auditoria deve sobreviver mesmo que a operação principal falhe depois — por isso roda em uma transação própria (REQUIRES_NEW), independente da transação que a chamou.
package com.empresa.api.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AuditLogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registrarLog(String evento) {
// Mesmo que o método chamador sofra rollback logo depois,
// este INSERT já foi commitado em sua própria transação.
System.out.println("LOG AUDITADO: " + evento);
}
}
package com.empresa.api.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Service
public class ContaService {
private final AuditLogService auditLogService;
public ContaService(AuditLogService auditLogService) {
this.auditLogService = auditLogService;
}
// READ_COMMITTED evita dirty reads em operações financeiras concorrentes.
@Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class)
public void debitar(Long id, BigDecimal valor) {
auditLogService.registrarLog("Tentativa de débito de " + valor + " na conta " + id);
if (valor.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Valor de débito inválido");
// A transação principal sofre rollback, mas o log acima já foi persistido.
}
// ... lógica de débito ...
}
}