Kursus Python / Menyimpan data SQLite dengan parameter dan transaksi

sqlite3 menyimpan data SQL dalam berkas tanpa proses server database terpisah. Berikan nilai masukan sebagai parameter SQL dan tentukan kapan transaksi dikomit, di-rollback, serta kapan koneksi ditutup.

Pilih kebijakan transaksi yang jelas

SQLite adalah mesin database tertanam yang dipakai di dalam program. Memulai dengan satu berkas praktis untuk aplikasi lokal, tetapi perilaku akses tulis serentaknya berbeda dari database server. Membuka koneksi saja tidak berarti setiap perubahan berikutnya telah dikomit.

Latihan memerlukan Python 3.12 atau lebih baru karena menetapkan autocommit=False secara eksplisit. Konteks with con mengomit transaksi ketika blok berhasil, atau me-rollback ketika pengecualian meninggalkannya. Konteks koneksi tidak sekaligus menutup koneksi, sehingga bagian finally memanggil close secara terpisah.

Kontrak transaksi dan pembersihan melanjutkan penanganan pengecualian. Pilihan parameter dan perilaku modul dijelaskan pada dokumentasi sqlite3 Python.

Jalankan penyimpanan dan pembacaan lengkap

Gunakan folder latihan baru agar ledger-demo.db menjadi database percobaan. Jangan memakai berkas database penting untuk mencoba contoh. Simpan kode lengkap sebagai sqlite_demo.py dengan encoding UTF-8 dan jalankan python sqlite_demo.py melalui lingkungan proyek.

import sqlite3
MAX_AMOUNT = 1_000_000_000_000
amount = 8000
if type(amount) is not int or not 0 <= amount <= MAX_AMOUNT:
    raise ValueError("amount outside practice range")
con = sqlite3.connect("ledger-demo.db", autocommit=False)
try:
    with con:
        con.execute("CREATE TABLE IF NOT EXISTS expenses (id TEXT PRIMARY KEY, name TEXT NOT NULL, amount INTEGER NOT NULL CHECK(amount BETWEEN 0 AND 1000000000000))")
        con.execute("INSERT INTO expenses VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET name=excluded.name, amount=excluded.amount", ("e1", "lunch", amount))
    print(con.execute("SELECT name, amount FROM expenses ORDER BY id").fetchall())
finally:
    con.close()

Keluaran yang diharapkan:

[('lunch', 8000)]

Validasi Python hanya menerima integer biasa antara 0 dan 1.000.000.000.000. Setelah lolos, program membuat tabel jika belum ada dan menyimpan record dengan ID e1. Klausa ON CONFLICT(id) DO UPDATE memperbarui record ber-ID sama, sehingga menjalankan ulang tidak terus menambahkan salinan e1.

Hasil fetchall merupakan list tuple dari kolom yang dipilih. Klausa ORDER BY id menetapkan urutan; jangan menganggap urutan kebetulan pada hasil tanpa ORDER BY merupakan janji database.

Setelah konteks transaksi selesai, query berikutnya masih menggunakan koneksi yang terbuka. finally menutupnya sesudah query atau ketika alur gagal. Menutup koneksi dan menentukan keberhasilan transaksi tetap dua tanggung jawab berbeda.

Pisahkan parameter nilai dari struktur SQL

Tanda tanya merupakan placeholder nilai. Argumen kedua execute berisi tuple nilai yang diikat, sehingga nama dengan tanda kutip diperlakukan sebagai data. Jangan membangun SQL dengan menyisipkan langsung teks masukan. Tuple satu nilai ditulis dengan koma, misalnya (value,).

Nama tabel, nama kolom pengurutan, dan kata kunci SQL bukan nilai yang dapat diganti melalui placeholder yang sama. Jika struktur perlu bervariasi, pilih dari daftar opsi yang dikendalikan program. Pemisahan ini menjaga agar data tidak berubah menjadi potongan instruksi SQL.

PRIMARY KEY, NOT NULL, dan CHECK menerapkan sebagian aturan pada penyimpanan. Python tetap memeriksa input sebelum binding, sedangkan database memberi perlindungan untuk jalur tulis lain yang melewati validasi aplikasi. Namun jangan menganggap kedua lapisan memeriksa hal yang persis sama.

SQLite pada tabel biasa mempunyai type affinity yang fleksibel. Deklarasi INTEGER dan CHECK(amount BETWEEN ...) contoh tidak memaksa semua penulisan langsung menjadi integer biasa: nilai real 1.5 dalam rentang dapat tersimpan jika validasi Python dilewati. Jika seluruh jalur harus menegakkan tipe ketat, rancang batas database tambahan yang sesuai. Dokumentasi tipe data SQLite menjelaskan perbedaan affinity dan tipe nilai.

Integer Python dapat melebihi rentang penyimpanan integer SQLite yang bertanda 64 bit. Batas jumlah latihan sengaja jauh lebih kecil dan diperiksa sebelum nilai diikat. Rentang ini merupakan aturan contoh, bukan batas universal semua aplikasi pengeluaran.

Uji penolakan, rollback, dan eksekusi ulang

  1. Jalankan program asli dua kali pada folder sama. Pastikan hasil tetap satu record e1 bernilai 8000.
  2. Ubah hanya jumlah menjadi -1, jalankan lagi, dan perhatikan ValueError sebelum koneksi serta binding. Pulihkan 8000 dan pastikan nilai lama masih tersedia.
  3. Untuk latihan penolakan database, lakukan penulisan nilai negatif langsung melalui SQL berparameter pada database percobaan. CHECK menolaknya. Jika satu transaksi memuat penulisan valid lalu penulisan yang gagal dan pengecualiannya keluar dari konteks, rollback juga membatalkan penulisan valid dalam transaksi itu.
  4. Untuk menambah record lain, gunakan ID e2 dan jumlah yang valid. Baca dengan ORDER BY id, lalu gunakan folder percobaan baru jika perlu kembali ke kondisi satu record tanpa menghapus data secara sembarang.

Tangkap kegagalan transaksi di luar blok konteks jika rollback atas seluruh unit kerja memang dikehendaki. Jika pengecualian ditangkap dan disembunyikan di dalam blok, konteks dapat melihat akhir normal dan mengomit perubahan yang masih ada. Uji keadaan database setelah kegagalan, bukan hanya pesan kesalahannya.

CREATE TABLE IF NOT EXISTS tidak menambahkan atau mengganti CHECK pada tabel lama. Database yang dibuat dengan skema lain membutuhkan migrasi yang dirancang dan dicadangkan; jangan menganggap menjalankan ulang contoh memperbarui seluruh skema.

Jika terjadi penguncian, periksa transaksi lain yang terlalu lama dan koneksi yang belum ditutup. Hindari menambah DROP TABLE sebagai jalan pintas untuk pengulangan pada berkas yang belum diketahui isinya. Nama path database juga mengikuti aturan folder kerja, sehingga periksa lokasi aktual sebelum menilai data yang tampak hilang.