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().
-- 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 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 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
// ── 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 │ 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
-- 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.
ServiceLoader em Java 6+ — Class.forName() é código legado desnecessário.PreparedStatement para queries com parâmetros — nunca concatenar strings SQL com input do utilizador.try-with-resources para garantir que Connection, PreparedStatement e ResultSet são sempre fechados, mesmo em caso de excepção.conn.setAutoCommit(false) para transacções explícitas com commit()/rollback() manual.rs.wasNull() após ler primitivos (getInt, getLong) — devolvem 0 quando a coluna é NULL.