Especialização em Sistemas com Java 21 LTS • Capítulo 17


🎯 Visão Geral dos Exemplos Práticos

Neste laboratório prático, implementaremos exemplos corporativos completos explorando:

  1. Transações Bancárias ACID com commit e rollback: transferência monetária atômica entre contas com tratamento de inconsistências.
  2. Camada de Acesso a Dados com o Padrão DAO: encapsulamento de comandos SQL com PreparedStatement e recuperação de chaves auto-incrementais (getGeneratedKeys).
  3. Prevenção Concreta contra SQL Injection: comparação de sanitização via bind de parâmetros posicionais (?).

💻 Exemplo 01 — Sistema Bancário de Transferências ACID com JDBC

package com.empresa.persistencia;

import java.math.BigDecimal;
import java.sql.*;

/**
 * Motor de transferências financeiras com atomicidade garantida e isolamento transacional.
 */
public class TransacaoBancariaAcidJdbc {

    private static final String JDBC_URL = "jdbc:h2:mem:bancodb;DB_CLOSE_DELAY=-1";

    public static void inicializarBanco(Connection conn) throws SQLException {
        try (Statement st = conn.createStatement()) {
            st.execute("""
                CREATE TABLE contas (
                    numero_conta VARCHAR(20) PRIMARY KEY,
                    titular VARCHAR(100) NOT NULL,
                    saldo DECIMAL(15,2) NOT NULL
                );
                INSERT INTO contas VALUES ('CC-1001', 'Alice Santos', 5000.00);
                INSERT INTO contas VALUES ('CC-1002', 'Bruno Rocha', 1200.00);
            """);
        }
        System.out.println("✅ Tabela 'contas' inicializada com dados de teste.");
    }

    public static boolean realizarTransferencia(
            Connection conn,
            String contaOrigem,
            String contaDestino,
            BigDecimal valor) {

        String sqlVerifica = "SELECT saldo FROM contas WHERE numero_conta = ? FOR UPDATE";
        String sqlDebito = "UPDATE contas SET saldo = saldo - ? WHERE numero_conta = ?";
        String sqlCredito = "UPDATE contas SET saldo = saldo + ? WHERE numero_conta = ?";

        try {
            // 1. Desativar commit automático para abrir fronteira transacional ACID
            conn.setAutoCommit(false);

            // 2. Checagem de saldo com lock exclusivo (FOR UPDATE)
            try (PreparedStatement psVerifica = conn.prepareStatement(sqlVerifica)) {
                psVerifica.setString(1, contaOrigem);
                try (ResultSet rs = psVerifica.executeQuery()) {
                    if (!rs.next()) {
                        throw new SQLException("Conta de origem não encontrada: " + contaOrigem);
                    }
                    BigDecimal saldoAtual = rs.getBigDecimal("saldo");
                    if (saldoAtual.compareTo(valor) < 0) {
                        throw new SQLException(String.format("Saldo insuficiente: Saldo R$ %.2f, Saque R$ %.2f", saldoAtual, valor));
                    }
                }
            }

            // 3. Execução do Débito
            try (PreparedStatement psDebito = conn.prepareStatement(sqlDebito)) {
                psDebito.setBigDecimal(1, valor);
                psDebito.setString(2, contaOrigem);
                psDebito.executeUpdate();
            }

            // Simulação de possível falha ou regra de negócio restritiva
            if (valor.compareTo(new BigDecimal("10000.00")) > 0) {
                throw new SQLException("Valor excede o limite diário de transferência sem validação prévia!");
            }

            // 4. Execução do Crédito
            try (PreparedStatement psCredito = conn.prepareStatement(sqlCredito)) {
                psCredito.setBigDecimal(1, valor);
                psCredito.setString(2, contaDestino);
                psCredito.executeUpdate();
            }

            // 5. Confirmação atômica de todas as instruções
            conn.commit();
            System.out.printf("   ✅ [COMMIT] Transferência de R$ %.2f de %s para %s liquidada com sucesso!%n",
                    valor, contaOrigem, contaDestino);
            return true;

        } catch (SQLException e) {
            try {
                // Em caso de qualquer erro, desfazemos todas as alterações
                conn.rollback();
                System.err.printf("   🚨 [ROLLBACK] Transação desfeita com integridade preservada: %s%n", e.getMessage());
            } catch (SQLException rollbackEx) {
                System.err.println("Falha catastrófica ao tentar rollback: " + rollbackEx.getMessage());
            }
            return false;
        } finally {
            try {
                conn.setAutoCommit(true); // Restaura o comportamento padrão
            } catch (SQLException ignored) {}
        }
    }

