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
commiterollback, 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
@Transactionaldo 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.