Especialização em Sistemas com Java 21 LTS • Módulo de Banco de Dados e Projetos Integradores


🗺️ Mapa Conceitual do Tópico

flowchart TD
    subgraph ARQ_JDBC["Arquitetura JDBC & Conexões"]
        A["Aplicação Java"] --> B["DataSource (Pool HikariCP)"]
        B --> C["Driver JDBC (PostgreSQL / MySQL / H2)"]
        C --> D["Banco de Dados Relacional (SGBD)"]
    end

    subgraph SEGURANCA["Prevenção de SQL Injection"]
        E["Statement: Concatenação Insegura (Vulnerável)"]
        F["PreparedStatement: Pré-compilação com Placeholders (?)"]
        E -.->|Substituir por| F
    end

    subgraph TRANSACAO["Controle Transacional ACID"]
        G["conn.setAutoCommit(false)"]
        H["Operação Débito + Operação Crédito"]
        I{"Sucesso Total?"}
        I -->|Sim| J["conn.commit()"]
        I -->|Erro| K["conn.rollback()"]
        G --> H --> I
    end

    ARQ_JDBC --> SEGURANCA
    SEGURANCA --> TRANSACAO

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style E fill:#ffebee,stroke:#f44336,stroke-width:2px
    style J fill:#c8e6c9,stroke:#388e3c,stroke-width:2px
    style K fill:#ffcdd2,stroke:#d32f2f,stroke-width:2px

🗄️ 1. Arquitetura do Java Database Connectivity (JDBC)

O JDBC (Java Database Connectivity) é a API padrão da especificação Java SE (pacotes java.sql e javax.sql) que fornece uma camada de abstração independente de fornecedor para comunicação com Sistemas de Gerenciamento de Bancos de Dados Relacionais (SGBDs).

Principais Componentes da Arquitetura:

  • java.sql.Driver: A implementação do protocolo de rede fornecida por cada fabricante de banco (PostgreSQL, MySQL, Oracle, SQL Server, H2).
  • java.sql.Connection: Representa a sessão ativa de comunicação estabelecida com o banco de dados.
  • Pools de Conexão (DataSource / HikariCP): Abrir uma conexão física com o banco de dados é uma das operações mais lentas da computação corporativa (handshake TCP, negociação TLS, alocação de processo no servidor consomem de 30 a 100 ms). Connection pools mantêm dezenas de conexões abertas e aquecidas, emprestando-as para a aplicação com overhead inferior a 1 ms.

🛡️ 2. Prevenção de SQL Injection: PreparedStatement

A vulnerabilidade de SQL Injection ocorre quando parâmetros fornecidos por usuários são concatenados diretamente em strings de comandos SQL:

// CÓDIGO INSEGURO E VULNERÁVEL A INJEÇÃO SQL:
String sql = "SELECT * FROM usuarios WHERE login = '" + login + "' AND senha = '" + senha + "'";
// Se o invasor passar login: admin' -- o login é burlado sem senha!

Por Que o PreparedStatement é Imune a SQL Injection:

  1. Pré-Compilação: O comando SQL com marcadores posicionais ? é enviado ao SGBD antes de qualquer parâmetro. O banco analisa a sintaxe, otimiza e fixa a árvore do plano de execução (query execution plan).
  2. Separação Rígida: Os parâmetros transmitidos posteriormente via métodos tipados (ps.setString(1, login)) são tratados estritamente como dados literais, impossibilitando qualquer alteração da gramática do comando SQL previamente compilado.

⚡ 3. Gestão Transacional ACID (commit e rollback)

Uma transação financeira deve cumprir rigorosamente as quatro propriedades ACID:

  • Atomicidade: Todas as operações do bloco são efetivadas com sucesso, ou nenhuma delas é persistida.
  • Consistência: O banco transita de um estado válido e íntegro para outro.
  • Isolamento: Transações simultâneas não interferem no estado parcial umas das outras.
  • Durabilidade: Uma vez confirmada a transação, seus dados sobrevivem a quedas de energia.

Controle Manual de Transações em JDBC:

Por padrão, toda conexão JDBC opera em modo autoCommit = true (cada comando SQL individual é confirmado imediatamente). Para operações atômicas complexas (como transferência bancária entre duas contas):

try (Connection conn = dataSource.getConnection()) {
    conn.setAutoCommit(false); // Inicia fronteira transacional

    debitarConta(conn, contaOrigem, valor);
    creditarConta(conn, contaDestino, valor);

    conn.commit(); // Confirma ambas as operações atomicamente
} catch (SQLException e) {
    conn.rollback(); // Reverte o estado original caso ocorra qualquer falha
    throw e;
}

🏛️ 4. O Padrão Data Access Object (DAO Pattern)

O padrão DAO isola completamente as regras de negócio das queries SQL e especificidades de persistência de dados.

