Gerenciamento de Transações com Spring Framework

Conceitos Fundamentais de Transações em Banco de Dados

Uma transação em banco de dados representa uma unidade lógica de trabalho, composta por uma ou mais operações. Essas operações devem ser executadas de forma atômica: ou todas são concluídas com sucesso, ou nenhuma delas é efetivada, garantindo a integridade dos dados.

Propriedades ACID:

  • Atomicidade (Atomicity): Assegura que todas as operações dentro de uma transação sejam tratadas como uma única unidade. Ou todas as operações são realizadas, ou nenhuma é.
  • Consistência (Consistency): Garante que uma transação leve o banco de dados de um estado válido para outro estado válido. Modificações devem respeitar restrições como chaves primárias, estrangeiras, unicidade e regras de negócio (ex: saldo não pode ser negativo).
  • Isolamento (Isolation): Múltiplas transações podem ser executadas concorrentemente sem interferir umas nas outras. Isso é crucial para manter a segurança e a correção dos dados em cenários de alta concorrência.
  • Durabilidade (Durability): Uma vez que uma transação é confirmada (commit), suas alterações são permanentes e sobrevivem a falhas subsequentes do sistema.

Níveis de Isolamento de Transação:

Os níveis de isolamento definem o grau em que uma transação está protegida de modifiacções feitas por outras transações concorrentes.

Nível de Isolamento Mecanismo Subjacente Leitura Suja (Dirty Read) Leitura Não Repetível (Non-repeatable Read) Leitura Fantasma (Phantom Read)
READ UNCOMMITTED Sem bloqueoi Sim Sim Sim
READ COMMITTED Bloqueio na escrita (linha), sem bloqueio na leitura Não Sim Sim
REPEATABLE READ Bloqueio na leitura e escrita (linha) Não Não Sim
SERIALIZABLE Bloqueio na tabela inteira Não Não Não
  • Leitura Suja: Ocorre quando uma transação lê dados que foram modificados por outra transação ainda não confirmada. O dado lido pode ser revertido posteriormente.
  • Leitura Não Repetível: Acontece dentro da mesma transação, onde múltiplas leituras da mesma linha retornam valores diferentes porque outra transação modificou e confirmou os dados entre as leituras.
  • Leitura Fantasma: Similar à Leitura Não Repetível, mas se aplica a consultas de intervalo. Múltiplas execuções da mesma consulta de intervalo dentro de uma transação retornam um conjunto de linhas diferente porque outra transação inseriu ou removeu linhas que satisfazem o critério de consulta.

O nível de isolamento padrão no MySQL é REPEATABLE READ, enquanto Oracle e PostgreSQL utilizam READ COMMITTED por padrão. Mesmo sem o nível SERIALIZABLE, muitos bancos de dados implementam mecanismos como MVCC (Multi-Version Concurrency Control) para oferecer segurança de dados.

Gerenciamento de Transações com JDBC

Configuração de Fontes de Dados (DataSource)

O DriverManager (introduzido no JDBC 1.0) é uma forma básica de obter conexões, mas não suporta pooling. O DataSource (introduzido no JDBC 2.0) é preferível por suportar pools de conexão, permitindo a reutilização de conexões estabelecidas.

Exemplo de obtenção de conexão com DriverManager


String urlConexao = "jdbc:mysql://localhost:3306/meu_banco";
String usuario = "nome_usuario";
String senha = "senha_acesso";
Connection conexao = DriverManager.getConnection(urlConexao, usuario, senha);
   

Exemplos de configuração de DataSource com pooling:


// HikariCP (recomendado)
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/meu_banco");
hikariDataSource.setUsername("nome_usuario");
hikariDataSource.setPassword("senha_acesso");
hikariDataSource.setMaximumPoolSize(10); // Define o tamanho máximo do pool

// DBCP (Apache Commons DBCP)
BasicDataSource dbcpDataSource = new BasicDataSource();
dbcpDataSource.setUrl("jdbc:mysql://localhost:3306/meu_banco");
dbcpDataSource.setUsername("nome_usuario");
dbcpDataSource.setPassword("senha_acesso");
dbcpDataSource.setInitialSize(5);  // Número inicial de conexões
dbcpDataSource.setMaxTotal(10);    // Número total máximo de conexões
   

Controle Manual de Transações (Programático)

O controle manual de transações, também conhecido como transações programáticas, envolve a manipulação direta do ciclo de vida da transação.


Connection conexao = null;
try {
   // Obter uma conexão do DataSource
   conexao = meuDataSource.getConnection();

   // Desabilitar auto-commit para iniciar a transação
   conexao.setAutoCommit(false);

   // Operação 1: Débito
   PreparedStatement stmtDebito = conexao.prepareStatement("UPDATE conta SET saldo = saldo - ? WHERE id = ?");
   stmtDebito.setInt(1, 100);
   stmtDebito.setInt(2, 1);
   stmtDebito.executeUpdate();

   // Operação 2: Crédito
   PreparedStatement stmtCredito = conexao.prepareStatement("UPDATE conta SET saldo = saldo + ? WHERE id = ?");
   stmtCredito.setInt(1, 100);
   stmtCredito.setInt(2, 2);
   stmtCredito.executeUpdate();

   // Confirmar a transação se ambas as operações foram bem-sucedidas
   conexao.commit();
} catch (SQLException e) {
   // Em caso de erro, reverter a transação
   if (conexao != null) {
       try {
           conexao.rollback();
       } catch (SQLException ex) {
           ex.printStackTrace(); // Logar o erro de rollback
       }
   }
   e.printStackTrace(); // Logar o erro original
} finally {
   // Restaurar o auto-commit e fechar a conexão
   if (conexao != null) {
       try {
           conexao.setAutoCommit(true);
           conexao.close();
       } catch (SQLException e) {
           e.printStackTrace(); // Logar o erro ao fechar a conexão
       }
   }
}
   

