SQL JDBC ODBC
Acesso a Dados

ODBC vs JDBC — O Conceito de Driver

Uma aplicação não fala directamente com PostgreSQL, MySQL ou Oracle — fala com uma interface abstracta, e é o driver que traduz essa interface para o protocolo de rede específico de cada SGBD. ODBC (Open Database Connectivity) é essa interface em C, universal e independente de linguagem. JDBC (Java Database Connectivity) é a mesma ideia reimplementada em Java, com uma API orientada a objectos que acompanha o ecossistema JVM. Compreender o papel do driver é compreender por que razão a mesma query SQL funciona com PostgreSQL e Oracle sem alterar uma linha de código da aplicação — e o que acontece internamente quando se chama connection.prepareStatement().

O Problema que ODBC e JDBC Resolvem

-- Sem uma camada de abstracção, cada aplicação teria de falar directamente
-- com o protocolo proprietário de cada SGBD:
--
-- Aplicação ──→ libpq (protocolo PostgreSQL)    ← código específico de PG
-- Aplicação ──→ MySQL C API                     ← código específico de MySQL
-- Aplicação ──→ Oracle OCI                      ← código específico de Oracle
--
-- Problema: mudar de SGBD implica reescrever toda a camada de acesso a dados.
--
-- Com ODBC / JDBC:
--
-- Aplicação ──→ ODBC/JDBC API (interface única)
--                    ↓
--              Driver Manager
--            /       |        \
--    PG Driver   MySQL Driver   Oracle Driver
--        ↓            ↓              ↓
--    PostgreSQL     MySQL          Oracle
--
-- A aplicação conhece apenas a API standard.
-- O driver traduz para o protocolo nativo de cada SGBD.
-- Trocar de SGBD = trocar o driver + ajustar a connection string.

-- Exemplo prático: a mesma query em Java funciona com qualquer SGBD
// Sem alterar este código:
Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement("SELECT id, nome FROM produto WHERE activo = ?");
ps.setBoolean(1, true);
ResultSet rs = ps.executeQuery();
// Mudar de PostgreSQL para MySQL: apenas url muda.
// Todo o código acima permanece igual.

ODBC — Open Database Connectivity

-- ODBC foi criado pela Microsoft em 1992, adoptado como standard pela indústria.
-- É uma API em C — funciona com qualquer linguagem que consiga chamar C:
-- Python (pyodbc), PHP, R, Excel, PowerBI, aplicações C/C++.
--
-- Arquitectura ODBC:
--
--  Aplicação (C, Python, R...)
--       ↓  chama funções ODBC (SQLConnect, SQLExecDirect, SQLFetch...)
--  Driver Manager (odbcinst / unixODBC no Linux; ODBC Administrator no Windows)
--       ↓  carrega o driver correcto baseado no DSN
--  Driver ODBC (.dll no Windows / .so no Linux)
--       ↓  implementa a API ODBC em termos do protocolo nativo
--  SGBD (PostgreSQL, SQL Server, Oracle...)
--
-- DSN — Data Source Name: nome simbólico que mapeia para uma connection string
-- Configurado no sistema operativo, não no código da aplicação.

-- /etc/odbc.ini (Linux):
-- [MinhaBD]
-- Driver   = PostgreSQL Unicode
-- Server   = db.example.com
-- Port     = 5432
-- Database = loja
-- SSLMode  = require

-- /etc/odbcinst.ini (regista os drivers disponíveis no sistema):
-- [PostgreSQL Unicode]
-- Description = PostgreSQL ODBC driver (Unicode version)
-- Driver      = /usr/lib/x86_64-linux-gnu/odbc/psqlodbcw.so
-- Setup       = /usr/lib/x86_64-linux-gnu/odbc/libodbcpsqlS.so

-- Conectar via pyodbc (Python):
import pyodbc
conn = pyodbc.connect('DSN=MinhaBD;UID=app_user;PWD=password')
cursor = conn.cursor()
cursor.execute("SELECT id, nome FROM produto WHERE activo = ?", True)
for row in cursor.fetchall():
    print(row.id, row.nome)
conn.close()