    public static void exibirSaldos(Connection conn) throws SQLException {
        String sql = "SELECT numero_conta, titular, saldo FROM contas ORDER BY numero_conta";
        try (Statement st = conn.createStatement();
             ResultSet rs = st.executeQuery(sql)) {
            System.out.println("--------------------------------------------------------------------------------");
            while (rs.next()) {
                System.out.printf("   Conta: %s | Titular: %-15s | Saldo: R$ %9.2f%n",
                        rs.getString("numero_conta"), rs.getString("titular"), rs.getBigDecimal("saldo"));
            }
            System.out.println("--------------------------------------------------------------------------------");
        }
    }

    public static void main(String[] args) {
        System.out.println("================================================================================");
        System.out.println("           SIMULADOR BANCÁRIO JDBC: TRANSAÇÕES ACID COM ROLLBACK                ");
        System.out.println("================================================================================");

        try (Connection conn = DriverManager.getConnection(JDBC_URL, "sa", "")) {
            inicializarBanco(conn);
            exibirSaldos(conn);

            // Cenário 1: Transferência legítima de R$ 1.500
            System.out.println("--- 1. Executando Transferência Válida (R$ 1.500,00) ---");
            realizarTransferencia(conn, "CC-1001", "CC-1002", new BigDecimal("1500.00"));
            exibirSaldos(conn);

            // Cenário 2: Transferência com saldo insuficiente (R$ 8.000)
            System.out.println("--- 2. Tentativa com Saldo Insuficiente (R$ 8.000,00) ---");
            realizarTransferencia(conn, "CC-1001", "CC-1002", new BigDecimal("8000.00"));
            exibirSaldos(conn); // Saldos devem permanecer inalterados!

            // Cenário 3: Transferência que excede teto de segurança (R$ 15.000)
            System.out.println("--- 3. Tentativa Excedendo Limite Operacional (R$ 15.000,00) ---");
            realizarTransferencia(conn, "CC-1001", "CC-1002", new BigDecimal("15000.00"));
            exibirSaldos(conn);

        } catch (SQLException e) {
            System.err.println("Erro geral de conexão: " + e.getMessage());
        }

        System.out.println("================================================================================");
    }
}

📋 Resultado Esperado no Console

================================================================================
           SIMULADOR BANCÁRIO JDBC: TRANSAÇÕES ACID COM ROLLBACK                
================================================================================
✅ Tabela 'contas' inicializada com dados de teste.
--------------------------------------------------------------------------------
   Conta: CC-1001 | Titular: Alice Santos    | Saldo: R$   5000.00
   Conta: CC-1002 | Titular: Bruno Rocha     | Saldo: R$   1200.00
--------------------------------------------------------------------------------
--- 1. Executando Transferência Válida (R$ 1.500,00) ---
   ✅ [COMMIT] Transferência de R$ 1500.00 de CC-1001 para CC-1002 liquidada com sucesso!
--------------------------------------------------------------------------------
   Conta: CC-1001 | Titular: Alice Santos    | Saldo: R$   3500.00
   Conta: CC-1002 | Titular: Bruno Rocha     | Saldo: R$   2700.00
--------------------------------------------------------------------------------
--- 2. Tentativa com Saldo Insuficiente (R$ 8.000,00) ---
   🚨 [ROLLBACK] Transação desfeita com integridade preservada: Saldo insuficiente: Saldo R$ 3500.00, Saque R$ 8000.00
--------------------------------------------------------------------------------
   Conta: CC-1001 | Titular: Alice Santos    | Saldo: R$   3500.00
   Conta: CC-1002 | Titular: Bruno Rocha     | Saldo: R$   2700.00
--------------------------------------------------------------------------------
--- 3. Tentativa Excedendo Limite Operacional (R$ 15.000,00) ---
   🚨 [ROLLBACK] Transação desfeita com integridade preservada: Valor excede o limite diário de transferência sem validação prévia!
--------------------------------------------------------------------------------
   Conta: CC-1001 | Titular: Alice Santos    | Saldo: R$   3500.00
   Conta: CC-1002 | Titular: Bruno Rocha     | Saldo: R$   2700.00
--------------------------------------------------------------------------------
================================================================================