Gerenciamento de Transações com Spring Framework

O Spring simplifica o gerenciamento de transações, oferecendo duas abordagens principais: transações programáticas e declarativas.

  • Transações Programáticas: Envolvem controle manual de commit e rollback, similar ao exemplo JDBC.
  • Transações Declarativas: Automatizam o gerenciamento de transações através de anotações ou configuração XML. A anotação @Transactional do Spring é um exemplo proeminente.

Gerenciador de Transações (Transaction Manager)

O Spring utiliza a interface PlatformTransactionManager como abstração central para o gerenciamento de transações.


public interface PlatformTransactionManager {
   TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
   void commit(TransactionStatus status) throws TransactionException;
   void rollback(TransactionStatus status) throws TransactionException;
}
   

Implementações comuns incluem:

  • DataSourceTransactionManager: Para JDBC e MyBatis.
  • HibernateTransactionManager: Para Hibernate.
  • JpaTransactionManager: Para JPA.
  • JtaTransactionManager: Para transações distribuídas (JTA).

Embora TransactionTemplate exista como um wrapper para PlatformTransactionManager, seu uso é menos comum em favor da abordagem declarativa.

Configuração do Gerenciador de Transações no Spring (JavaConfig)

Para utilizar transações declarativas ou programáticas com JDBC, é necesssário configurar a fonte de dados e o gerenciador de transações.


@Configuration
@EnableTransactionManagement // Habilita o suporte a anotações de transação
public class ConfiguracaoApp {

   // 1. Configuração da Fonte de Dados (DataSource)
   @Bean
   public DataSource dataSource() {
       HikariDataSource dataSource = new HikariDataSource();
       dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/meu_banco");
       dataSource.setUsername("root");
       dataSource.setPassword("senha_admin");
       dataSource.setMaximumPoolSize(15);
       return dataSource;
   }

   // 2. Configuração do Gerenciador de Transações
   // Requer a fonte de dados configurada
   @Bean
   public PlatformTransactionManager transactionManager(DataSource dataSource) {
       return new DataSourceTransactionManager(dataSource);
   }

   // 3. Configuração do JdbcTemplate (opcional, mas comum para JDBC)
   // Requer a fonte de dados configurada
   @Bean
   public JdbcTemplate jdbcTemplate(DataSource dataSource) {
       return new JdbcTemplate(dataSource);
   }
}
   

Transações Programáticas com Spring

Mesmo com o Spring, é possível gerenciar transações manualmente usando PlatformTransactionManager.


@Service
public class ServicoConta {

   @Autowired
   private PlatformTransactionManager gerenciarTransacao; // Injete o gerenciador configurado

   @Autowired
   private JdbcTemplate jdbcTemplate; // Para executar operações SQL

   public void transferir(int idRemetente, int idDestinatario, int valor) {
       // Define as propriedades da transação (pode ser mais detalhado)
       DefaultTransactionDefinition definicao = new DefaultTransactionDefinition();
       TransactionStatus status = gerenciarTransacao.getTransaction(definicao); // Obtém o status da transação

       try {
           // Lógica de negócio: Realizar as operações
           jdbcTemplate.update("UPDATE conta SET saldo = saldo - ? WHERE id = ?", valor, idRemetente);
           jdbcTemplate.update("UPDATE conta SET saldo = saldo + ? WHERE id = ?", valor, idDestinatario);

           gerenciarTransacao.commit(status); // Confirma a transação
       } catch (Exception ex) {
           gerenciarTransacao.rollback(status); // Reverte a transação em caso de erro
           throw ex; // Relança a exceção
       }
   }
}
   

Transações Declarativas com Spring (@Transactional)

A abordagem declarativa é a mais comum e recomendada no Spring, simplificando o código e reduzindo a verbosidade.


// A configuração do DataSource e TransactionManager é a mesma do exemplo anterior.
// O Spring Boot geralmente configura automaticamente o DataSource e o TransactionManager
// se as dependências apropriadas estiverem presentes.

@Configuration
@EnableTransactionManagement
public class ConfiguracaoApp {
   // ... configuração do DataSource e TransactionManager ...
}

@Service
public class ServicoConta {

   @Autowired
   private JdbcTemplate jdbcTemplate;

   // A anotação @Transactional indica que este método deve ser executado dentro de uma transação.
   // Se ocorrer uma exceção (unchecked), a transação será automaticamente revertida.
   @Transactional
   public void transferir(int idRemetente, int idDestinatario, int valor) {
       // Operação de débito
       jdbcTemplate.update("UPDATE conta SET saldo = saldo - ? WHERE id = ?", valor, idRemetente);

       // Operação de crédito
       jdbcTemplate.update("UPDATE conta SET saldo = saldo + ? WHERE id = ?", valor, idDestinatario);

       // O commit é feito automaticamente ao final do método se não houver exceções.
   }
}
   

Detalhes da Anotação @Transactional

A anotação @Transactional pode ser aplicada a classes ou métodos. Ela oferece configurações para:

  • Nível de Isolamento: Define o nível de isolamento da transação (ex: Isolation.READ\_COMMITTED).
  • Propagação: Controla como as transações se comportam quando um método transacional chama outro método transacional (ex: Propagation.REQUIRED, Propagation.REQUIRES\_NEW).

É importante notar que @Transactional não pode ser aplicada a métodos private. Além disso, quando a anotação é aplicada a uma classe, todos os métodos públicos herdaram essa configuração transacional.

Tags: Spring jdbc Transações Gerenciamento de Transações ACID

Publicado em 7-30 08:00