Estrutura Arquitetural:

  1. Modelo de Domínio: Entidade pura sem amarras tecnológicas (ex: Cliente ou ContaCorrente).
  2. Interface DAO: Contrato limpo com operações CRUD (salvar, buscarPorId, listarTodos, remover).
  3. Implementação JDBC DAO: Código que instancia PreparedStatement, itera sobre ResultSet e fecha recursos via try-with-resources.

📊 5. Matriz Comparativa: Paradigmas de Persistência em Java

Critério JDBC Puro Spring Data JPA / Hibernate jOOQ (Type-safe SQL)
Curva de Aprendizado Baixa (SQL puro) Alta (Mapeamento ORM, cache N+1) Média
Performance e Controle Máxima (Controle absoluto da query) Média (Overhead de proxies e dirty checking) Alta
Produtividade em CRUDs Baixa (muito código de mapeamento) Altíssima (Geração dinâmica de queries) Alta
Ideal Para Microserviços ultra-leves, relatórios complexos Aplicações corporativas com regras ricas de domínio Queries complexas e type-safe

💻 6. Código de Demonstração Corporativo (Java 21 LTS)

package com.empresa.persistencia;

import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Padrão DAO com PreparedStatement, tratamento de ResultSet e transações manuais.
 */
public class GestorPersistenciaJdbc {

    public record Fornecedor(Long id, String razaoSocial, String cnpj, BigDecimal limiteCredito) {}

    public interface FornecedorDao {
        void salvar(Fornecedor f, Connection conn) throws SQLException;
        Optional<Fornecedor> buscarPorCnpj(String cnpj, Connection conn) throws SQLException;
    }

    public static class FornecedorDaoJdbc implements FornecedorDao {
        @Override
        public void salvar(Fornecedor f, Connection conn) throws SQLException {
            String sql = "INSERT INTO fornecedores (razao_social, cnpj, limite_credito) VALUES (?, ?, ?)";
            try (PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
                ps.setString(1, f.razaoSocial());
                ps.setString(2, f.cnpj());
                ps.setBigDecimal(3, f.limiteCredito());
                ps.executeUpdate();
            }
        }

        @Override
        public Optional<Fornecedor> buscarPorCnpj(String cnpj, Connection conn) throws SQLException {
            String sql = "SELECT id, razao_social, cnpj, limite_credito FROM fornecedores WHERE cnpj = ?";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                ps.setString(1, cnpj);
                try (ResultSet rs = ps.executeQuery()) {
                    if (rs.next()) {
                        return Optional.of(new Fornecedor(
                                rs.getLong("id"),
                                rs.getString("razao_social"),
                                rs.getString("cnpj"),
                                rs.getBigDecimal("limite_credito")
                        ));
                    }
                }
            }
            return Optional.empty();
        }
    }

    public static void main(String[] args) {
        System.out.println("================================================================================");
        System.out.println("           ARQUITETURA JDBC ROBUSTA: DAO E TRANSAÇÕES ACID (JAVA 21)            ");
        System.out.println("================================================================================");

        // Conexão simulada com banco em memória SQLite ou H2
        String urlConexao = "jdbc:h2:mem:lojadb;DB_CLOSE_DELAY=-1";

        try (Connection conn = DriverManager.getConnection(urlConexao, "sa", "")) {
            // Criação da tabela DDL
            try (Statement st = conn.createStatement()) {
                st.execute("""
                    CREATE TABLE fornecedores (
                        id BIGINT AUTO_INCREMENT PRIMARY KEY,
                        razao_social VARCHAR(100) NOT NULL,
                        cnpj VARCHAR(20) UNIQUE NOT NULL,
                        limite_credito DECIMAL(15,2) NOT NULL
                    )
                """);
            }
            System.out.println("✅ Tabela 'fornecedores' provisionada em memória.");

            // Bloco Transacional ACID com DAO
            conn.setAutoCommit(false);
            FornecedorDao dao = new FornecedorDaoJdbc();

            try {
                dao.salvar(new Fornecedor(null, "Logística Global S/A", "11.222.333/0001-44", new BigDecimal("75000.00")), conn);
                dao.salvar(new Fornecedor(null, "Componentes Eletrônicos", "55.666.777/0001-88", new BigDecimal("120000.00")), conn);

                conn.commit(); // Persistência atômica confirmada
                System.out.println("✅ Transação de inserção consolidada com COMMIT.");
            } catch (SQLException ex) {
                conn.rollback(); // Reversão automática em caso de erro
                System.err.println("🚨 Transação revertida com ROLLBACK: " + ex.getMessage());
            }

            // Consulta com PreparedStatement
            Optional<Fornecedor> encontrado = dao.buscarPorCnpj("11.222.333/0001-44", conn);
            encontrado.ifPresent(f -> 
                System.out.printf("🔍 Fornecedor Localizado: %s (CNPJ: %s) | Limite: R$ %,.2f%n",
                        f.razaoSocial(), f.cnpj(), f.limiteCredito()));

        } catch (SQLException e) {
            System.err.println("Falha no driver JDBC: " + e.getMessage());
        }

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