-- Funções ODBC de baixo nível (C — o que o driver manager expõe):
-- SQLAllocHandle()    → alocar handle de conexão/statement
-- SQLConnect()        → estabelecer ligação
-- SQLPrepare()        → preparar statement
-- SQLBindParameter()  → ligar parâmetro (evita SQL injection)
-- SQLExecute()        → executar
-- SQLFetch()          → avançar no cursor
-- SQLGetData()        → ler valor de uma coluna
-- SQLDisconnect()     → fechar ligação

JDBC — Java Database Connectivity

-- JDBC foi criado pela Sun Microsystems em 1997, parte do Java SE desde o início.
-- É uma API Java — interfaces no pacote java.sql e javax.sql.
-- Os drivers são JARs que implementam essas interfaces.
--
-- Arquitectura JDBC:
--
--  Aplicação Java
--       ↓  usa java.sql.Connection, PreparedStatement, ResultSet...
--  java.sql.DriverManager  (ou javax.sql.DataSource via connection pool)
--       ↓  encontra o driver registado para o URL (ex: jdbc:postgresql://...)
--  Driver JDBC (JAR: postgresql-42.x.x.jar, mysql-connector-j-8.x.x.jar...)
--       ↓  implementa java.sql.Driver, Connection, PreparedStatement, etc.
--  SGBD
--
-- Os quatro tipos de driver JDBC (histórico → tipo 4 é o actual):
--
-- Tipo 1 — JDBC-ODBC Bridge: obsoleto, wrappava ODBC. Removido no Java 8.
-- Tipo 2 — Native API: usava bibliotecas nativas C do SGBD. Requer instalação local.
-- Tipo 3 — Network Protocol: usava um servidor intermediário. Complexidade desnecessária.
-- Tipo 4 — Pure Java (thin driver): protocolo nativo implementado em Java puro.
--           Sem dependências nativas. É o tipo de TODOS os drivers modernos.
--           postgresql-42.x.x.jar fala o protocolo de rede do PostgreSQL em Java puro.


-- ── Connection URL (JDBC) ────────────────────────────────────────────────────
-- Formato: jdbc:{subprotocolo}://{host}:{porta}/{base_de_dados}?{parâmetros}

String pgUrl    = "jdbc:postgresql://db.example.com:5432/loja?sslmode=require¤tSchema=public";
String mysqlUrl = "jdbc:mysql://db.example.com:3306/loja?useSSL=true&serverTimezone=UTC";
String h2Url    = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";  // in-memory para testes

-- O subprotocolo (postgresql, mysql, h2) identifica qual driver carregar.
-- O DriverManager procura no classpath um driver registado para esse subprotocolo.


-- ── DriverManager — como o driver é descoberto ───────────────────────────────
// Forma moderna (Java 6+ / ServiceLoader — automático):
// O JAR do driver contém META-INF/services/java.sql.Driver com o nome da classe.
// O DriverManager encontra e regista automaticamente ao fazer getConnection().

Connection conn = DriverManager.getConnection(
    "jdbc:postgresql://localhost:5432/loja",
    "app_user",
    "password"
);
// Internamente:
// 1. DriverManager itera os drivers registados via ServiceLoader
// 2. Chama driver.acceptsURL("jdbc:postgresql://...") em cada um
// 3. O driver PostgreSQL responde true
// 4. Chama driver.connect(url, properties) → devolve a Connection

// Forma antiga (Java 5 e anterior — ainda vista em código legado):
Class.forName("org.postgresql.Driver");  // carrega e regista o driver manualmente
Connection conn = DriverManager.getConnection(url, user, password);
// Class.forName() é desnecessário em Java 6+ com ServiceLoader

A API JDBC em Detalhe

// ── Connection ────────────────────────────────────────────────────────────────
// Representa uma ligação física (ou lógica, via pool) ao SGBD.
// É thread-unsafe — não partilhar entre threads. Uma connection por thread/request.

Connection conn = DriverManager.getConnection(url, user, password);

conn.setAutoCommit(false);          // desactivar auto-commit para transacções explícitas
conn.setTransactionIsolation(
    Connection.TRANSACTION_REPEATABLE_READ
);                                  // definir isolation level
conn.close();                       // devolver ao pool (ou fechar a conexão física)

// Verificar se a conexão ainda está viva:
boolean alive = conn.isValid(5);    // timeout em segundos


// ── Statement vs PreparedStatement vs CallableStatement ──────────────────────

// Statement — query dinâmica sem parâmetros (NUNCA usar com input do utilizador):
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM produto");
// ⚠️ Vulnerável a SQL Injection se construído com concatenação de strings

// PreparedStatement — query parametrizada (SEMPRE usar para queries com input):
PreparedStatement ps = conn.prepareStatement(
    "SELECT id, nome, preco_base FROM produto WHERE categoria_id = ? AND activo = ?"
);
ps.setInt(1, categoriaId);          // ? index começa em 1
ps.setBoolean(2, true);
ResultSet rs = ps.executeQuery();

// PreparedStatement para INSERT com RETURNING:
PreparedStatement insert = conn.prepareStatement(
    "INSERT INTO utilizador (email, nome, password_hash) VALUES (?, ?, ?) RETURNING id",
    Statement.RETURN_GENERATED_KEYS   // ou usar RETURNING directamente
);
insert.setString(1, email);
insert.setString(2, nome);
insert.setString(3, passwordHash);
insert.executeUpdate();
ResultSet keys = insert.getGeneratedKeys();
if (keys.next()) {
    long novoId = keys.getLong(1);
}

// CallableStatement — stored procedures / functions:
CallableStatement cs = conn.prepareCall("{ call transferir(?, ?, ?) }");
// PostgreSQL: SELECT * FROM transferir(?, ?, ?)
CallableStatement cs = conn.prepareCall("SELECT transferir(?, ?, ?)");
cs.setInt(1, contaOrigem);
cs.setInt(2, contaDestino);
cs.setBigDecimal(3, valor);
cs.execute();


// ── ResultSet — navegar os resultados ─────────────────────────────────────────
PreparedStatement ps = conn.prepareStatement(
    "SELECT id, email, criado_em FROM utilizador WHERE activo = ? ORDER BY criado_em DESC"
);
ps.setBoolean(1, true);
ResultSet rs = ps.executeQuery();

while (rs.next()) {                         // avança para a próxima linha; false quando acabar
    long id           = rs.getLong("id");
    String email      = rs.getString("email");
    Timestamp criadoEm = rs.getTimestamp("criado_em");
    // Usar nomes de coluna em vez de índices — mais legível e resiliente a alterações
}

// Verificar NULL antes de ler primitivos:
int quantidade = rs.getInt("quantidade");
if (rs.wasNull()) {
    // a coluna era NULL — getInt() devolveu 0 por default
}

// Fechar recursos na ordem correcta (ou usar try-with-resources):
rs.close();
ps.close();
// conn.close() — devolver ao pool


// ── try-with-resources (Java 7+) — o padrão correcto ─────────────────────────
String sql = "SELECT id, nome FROM produto WHERE categoria_id = ? AND activo = ?";

try (
    Connection conn  = dataSource.getConnection();           // obtém do pool
    PreparedStatement ps = conn.prepareStatement(sql)
) {
    ps.setInt(1, categoriaId);
    ps.setBoolean(2, true);

    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            produtos.add(new Produto(rs.getLong("id"), rs.getString("nome")));
        }
    }
    // rs fechado automaticamente
}
// ps e conn fechados automaticamente — conn devolvida ao pool mesmo em caso de excepção


