💻 Exemplos: Capítulo 16: Acesso a Dados Relacionais com SQLite3 Nativo, Transações ACID e DAO
Especialização em Sistemas com Python 3.12+ • Capítulo 16
🎯 Demonstração Prática: Transações ACID e Padrão DAO com SQLite3
O exemplo abaixo implementa uma camada completa de persistência relacional utilizando o módulo nativo sqlite3:
- Configuração de pragmas mandatórios (
PRAGMA foreign_keys = ONesqlite3.Row). - Implementação do padrão Data Access Object (DAO) com queries 100% parametrizadas.
- Transferência monetária transacional atômica demonstrando garantias ACID (
with conn:gerenciando commit e rollback em falhas de saldo).
"""
demonstracao_sqlite_e_dao.py
Camada DAO Relacional com SQLite3 Nativo, Pragmas de Engenharia e Transações ACID.
Compatível com Python 3.12+.
"""
from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_EVEN
import sqlite3
from typing import Final
# ==============================================================================
# 1. MODELOS DE DOMÍNIO
# ==============================================================================
@dataclass(slots=True)
class ContaCorrente:
id_conta: int | None
titular: str
saldo: Decimal
bloqueada: bool = False
# ==============================================================================
# 2. DATA ACCESS OBJECT (DAO) COM PRAGMAS E TRANSAÇÕES
# ==============================================================================
class ContaCorrenteDAO:
"""DAO responsável pelo isolamento de comandos SQL e mapeamento de registros."""
def __init__(self, conexao: sqlite3.Connection) -> None:
self._conn: sqlite3.Connection = conexao
self._configurar_ambiente_db()
def _configurar_ambiente_db(self) -> None:
"""Habilita integridade referencial e row_factory dinâmico."""
self._conn.row_factory = sqlite3.Row
self._conn.execute("PRAGMA foreign_keys = ON;")
self._conn.execute("PRAGMA journal_mode = WAL;")
with self._conn:
self._conn.execute("""
CREATE TABLE IF NOT EXISTS contas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titular TEXT NOT NULL,
saldo TEXT NOT NULL,
bloqueada INTEGER DEFAULT 0 CHECK (bloqueada IN (0, 1))
);
""")
def criar_conta(self, titular: str, saldo_inicial: Decimal) -> ContaCorrente:
query = "INSERT INTO contas (titular, saldo, bloqueada) VALUES (?, ?, 0);"
with self._conn:
cursor = self._conn.execute(query, (titular, str(saldo_inicial)))
novo_id = cursor.lastrowid or 0
return ContaCorrente(id_conta=novo_id, titular=titular, saldo=saldo_inicial)
def buscar_por_id(self, id_conta: int) -> ContaCorrente | None:
cursor = self._conn.execute("SELECT * FROM contas WHERE id = ?;", (id_conta,))
linha = cursor.fetchone()
if not linha:
return None
return ContaCorrente(
id_conta=linha["id"],
titular=linha["titular"],
saldo=Decimal(linha["saldo"]),
bloqueada=bool(linha["bloqueada"])
)
def transferir_fundos_atomico(
self,
id_origem: int,
id_destino: int,
valor: Decimal
) -> bool:
"""
Executa transferência atômica com garantias ACID completas.
Se qualquer débito ou crédito falhar, o bloco emite ROLLBACK incondicional.
"""
if valor <= Decimal("0.00"):
raise ValueError(f"Valor de transferência inválido: {valor}")
# Bloco transacional com commit ou rollback automático
with self._conn:
# 1. Validação da conta de origem
cursor_origem = self._conn.execute("SELECT saldo, bloqueada FROM contas WHERE id = ?;", (id_origem,))
row_origem = cursor_origem.fetchone()
if not row_origem or bool(row_origem["bloqueada"]):
raise RuntimeError("Conta de origem inexistente ou bloqueada.")
saldo_origem = Decimal(row_origem["saldo"])
if saldo_origem < valor:
raise ValueError(f"Saldo insuficiente na conta {id_origem}: R$ {saldo_origem:,.2f}")
# 2. Validação da conta de destino
cursor_dest = self._conn.execute("SELECT saldo, bloqueada FROM contas WHERE id = ?;", (id_destino,))
row_dest = cursor_dest.fetchone()
if not row_dest or bool(row_dest["bloqueada"]):
raise RuntimeError("Conta de destino inexistente ou bloqueada.")
saldo_dest = Decimal(row_dest["saldo"])
# 3. Operações de Débito e Crédito atômicas
novo_saldo_origem = (saldo_origem - valor).quantize(Decimal("0.01"), ROUND_HALF_EVEN)
novo_saldo_dest = (saldo_dest + valor).quantize(Decimal("0.01"), ROUND_HALF_EVEN)
self._conn.execute("UPDATE contas SET saldo = ? WHERE id = ?;", (str(novo_saldo_origem), id_origem))
self._conn.execute("UPDATE contas SET saldo = ? WHERE id = ?;", (str(novo_saldo_dest), id_destino))
return True
# ==============================================================================
# 3. EXECUÇÃO DEMONSTRATIVA EM BANCO IN-MEMORY
# ==============================================================================
if __name__ == "__main__":
print("=" * 80)
print(" PERSISTÊNCIA RELACIONAL E TRANSAÇÕES ACID COM SQLITE3 NATIVO")
print("=" * 80)
# Conexão em memória isolada para validação ágil
conexao_banco = sqlite3.connect(":memory:")
dao = ContaCorrenteDAO(conexao_banco)
# 1. Cadastro inicial de contas
c1 = dao.criar_conta("Mariana Albuquerque", Decimal("2500.00"))
c2 = dao.criar_conta("Roberto Mendonça", Decimal("350.00"))
print(f"Conta Origem (#1) : {c1.titular} | Saldo: R$ {c1.saldo:>10.2f}")
print(f"Conta Destino (#2): {c2.titular} | Saldo: R$ {c2.saldo:>10.2f}")
print("-" * 80)
# 2. Transferência Válida
print(">> Executando transferência atômica de R$ 800,00 da Conta #1 para #2...")
sucesso = dao.transferir_fundos_atomico(c1.id_conta, c2.id_conta, Decimal("800.00"))
print(f"Operação Concluída com Sucesso? : {sucesso}")
c1_atual = dao.buscar_por_id(1)
c2_atual = dao.buscar_por_id(2)
print(f"Novo Saldo Conta #1: R$ {c1_atual.saldo:>10.2f}")
print(f"Novo Saldo Conta #2: R$ {c2_atual.saldo:>10.2f}")
print("-" * 80)
# 3. Teste de Falha e Rollback Automático
print(">> Tentando transferir R$ 5.000,00 da Conta #1 (Valor acima do saldo disponível)...")
try:
dao.transferir_fundos_atomico(c1.id_conta, c2.id_conta, Decimal("5000.00"))
except ValueError as err_transacao:
print(f" [ROLLBACK DISPARADO] {err_transacao}")
c1_pos_erro = dao.buscar_por_id(1)
c2_pos_erro = dao.buscar_por_id(2)
print("\nAuditoria pós-tentativa frustrada (Saldos Intactos):")
print(f" * Saldo Conta #1 (Preservado): R$ {c1_pos_erro.saldo:>10.2f}")
print(f" * Saldo Conta #2 (Preservado): R$ {c2_pos_erro.saldo:>10.2f}")
print("=" * 80)