Capítulo 17: Acesso a Dados com JDBC, Transações ACID, PreparedStatement e DAO Pattern
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:
- 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). - 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:
- Modelo de Domínio: Entidade pura sem amarras tecnológicas (ex:
ClienteouContaCorrente). - Interface DAO: Contrato limpo com operações CRUD (
salvar,buscarPorId,listarTodos,remover). - Implementação JDBC DAO: Código que instancia
PreparedStatement, itera sobreResultSete fecha recursos viatry-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("================================================================================");
}
}