Repo in-memory di Bagian 2 hilang saat program berakhir. Pada Bagian 3 kita menggantinya dengan database SQLite via JDBC. Sekalian, kita menambahkan pencarian berdasarkan nama menggunakan operator SQL LIKE, dan index untuk mempercepat pencarian. Database SQLite dengan file persisten memungkinkan data tetap ada antar sesi pemakaian.
Skema Tabel #
Tabel barang menyimpan lima kolom: sku (primary key), nama, satuan, stok. Tambahan index pada kolom nama mempercepat pencarian LIKE:
CREATE TABLE IF NOT EXISTS barang (
sku TEXT PRIMARY KEY,
nama TEXT NOT NULL,
satuan TEXT NOT NULL,
stok INTEGER NOT NULL DEFAULT 0
);
CREATE INDEX IF NOT EXISTS idx_barang_nama ON barang(nama);PRIMARY KEY pada sku memastikan keunikan dan mempercepat lookup berdasarkan SKU. Index pada nama mempercepat pencarian LIKE meskipun SQLite memakai full-table scan untuk pola %kata%.
Implementasi Repo dengan Database #
Kode berikut menunjukkan class RepoBarangDb dengan operasi CRUD dan pencarian:
// Bagian 3: Database SQLite dan pencarian barang.
// Mendemonstrasikan akses database SQLite via JDBC, query LIKE untuk
// pencarian, dan indexing dasar.
import java.sql.Connection
import java.sql.DriverManager
data class Barang(
val sku: String,
val nama: String,
val satuan: String,
val stok: Int
)
class RepoBarangDb(private val conn: Connection) {
init {
conn.createStatement().use { stmt ->
stmt.executeUpdate("""
CREATE TABLE IF NOT EXISTS barang (
sku TEXT PRIMARY KEY,
nama TEXT NOT NULL,
satuan TEXT NOT NULL,
stok INTEGER NOT NULL DEFAULT 0
)
""".trimIndent())
stmt.executeUpdate("CREATE INDEX IF NOT EXISTS idx_barang_nama ON barang(nama)")
}
}
fun tambah(b: Barang): Boolean {
conn.prepareStatement("INSERT OR REPLACE INTO barang (sku, nama, satuan, stok) VALUES (?,?,?,?)").use { ps ->
ps.setString(1, b.sku)
ps.setString(2, b.nama)
ps.setString(3, b.satuan)
ps.setInt(4, b.stok)
return ps.executeUpdate() > 0
}
}
fun cariBySku(sku: String): Barang? {
conn.prepareStatement("SELECT sku, nama, satuan, stok FROM barang WHERE sku = ?").use { ps ->
ps.setString(1, sku)
ps.executeQuery().use { rs ->
if (rs.next()) return Barang(
rs.getString("sku"), rs.getString("nama"),
rs.getString("satuan"), rs.getInt("stok")
)
}
}
return null
}
fun cariByNama(kataKunci: String): List<Barang> {
val hasil = mutableListOf<Barang>()
conn.prepareStatement("SELECT sku, nama, satuan, stok FROM barang WHERE nama LIKE ? ORDER BY nama").use { ps ->
ps.setString(1, "%$kataKunci%")
ps.executeQuery().use { rs ->
while (rs.next()) {
hasil.add(Barang(
rs.getString("sku"), rs.getString("nama"),
rs.getString("satuan"), rs.getInt("stok")
))
}
}
}
return hasil
}
fun semua(): List<Barang> {
val hasil = mutableListOf<Barang>()
conn.prepareStatement("SELECT sku, nama, satuan, stok FROM barang ORDER BY sku").use { ps ->
ps.executeQuery().use { rs ->
while (rs.next()) {
hasil.add(Barang(
rs.getString("sku"), rs.getString("nama"),
rs.getString("satuan"), rs.getInt("stok")
))
}
}
}
return hasil
}
fun hapus(sku: String): Boolean {
conn.prepareStatement("DELETE FROM barang WHERE sku = ?").use { ps ->
ps.setString(1, sku)
return ps.executeUpdate() > 0
}
}
}
fun main() {
val conn = DriverManager.getConnection("jdbc:sqlite::memory:")
val repo = RepoBarangDb(conn)
println("=== Tambah barang ===")
repo.tambah(Barang("SKU-001", "Buku Kotlin Dasar", "pcs", 10))
repo.tambah(Barang("SKU-002", "Buku Kotlin Lanjut", "pcs", 5))
repo.tambah(Barang("SKU-003", "Pulpen Biru", "pcs", 50))
repo.tambah(Barang("SKU-004", "Pulpen Merah", "pcs", 30))
repo.tambah(Barang("SKU-005", "Buku Catatan", "pcs", 20))
println("Total: ${repo.semua().size} barang")
println()
println("=== Cari by SKU ===")
val b = repo.cariBySku("SKU-002")
println("Hasil: $b")
println()
println("=== Cari by nama 'Buku' ===")
repo.cariByNama("Buku").forEach { println("[${it.sku}] ${it.nama} stok=${it.stok}") }
println()
println("=== Cari by nama 'Pulpen' ===")
repo.cariByNama("Pulpen").forEach { println("[${it.sku}] ${it.nama} stok=${it.stok}") }
println()
println("=== Cari by nama 'Kotlin' ===")
repo.cariByNama("Kotlin").forEach { println("[${it.sku}] ${it.nama} stok=${it.stok}") }
println()
println("=== Hapus barang SKU-005 ===")
val hapus = repo.hapus("SKU-005")
println("Berhasil: $hapus")
println("Sisa: ${repo.semua().size} barang")
conn.close()
}Output yang Dihasilkan:
=== Tambah barang ===
Total: 5 barang
=== Cari by SKU ===
Hasil: Barang(sku=SKU-002, nama=Buku Kotlin Lanjut, satuan=pcs, stok=5)
=== Cari by nama 'Buku' ===
[SKU-005] Buku Catatan stok=20
[SKU-001] Buku Kotlin Dasar stok=10
[SKU-002] Buku Kotlin Lanjut stok=5
=== Cari by nama 'Pulpen' ===
[SKU-003] Pulpen Biru stok=50
[SKU-004] Pulpen Merah stok=30
=== Cari by nama 'Kotlin' ===
[SKU-001] Buku Kotlin Dasar stok=10
[SKU-002] Buku Kotlin Lanjut stok=5
=== Hapus barang SKU-005 ===
Berhasil: true
Sisa: 4 barangMari kita bedah operasi JDBC:
conn.createStatement().use { stmt -> ... }:useadalah extension function yang otomatis menutup resource. Setara dengantry-with-resourcesdi Java. Setelah blok selesai,Statementditutup meskipun terjadi exception.INSERT OR REPLACE INTO: SQLite-specific. Jika SKU sudah ada, baris diganti; jika belum, baris baru disisipkan. Berguna untuk operasi upsert.ps.setString(1, "%$kataKunci%"): parameter untuk LIKE. Tanda%di awal dan akhir mencocokkan kata kunci di mana saja dalam nama. Driver memperlakukan string sebagai literal, bukan kode SQL, sehingga aman dari SQL injection.rs.next(): memajukan kursor ke baris berikutnya. Mengembalikantruejika ada baris,falsejika akhir result set. Polaif (rs.next())untuk satu hasil,while (rs.next())untuk banyak.ORDER BY nama: mengurutkan hasil berdasarkan nama. TanpaORDER BY, urutan tidak ditentukan dan dapat berubah antar eksekusi.ps.executeUpdate() > 0: mengembalikan jumlah baris yang terpengaruh. Jika nol, tidak ada baris yang cocok (misalnya SKU tidak ditemukan saat DELETE).
Pencarian LIKE #
Operator LIKE melakukan pencarian pattern. Tanda % mencocokkan nol atau lebih karakter, _ mencocokkan satu karakter:
nama LIKE 'Buku%'— diawali “Buku”.nama LIKE '%Buku'— diakhiri “Buku”.nama LIKE '%Buku%'— mengandung “Buku” di mana saja.
Pencarian %kata% paling umum karena fleksibel. Namun, pola ini tidak dapat memakai index secara penuh; SQLite melakukan full-table scan. Untuk pencarian text yang sering, pertimbangkan FTS (Full-Text Search) yang disediakan SQLite.
Index untuk Performa #
CREATE INDEX idx_barang_nama ON barang(nama) membuat B-tree index pada kolom nama. Index mempercepat:
- Pencarian
nama = 'X'(exact match). - Pencarian
nama LIKE 'X%'(prefix match).
Index tidak membantu nama LIKE '%X%' karena pola tidak memiliki prefix. Untuk performa optimal pada %X%, gunakan FTS atau cache in-memory.
Referensi Lanjutan #
- Kotlin Collections overview
— pengantar koleksi Kotlin untuk
filterdanbinarySearchpada pencarian barang inventaris
Ringkasan Bagian 3 #
Database SQLite kini menyimpan barang secara persisten. RepoBarangDb menangani CRUD dan pencarian LIKE dengan parameterized query. Index pada nama mempercepat pencarian prefix. Pada Bagian 4 kita menghasilkan laporan stok (total nilai, stok rendah) dan menulis unit test untuk logika inventaris.