Bayangkan kode aplikasi yang menaburkan sqlite3.connect(), PRAGMA, dan string SQL di banyak tempat: satu file lupa row_factory, file lain lupa PRAGMA foreign_keys = ON, dan mengganti nama kolom berarti memburu query di seluruh proyek. Pola akses data (data access layer) memusatkan semua itu ke satu modul kecil: satu fungsi pembuka koneksi, lalu fungsi-fungsi repository per operasi data. Aplikasi tinggal memanggil fungsi tanpa pernah menyentuh SQL.

Satu Modul untuk Semua Akses #

Berikut isi lengkap db.py — satu-satunya tempat di proyek yang boleh memanggil sqlite3.connect():

python
import sqlite3
from contextlib import closing

DB_NAME = "toko.db"


def get_connection():
    con = sqlite3.connect(DB_NAME)
    con.row_factory = sqlite3.Row
    con.execute("PRAGMA foreign_keys = ON")
    con.execute("PRAGMA busy_timeout = 5000")
    return con


def buat_tabel():
    with closing(get_connection()) as con:
        with con:
            con.execute(
                "CREATE TABLE IF NOT EXISTS pelanggan ("
                "id INTEGER PRIMARY KEY, "
                "nama TEXT NOT NULL UNIQUE, "
                "kota TEXT)"
            )


def insert_pelanggan(nama, kota):
    con = get_connection()
    try:
        with con:
            con.execute(
                "INSERT INTO pelanggan (nama, kota) VALUES (?, ?)",
                (nama, kota),
            )
    finally:
        con.close()


def get_pelanggan_by_id(id_pelanggan):
    with closing(get_connection()) as con:
        return con.execute(
            "SELECT id, nama, kota FROM pelanggan WHERE id = ?",
            (id_pelanggan,),
        ).fetchone()


def hitung_pelanggan():
    with closing(get_connection()) as con:
        return con.execute("SELECT COUNT(*) FROM pelanggan").fetchone()[0]

Mari kita bedah bagiannya:

  1. Baris DB_NAME = "toko.db": nama berkas database hidup di satu konstanta — pindah database tinggal mengganti satu baris ini.
  2. Fungsi get_connection(): setiap koneksi baru otomatis mendapat sqlite3.Row (Chapter Row Factory) dan dua pengaturan yang sudah kita kenal: PRAGMA foreign_keys = ON — penegakan foreign key memang berlaku per koneksi, seperti dibahas pada Chapter Foreign Key — serta PRAGMA busy_timeout = 5000 agar koneksi menunggu hingga lima detik saat berkas terkunci (Chapter Penanganan Error sqlite3).
  3. with closing(get_connection()) as con:: closing() dari contextlib memastikan con.close() dipanggil saat keluar blok — menutup celah blok with con: yang hanya meng-commit, tidak menutup.
  4. with con: di dalamnya: transaction tetap dikelola otomatis — commit saat sukses, rollback saat exception.
  5. Fungsi insert_pelanggan(): bentuk panjang try/finally yang setara dengan closing() — ditampilkan agar keduanya saling dikenali di kode orang lain. Parameternya memakai placeholder ? (Chapter Query Berparameter), jadi string SQL tidak pernah dirangkai.
  6. Fungsi get_pelanggan_by_id() dan hitung_pelanggan(): operasi baca satu kali — koneksi dibuka, query dijalankan, hasil diambil, koneksi ditutup.

Memakai Pola Ini #

Aplikasi — sebut saja aplikasi.py, diletakkan satu folder dengan db.py — hanya mengimpor dan memanggil. Tidak ada SQL di sini sama sekali:

python
import os

import sqlite3

import db

if os.path.exists(db.DB_NAME):
    os.remove(db.DB_NAME)

db.buat_tabel()
db.insert_pelanggan("Budi", "Jakarta")
db.insert_pelanggan("Siti", "Bandung")

pelanggan = db.get_pelanggan_by_id(2)
print(pelanggan["id"], pelanggan["nama"], pelanggan["kota"])

try:
    db.insert_pelanggan("Budi", "Depok")
except sqlite3.IntegrityError as e:
    print("IntegrityError:", e)

print("jumlah pelanggan:", db.hitung_pelanggan())

Output yang Dihasilkan:

text
2 Siti Bandung
IntegrityError: UNIQUE constraint failed: pelanggan.nama
jumlah pelanggan: 2

Baris try/except pada pemanggilan kedua tetap perlu: pelanggaran UNIQUE adalah keputusan bisnis (apa yang dilakukan kalau nama sudah dipakai?), jadi penanganannya memang milik pemanggil, bukan modul db.py. Modul hanya menjamin bahwa pemanggil menerima sqlite3.IntegrityError yang bersih, bukan galat koneksi yang setengah jalan.

Koneksi per Operasi vs Koneksi Panjang #

Pola di atas membuka koneksi baru untuk setiap operasi. Di database server seperti PostgreSQL itu mahal, tetapi SQLite berjalan sebagai pustaka di dalam proses — membuka koneksi hanya membaca header berkas, biayanya kecil. Keduanya tetap punya wilayah masing-masing:

AspekKoneksi per operasiKoneksi panjang
Biaya per panggilanbuka-tutup tiap kali (murah di SQLite)satu kali di awal
Kunci databasehanya dipegang selama satu operasidipegang selama transaction terbuka
Cocok untukaplikasi web, CLI, kode multi-threadskrip batch, laporan analitik
Risikolupa close() jika tanpa closing()lupa commit(), kunci menggantung

Untuk aplikasi web yang permintaannya datang dari banyak thread, koneksi per operasi adalah yang paling aman: tidak ada state yang dibagi, tidak ada kunci yang lupa dilepas. Untuk skrip batch yang mengolah jutaan baris, satu koneksi panjang dengan executemany() jauh lebih efisien.

Tinggal satu langkah untuk melengkapi fondasi ini: memastikan fungsi-fungsi repository bekerja seperti yang dijanjikan, lewat test yang cepat dan terisolasi.