// ── Transacções em JDBC ───────────────────────────────────────────────────────
try (Connection conn = dataSource.getConnection()) {
    conn.setAutoCommit(false);
    try {
        PreparedStatement debito = conn.prepareStatement(
            "UPDATE conta SET saldo = saldo - ? WHERE id = ?"
        );
        debito.setBigDecimal(1, valor);
        debito.setInt(2, contaOrigem);
        debito.executeUpdate();

        PreparedStatement credito = conn.prepareStatement(
            "UPDATE conta SET saldo = saldo + ? WHERE id = ?"
        );
        credito.setBigDecimal(1, valor);
        credito.setInt(2, contaDestino);
        credito.executeUpdate();

        conn.commit();
    } catch (SQLException e) {
        conn.rollback();
        throw e;
    }
}

ODBC vs JDBC — Diferenças Práticas

-- ┌─────────────────────┬──────────────────────────────┬────────────────────────────────┐
-- │                     │ ODBC                         │ JDBC                           │
-- ├─────────────────────┼──────────────────────────────┼────────────────────────────────┤
-- │ Linguagem base      │ C (API nativa do SO)         │ Java (API da JVM)              │
-- │ Portabilidade       │ Qualquer linguagem via FFI   │ Apenas JVM (Java, Kotlin, Scala)│
-- │ Driver format       │ .dll / .so (nativo)          │ .jar (bytecode JVM)            │
-- │ Instalação driver   │ No sistema operativo         │ No classpath da aplicação      │
-- │ Config connection   │ DSN (odbc.ini do SO)         │ URL no código / properties     │
-- │ Connection pool     │ Pool no Driver Manager       │ DataSource (HikariCP, etc.)    │
-- │ Descoberta driver   │ Driver Manager do SO         │ ServiceLoader / DriverManager  │
-- │ Parametrização      │ SQLBindParameter()           │ PreparedStatement.setXxx()     │
-- │ Tipo safety         │ Fraca (void* / SQLPOINTER)   │ Forte (setString, setInt...)   │
-- │ Uso típico          │ ETL, BI, Excel, R, Python    │ Aplicações Java/Spring/JPA     │
-- │ Performance raw     │ Ligeiramente mais rápido     │ Overhead JVM negligível        │
-- └─────────────────────┴──────────────────────────────┴────────────────────────────────┘

