12 - Gerenciadores de Transações Spring
Introdução
Em aplicações empresariais, as transações são essenciais para garantir que um grupo de operações ou seja bem-sucedido completamente ou falhe completamente.
Spring oferece uma poderosa abstração de gerenciamento de transações que separa o tratamento de transações da lógica de negócio, permitindo que os desenvolvedores se concentrem na funcionalidade da aplicação em vez do código repetitivo (boilerplate) de transação.
Spring suporta duas abordagens principais:
- Gerenciamento Programático de Transações
- Gerenciamento Declarativo de Transações
Ele também fornece diferentes implementações de transaction manager dependendo da tecnologia utilizada.
Por que Precisamos de Transaction Managers?
Sem um transaction manager, os desenvolvedores precisariam:
- Abrir uma transação
- Executar a lógica de negócio
- Realizar commit se for bem-sucedido
- Realizar rollback se ocorrer um erro
- Fechar recursos
Exemplo sem Spring:
Connection connection = dataSource.getConnection();
try {
connection.setAutoCommit(false);
// Business logic
connection.commit();
} catch (Exception ex) {
connection.rollback();
} finally {
connection.close();
}
Problemas com esta abordagem:
- Código repetitivo
- Difícil de manter
- Propenso a erros
- A lógica de negócio torna-se acoplada ao gerenciamento de transações
- Difícil de lidar com cenários de transação complexos
Spring resolve esses problemas fornecendo uma abstração de transação.
O que é um Transaction Manager?
Um Transaction Manager é um componente Spring responsável por controlar transações.
Suas principais responsabilidades incluem:
- Iniciar transações
- Realizar commit de transações
- Realizar rollback de transações
- Gerenciar limites de transação
- Coordenar recursos transacionais
- Integrar transações com tecnologias de persistência
A abstração central do Spring é:
PlatformTransactionManager
Conceitualmente:
Application
|
v
Transaction Manager
|
+---- Begin Transaction
|
+---- Execute Business Logic
|
+---- Commit
| or
+---- Rollback
A interface PlatformTransactionManager fornece as operações fundamentais de transação:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(
TransactionDefinition definition
);
void commit(TransactionStatus status);
void rollback(TransactionStatus status);
}
Implementações de Transaction Manager
Spring oferece diferentes implementações de transaction manager dependendo da tecnologia.
Exemplos comuns incluem:
DataSourceTransactionManagerJpaTransactionManagerJtaTransactionManagerR2dbcTransactionManager
DataSourceTransactionManager
DataSourceTransactionManager é usado para aplicações que trabalham diretamente com JDBC.
Exemplo:
@Bean
public PlatformTransactionManager transactionManager(
DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
Arquitetura típica:
Spring
|
v
DataSourceTransactionManager
|
v
JDBC
|
v
Database
Quando Usar
Use-o quando:
- Trabalhando diretamente com JDBC
- Usando
JdbcTemplate - Não utilizando JPA/Hibernate
Vantagens
- Leve
- Simples
- Bom controle sobre transações JDBC
Desvantagens
- Específico para JDBC
- Não destinado ao gerenciamento de entidades JPA
JpaTransactionManager
JpaTransactionManager é comumente usado com:
- Spring Data JPA
- JPA
- Hibernate
Exemplo:
@Bean
public PlatformTransactionManager transactionManager(
EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
Arquitetura típica:
Spring
|
v
JpaTransactionManager
|
v
JPA / Hibernate
|
v
Database
Em aplicações Spring Boot que utilizam Spring Data JPA, o Spring Boot geralmente configura a infraestrutura de transações necessária automaticamente.
Vantagens
- Integra-se com JPA
- Integra-se com Hibernate
- Gerencia
EntityManager - Suporta sincronização de transações
- Funciona naturalmente com
@Transactional
Quando Usar
Use-o quando sua aplicação utiliza:
Spring Data JPA
+
Hibernate
+
Relational Database
Esta é uma das configurações de transaction manager mais comuns em aplicações Spring.
JtaTransactionManager
JtaTransactionManager é projetado para transações baseadas em JTA e cenários de transações distribuídas.
Ele pode coordenar transações envolvendo múltiplos recursos.
Por exemplo:
Application
|
v
JtaTransactionManager
|
+---- Database A
|
+---- Database B
|
+---- Message Broker
Este tipo de transação é às vezes chamado de transação distribuída.
Quando Usar
Casos de uso típicos incluem:
- Múltiplos bancos de dados
- Múltiplos recursos transacionais
- Aplicações empresariais legadas
- Transações XA
Vantagens
- Pode coordenar múltiplos recursos transacionais
- Suporta transações distribuídas
Desvantagens
- Mais complexo
- Maior overhead operacional
- Pode afetar negativamente o desempenho
- Geralmente desnecessário para arquiteturas de microserviços modernas
R2dbcTransactionManager
Para aplicações reativas usando R2DBC, o Spring oferece:
R2dbcTransactionManager
Exemplo:
@Bean
ReactiveTransactionManager transactionManager(
ConnectionFactory connectionFactory) {
return new R2dbcTransactionManager(connectionFactory);
}
Arquitetura típica:
Spring WebFlux
|
v
Reactive Transaction Manager
|
v
R2DBC
|
v
Database
Isso é usado com tipos reativos como:
Mono<T>
Flux<T>
Gerenciamento Programático de Transações
O que é Gerenciamento Programático de Transações?
Gerenciamento programático de transações significa que o desenvolvedor controla explicitamente o comportamento da transação através de código.
O desenvolvedor decide:
- Quando a transação começa
- Quando ela faz commit
- Quando ela faz rollback
- Qual configuração de transação é usada
Spring oferece dois mecanismos importantes:
PlatformTransactionManagerTransactionTemplate
Transações Programáticas com TransactionTemplate
TransactionTemplate é uma das maneiras mais fáceis de implementar transações programáticas.
Exemplo:
@Service
public class PaymentService {
private final TransactionTemplate transactionTemplate;
public PaymentService(
PlatformTransactionManager transactionManager) {
this.transactionTemplate =
new TransactionTemplate(transactionManager);
}
public void processPayment() {
transactionTemplate.execute(status -> {
// Business logic
return null;
});
}
}
Spring gerencia:
Begin
|
v
Business Logic
|
+---- Success ---> Commit
|
+---- Exception --> Rollback
Rollback Programático
Você pode marcar explicitamente uma transação para rollback:
transactionTemplate.execute(status -> {
try {
// Business logic
} catch (Exception ex) {
status.setRollbackOnly();
}
return null;
});
A transação será revertida (rollback) quando o callback da transação terminar.
Configuração Programática de Transações
Você pode configurar propriedades de transação usando TransactionTemplate.
Por exemplo:
transactionTemplate.setTimeout(30);
transactionTemplate.setReadOnly(true);
Você também pode configurar a propagação:
transactionTemplate.setPropagationBehavior(
TransactionDefinition.PROPAGATION_REQUIRES_NEW
);
E isolamento:
transactionTemplate.setIsolationLevel(
TransactionDefinition.ISOLATION_READ_COMMITTED
);
Isso dá às transações programáticas um alto nível de controle.
Vantagens das Transações Programáticas
Controle Granular
Você decide exatamente onde a transação começa e termina.
transactionTemplate.execute(status -> {
// Transaction boundary is explicit
return null;
});
Comportamento Dinâmico da Transação
As configurações de transação podem ser configuradas programaticamente.
Por exemplo:
transactionTemplate.setTimeout(30);
ou:
transactionTemplate.setReadOnly(true);
Útil para Fluxos de Trabalho Complexos
Transações programáticas podem ser úteis quando os limites de transação dependem de condições de tempo de execução.
Exemplo:
if condition A
Transaction A
else
Transaction B
Desvantagens das Transações Programáticas
Mais Boilerplate (Código Repetitivo)
A lógica da transação é misturada com o código da aplicação.
transactionTemplate.execute(status -> {
// Transaction-related code
// Business logic
return null;
});
Mais Difícil de Manter
Se muitos métodos utilizarem transações programáticas, a aplicação pode se tornar repetitiva.
A Lógica de Negócio Pode se Tornar Acoplada às Transações
Em vez de:
@Transactional
public void processOrder() {
}
você pode ter:
transactionTemplate.execute(status -> {
processOrder();
return null;
});
A infraestrutura de transação torna-se mais visível no código de negócio.
Quando Você Deve Usar Transações Programáticas?
O gerenciamento programático de transações é útil quando:
- Os limites de transação são dinâmicos
- Controle de transação avançado é necessário
- Comportamentos de transação diferentes são necessários dentro de um método
- Lógica de rollback condicional é necessária
- Transações declarativas não são flexíveis o suficiente
Para a maioria das operações padrão da camada de serviço, as transações declarativas são geralmente preferíveis.
Gerenciamento Declarativo de Transações
O que é Gerenciamento Declarativo de Transações?
O gerenciamento declarativo de transações permite que os desenvolvedores definam o comportamento da transação sem controlar manualmente o ciclo de vida da transação.
A abordagem mais comum é:
@Transactional
Exemplo:
@Service
public class AccountService {
@Transactional
public void transferMoney(
Long from,
Long to,
BigDecimal amount) {
withdraw(from, amount);
deposit(to, amount);
}
}
Spring gerencia automaticamente:
- Criação da transação
- Commit
- Rollback
- Sincronização da transação
- Propagação da transação
- Isolamento
- Ciclo de vida da transação
Como as Transações Declarativas Funcionam
O gerenciamento declarativo de transações depende fortemente de proxies Spring AOP.
Considere:
@Transactional
public void transferMoney() {
}
Spring cria um proxy em torno do bean.
Conceitualmente:
Client
|
v
Spring Proxy
|
+---- Begin Transaction
|
v
Business Method
|
+---- Success ---> Commit
|
+---- Exception --> Rollback
O proxy interage com o transaction manager configurado.
Por exemplo:
@Transactional
|
v
Spring AOP Proxy
|
v
Transaction Manager
|
v
Database
Exemplo de Transação Declarativa
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
// Additional database operations
}
}
Spring inicia automaticamente a transação antes de executar o método.
Se o método terminar com sucesso:
Commit
Se uma exceção apropriada causar rollback:
Rollback
Configuração de Transação Declarativa
Em aplicações Spring Boot modernas, o gerenciamento de transações é frequentemente configurado automaticamente.
Por exemplo, ao usar:
Spring Boot
+
Spring Data JPA
+
Hibernate
Spring Boot pode configurar automaticamente a infraestrutura de transações necessária.
O gerenciamento explícito de transações também pode ser habilitado usando:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
}
Em muitas aplicações Spring Boot, no entanto, adicionar explicitamente @EnableTransactionManagement é desnecessário porque o Boot fornece a auto-configuração necessária.
Vantagens das Transações Declarativas
Código Mais Limpo
A lógica de negócio permanece focada na operação de negócio.
@Transactional
public void processOrder() {
// Business logic
}
Menos Boilerplate (Código Repetitivo)
Você não precisa:
begin
commit
rollback
close
Manutenção Mais Fácil
O comportamento da transação é declarado diretamente no método.
Melhor Legibilidade
Os desenvolvedores podem ver imediatamente que um método é transacional:
@Transactional
public void transferMoney() {
}
Desvantagens das Transações Declarativas
Limitações do Proxy
Transações declarativas são geralmente implementadas através de proxies Spring.
Isso cria uma limitação importante chamada self-invocation (auto-invocação).
Exemplo:
@Service
public class UserService {
public void methodA() {
methodB();
}
@Transactional
public void methodB() {
// Transaction
}
}
Chamando:
methodA();
não ativa necessariamente o proxy transacional para methodB().
A razão é que a chamada ocorre internamente dentro do mesmo objeto.
Conceitualmente:
External Caller
|
v
Spring Proxy
|
v
methodA()
|
v
methodB()
A chamada de methodA() para methodB() não passa pelo proxy novamente.
Transações Programáticas vs Declarativas
| Característica | Programática | Declarativa |
|---|---|---|
| Facilidade de Uso | Baixa | Alta |
| Boilerplate | Alto | Baixo |
| Legibilidade | Média | Alta |
| Flexibilidade | Muito Alta | Alta |
| Manutenção | Mais Difícil | Mais Fácil |
| Controle da Transação | Explícito | Implícito |
| Recomendado | Casos Especiais | Maioria das Aplicações |
Múltiplos Transaction Managers
Uma aplicação pode ter mais de um transaction manager.
Por exemplo, uma aplicação pode usar dois bancos de dados:
Application
|
+---- Database A
|
+---- Database B
Cada banco de dados pode ter sua própria infraestrutura de transação.
Exemplo:
@Bean
public PlatformTransactionManager userTransactionManager(
EntityManagerFactory userEntityManagerFactory) {
return new JpaTransactionManager(
userEntityManagerFactory
);
}
E:
@Bean
public PlatformTransactionManager auditTransactionManager(
EntityManagerFactory auditEntityManagerFactory) {
return new JpaTransactionManager(
auditEntityManagerFactory
);
}
Você pode selecionar explicitamente um transaction manager:
@Transactional("auditTransactionManager")
public void saveAudit() {
}
Isso diz ao Spring qual transaction manager deve controlar a transação.
TransactionSynchronizationManager
Um componente interno importante do Spring é:
TransactionSynchronizationManager
Ele ajuda o Spring a associar recursos transacionais ao contexto de execução atual.
Ele pode gerenciar recursos como:
- Conexões JDBC
- EntityManagers
- Callbacks de sincronização de transação
- Estado relacionado à transação
Conceitualmente:
Application Thread
|
v
TransactionSynchronizationManager
|
+---- EntityManager
|
+---- JDBC Connection
|
+---- Transaction State
Este mecanismo é importante para permitir que diferentes componentes Spring participem da mesma transação.
Transaction Manager e @Transactional
É importante entender a relação entre esses dois conceitos.
Eles não são a mesma coisa.
@Transactional declara o comportamento da transação:
@Transactional
public void saveOrder() {
}
O Transaction Manager realiza o gerenciamento real da transação.
@Transactional
|
v
Spring AOP Proxy
|
v
Transaction Manager
|
v
Database
Então:
@Transactional
=
Configuração/declaração da transação
Transaction Manager
=
Execução/infraestrutura da transação
Transaction Manager e Propagação
Transaction managers também participam da propagação de transações.
Por exemplo:
@Transactional(
propagation = Propagation.REQUIRES_NEW
)
O transaction manager determina como a transação atual deve ser tratada.
Por exemplo:
Transaction A
|
v
Service A
|
v
REQUIRES_NEW
|
v
Transaction B
A Transação B é executada independentemente da Transação A.
Esta é uma das razões pelas quais entender os transaction managers é importante antes de estudar a propagação de transações em profundidade.
Transaction Manager e Isolamento
Transaction managers também aplicam configurações de isolamento.
Exemplo:
@Transactional(
isolation = Isolation.READ_COMMITTED
)
public void processOrder() {
}
O transaction manager comunica a configuração de transação solicitada ao recurso transacional subjacente.
Níveis de isolamento comuns incluem:
READ_UNCOMMITTED
READ_COMMITTED
REPEATABLE_READ
SERIALIZABLE
O comportamento real depende do banco de dados subjacente e da implementação da transação.
Transaction Manager e Transações Somente Leitura
Uma transação pode ser declarada como somente leitura:
@Transactional(readOnly = true)
public List<User> findUsers() {
return userRepository.findAll();
}
Isso comunica que a transação é destinada à leitura de dados.
Dependendo da tecnologia de persistência, banco de dados e configuração, transações somente leitura podem fornecer otimizações e ajudar a prevenir escritas não intencionais.
Transaction Manager e Timeouts
Transaction managers também podem impor timeouts de transação.
Usando transações declarativas:
@Transactional(timeout = 30)
public void processPayment() {
}
Isso significa que a transação não deve permanecer ativa indefinidamente.
Timeouts são particularmente importantes para evitar que transações de longa duração retenham:
- Conexões de banco de dados
- Locks
- Recursos
por períodos excessivos.
Boas Práticas
Prefira Transações Declarativas
Para a maioria das operações da camada de serviço, use:
@Transactional
em vez de gerenciar transações manualmente.
Coloque Limites de Transação na Camada de Serviço
Um padrão comum é:
Controller
|
v
Service
|
+---- @Transactional
|
v
Repository
Exemplo:
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
paymentRepository.save(order.getPayment());
}
}
A camada de serviço geralmente representa a operação de negócio que precisa ser atômica.
Mantenha as Transações Pequenas
Evite transações desnecessariamente longas.
Bom:
@Transactional
public void saveOrder() {
orderRepository.save(order);
}
Potencialmente problemático:
@Transactional
public void processLargeBatch() {
// Milhares de operações de banco de dados
// Chamadas a APIs externas
// Cálculos longos
// Processamento de arquivos
}
Transações longas podem reter recursos do banco de dados e locks por muito tempo.
Evite Chamadas a APIs Externas Dentro de Transações
Evite:
@Transactional
public void processOrder() {
paymentGateway.call();
orderRepository.save(order);
}
Se a API externa demorar muito para responder, a transação do banco de dados pode permanecer aberta desnecessariamente.
Uma arquitetura melhor pode separar a comunicação externa da transação do banco de dados ou usar processamento assíncrono/orientado a eventos quando apropriado.
Entenda o Transaction Manager Subjacente
Ao depurar problemas de transação, é importante saber qual transaction manager está sendo usado.
Por exemplo:
Spring Data JPA
|
v
JpaTransactionManager
|
v
Hibernate
|
v
Database
Entender essa cadeia facilita o diagnóstico de problemas envolvendo:
- Rollbacks
- Propagação
- Isolamento
- Conexões
- EntityManager
- Contexto de persistência
- Limites de transação
Erros Comuns
Erro 1: Assumir que @Transactional Cria a Transação Por Si Só
@Transactional é uma declaração.
O transaction manager é responsável por gerenciar a transação real.
@Transactional
|
v
Proxy
|
v
Transaction Manager
|
v
Transaction
Erro 2: Usar Transações Programáticas Em Todo Lugar
Transações programáticas fornecem controle, mas o uso excessivo cria complexidade desnecessária.
Prefira:
@Transactional
quando o comportamento da transação é simples.
Erro 3: Ignorar o Comportamento do Proxy
Lembre-se que as transações declarativas do Spring geralmente dependem de proxies.
Portanto, a self-invocation pode impedir que o interceptor transacional seja acionado.
Erro 4: Tornar as Transações Muito Grandes
Transações grandes podem levar a:
- Locks de banco de dados de longa duração
- Esgotamento de conexões
- Aumento de contenção
- Baixo desempenho
- Cenários de rollback difíceis
Arquitetura Recomendada
Uma arquitetura comum do Spring Boot se parece com:
Client
|
v
REST Controller
|
v
Service Layer
|
+---- @Transactional
|
v
Transaction Manager
|
v
Repository Layer
|
v
JPA / Hibernate
|
v
Database
Exemplo:
@RestController
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping("/orders")
public void createOrder(@RequestBody Order order) {
orderService.createOrder(order);
}
}
Serviço:
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
// Other database operations
}
}
O controller não precisa saber como a transação funciona.
O serviço define o limite da transação de negócio.
Resumo
Spring Transaction Managers fornecem a infraestrutura necessária para gerenciar transações em aplicações Spring.
Os conceitos mais importantes são:
PlatformTransactionManageré a principal abstração de transação do Spring.JpaTransactionManageré comumente usado com JPA e Hibernate.DataSourceTransactionManageré usado para aplicações baseadas em JDBC.JtaTransactionManagersuporta transações distribuídas/JTA.R2dbcTransactionManageré usado para transações de banco de dados reativas.- O gerenciamento programático de transações fornece controle explícito.
- O gerenciamento declarativo de transações usa
@Transactionale é a abordagem preferida para a maioria das aplicações. - Spring usa proxies AOP para implementar transações declarativas.
- Múltiplos transaction managers podem existir na mesma aplicação.
TransactionSynchronizationManagerajuda a coordenar recursos transacionais.- Transaction managers trabalham em conjunto com configurações de propagação, isolamento, timeouts e somente leitura.
- Os limites de transação são comumente colocados na camada de serviço.
A relação chave a ser lembrada é:
@Transactional
|
v
Spring AOP Proxy
|
v
Transaction Manager
|
v
Transaction
|
v
Database
Na maioria das aplicações Spring Boot, o desenvolvedor deve focar em definir o limite de transação correto com @Transactional, enquanto o Spring e o Transaction Manager configurado lidam com o ciclo de vida da transação.