Capítulo 16: Acesso a Dados Relacionais com SQLite3 Nativo, Transações ACID e DAO
Especialização em Sistemas com Python & POO com Type Hints • Capítulo 16
🗺️ Mapa Conceitual do Tópico
flowchart TD
subgraph ENGINE["Motor Relacional SQLite3 Integrado"]
ACID["Garantias Transacionais ACID\nAtomicidade · Consistência · Isolamento · Durabilidade"]
PRAGMA["Pragmas Mandatórios de Engenharia\nPRAGMA foreign_keys = ON · PRAGMA journal_mode = WAL"]
ROW["conn.row_factory = sqlite3.Row\nAcesso O(1) Indexado por Nome de Coluna"]
end
subgraph SECURITY["Segurança e Parametrizacão de Queries"]
PLACEHOLDER["Consultas Parametrizadas (?, :param)\nImunidade Completa contra SQL Injection (OWASP Top 1)"]
ANTI_PATTERN["Anti-pattern: F-Strings em SQL (Vulnerabilidade Crítica)"]
end
subgraph PATTERNS["Padrões de Persistência (DAO & Repository)"]
ENT["Entidade de Domínio Pura (Dataclass)"]
DAO["Data Access Object (DAO)\nExecuta DDL/DML · Mapeia Tuplas para Entidades"]
TRANS["with conn: Bloco Atômico com Commit / Rollback Automático"]
end
ENGINE --> SECURITY
SECURITY --> PATTERNS
PATTERNS --> PROD["Aplicações Desktop, Dispositivos Embarcados e Testes Ágeis"]
style ENGINE fill:#ede7f6,stroke:#512da8,stroke-width:2px
style SECURITY fill:#ffebee,stroke:#f44336,stroke-width:2px
style PATTERNS fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
💾 1. O Banco Relacional SQLite3 no Ecossistema Python
Diferente de SGBDs cliente-servidor como PostgreSQL ou MySQL, o SQLite3 é um banco de dados relacional embarcado (in-process), que reside diretamente no espaço de memória da aplicação e persiste seus dados em um único arquivo de disco multiplataforma (ou puramente em memória via :memory:).
Pragmas Mandatórios de Engenharia
Por questões de compatibilidade retroativa com sistemas legados da década de 1990, o SQLite vem por padrão com certos recursos críticos desativados:
- Ativação de Chaves Estrangeiras: O suporte a
FOREIGN KEYvem desabilitado por padrão! Deve ser ativado em toda conexão recém-aberta:PRAGMA foreign_keys = ON; - Modo WAL (Write-Ahead Logging): Em servidores de aplicação com concorrência, o modo padrão de journal bloqueia o banco inteiro durante escritas. O modo WAL permite múltiplos leitores simultâneos enquanto um escritor grava no disco:
PRAGMA journal_mode = WAL;
🔒 2. Prevenção de SQL Injection e Consultas Parametrizadas
A regra de ouro inegociável da engenharia de banco de dados é: nunca interpole variáveis diretamente em strings SQL via f-strings ou operador %:
# VULNERABILIDADE GRAVE DE SEGURANÇA (SQL Injection / OWASP A03):
# Se usuario_input for "' OR '1'='1", todo o banco é exposto!
cursor.execute(f"SELECT * FROM usuarios WHERE email = '{usuario_input}'")
# PADRÃO OURO DE ENGENHARIA (Placeholders Parametrizados):
# O driver envia a consulta compilada e os parâmetros em canais separados
cursor.execute("SELECT * FROM usuarios WHERE email = ?", (usuario_input,))
cursor.execute("SELECT * FROM usuarios WHERE email = :email", {"email": usuario_input})
⚡ 3. Gestão Transacional ACID e sqlite3.Row
Gerenciador de Contexto de Transações (with conn:)
A classe sqlite3.Connection atua nativamente como gerenciador de contexto transacional:
- Se o bloco
with conn:terminar sem erros, o driver emite umCOMMITautomático no banco. - Se qualquer exceção for lançada dentro do bloco, o driver emite um
ROLLBACKinstantâneo, revertendo todas as alterações para o estado consistente anterior.
Acesso Dinâmico com conn.row_factory = sqlite3.Row
Por padrão, cursor.fetchall() retorna tuplas brutas sem nome (('João', 25)). Configurar conn.row_factory = sqlite3.Row transforma os retornos em mapeamentos otimizados de alta performance:
conn.row_factory = sqlite3.Row
cursor = conn.execute("SELECT id, nome, saldo FROM contas WHERE id = ?", (1,))
linha = cursor.fetchone()
# Acesso duplo transparente: por chave textual ou por índice numérico
print(linha["nome"], linha["saldo"])
print(linha[0], linha[1])
🏛️ 4. O Padrão Data Access Object (DAO / Repository)
O padrão DAO isola todas as instruções SQL (DDL, DML) dentro de uma classe especializada, mantendo as entidades de domínio desacopladas de dialetos de persistência:
from dataclasses import dataclass
from decimal import Decimal
import sqlite3
from typing import Self
@dataclass(slots=True)
class Produto:
id: int | None
sku: str
nome: str
preco: Decimal
estoque: int
class ProdutoDAO:
def __init__(self, conexao: sqlite3.Connection) -> None:
self._conn: sqlite3.Connection = conexao
def salvar(self, produto: Produto) -> int:
query = """
INSERT INTO produtos (sku, nome, preco, estoque)
VALUES (?, ?, ?, ?)
ON CONFLICT(sku) DO UPDATE SET
preco = excluded.preco,
estoque = excluded.estoque;
"""
with self._conn:
cursor = self._conn.execute(
query, (produto.sku, produto.nome, str(produto.preco), produto.estoque)
)
return cursor.lastrowid or 0
Essa separação viabiliza testes unitários ultrarrápidos utilizando bancos SQLite em memória (:memory:), simulando com precisão o comportamento relacional sem dependências externas.