-- Quando usar ODBC:
--   - Ferramentas de BI (Power BI, Tableau, Excel) que falam ODBC nativamente
--   - Scripts Python/R de análise de dados
--   - Aplicações C/C++ legacy
--   - Ambientes onde a JVM não está disponível

-- Quando usar JDBC:
--   - Qualquer aplicação Java, Kotlin ou Scala
--   - Spring Boot (usa JDBC internamente, mesmo com JPA/Hibernate)
--   - Android (SQLite usa uma API semelhante)
--   - Ferramentas JVM: Flyway, Liquibase, jOOQ, MyBatis, Hibernate

Acima do JDBC — As Camadas de Abstracção

-- JDBC é a base. Na prática, raramente se usa JDBC directamente em aplicações
-- modernas — existem camadas de abstracção que simplificam o código:
--
-- Camada         │ Exemplos              │ O que abstrai
-- ───────────────┼───────────────────────┼────────────────────────────────────
-- JDBC puro      │ java.sql.*            │ Nada — API de baixo nível
-- SQL mapper     │ jOOQ, MyBatis         │ Gera SQL type-safe ou mapeia queries
-- ORM            │ Hibernate, EclipseLink│ Mapeia objectos Java ↔ tabelas SQL
-- Spring Data    │ JdbcTemplate,         │ Elimina boilerplate de JDBC
--                │ Spring Data JPA       │ Repositórios automáticos
--
-- Exemplo: a mesma query em cada camada

// ── JDBC puro ─────────────────────────────────────────────────────────────────
try (Connection conn = ds.getConnection();
     PreparedStatement ps = conn.prepareStatement(
         "SELECT id, nome FROM produto WHERE categoria_id = ?")) {
    ps.setInt(1, catId);
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) result.add(new Produto(rs.getLong(1), rs.getString(2)));
    }
}

// ── Spring JdbcTemplate ───────────────────────────────────────────────────────
List produtos = jdbcTemplate.query(
    "SELECT id, nome FROM produto WHERE categoria_id = ?",
    (rs, rowNum) -> new Produto(rs.getLong("id"), rs.getString("nome")),
    catId
);
// Sem try-with-resources, sem gestão de Connection/ResultSet

// ── jOOQ (SQL type-safe) ──────────────────────────────────────────────────────
List produtos = dsl
    .selectFrom(PRODUTO)
    .where(PRODUTO.CATEGORIA_ID.eq(catId))
    .fetchInto(Produto.class);
// SQL gerado em Java — erros de schema detectados em tempo de compilação

// ── Spring Data JPA / Hibernate ───────────────────────────────────────────────
List produtos = produtoRepository.findByCategoriaId(catId);
// Zero SQL escrito — o repositório gera a query a partir do nome do método
// Hibernate traduz para JDBC internamente

// Todas estas camadas usam JDBC por baixo.
// Compreender JDBC é compreender o que acontece quando qualquer uma delas executa.

Checklist ODBC / JDBC