Pertemuan 10

Database Relasional
Skala Industri 🗄️

Aplikasi di dunia kerja nyata jarang sekali yang hanya memakai 1 tabel sederhana. Melanjutkan project Cafe kita, mari merakit Sistem Manajemen Cafe yang menggunakan relasi antar-tabel (Foreign Key & JOIN) dengan SQLite Murni dan Room Database!

1. SQLite Murni vs Room Database

Sistem Android sudah dilengkapi mesin database bawaan bernama SQLite. Di zaman dulu, developer merakit tabel dan menarik datanya secara manual (SQLite Murni). Namun, karena sangat rawan error, Google merilis Room—sebuah "Pembungkus Cerdas" (ORM) yang membuat penulisan SQLite jadi super aman dan rapi.

Sebagai Developer profesional, kamu wajib menguasai keduanya. Menguasai SQLite Murni memberimu pondasi logika SQL yang kuat, sedangkan menguasai Room membuatmu siap terjun ke dunia industri modern yang menuntut kode ringkas dan bebas bug.

Sang Pondasi: SQLite Murni

👍 Kelebihan:
  • Bawaan Android (Tanpa perlu download library).
  • Kontrol penuh 100% terhadap query relasi tabel secara langsung.
👎 Kekurangan:
  • Sangat rawan Typo: Jika salah ketik perintah INNER JOIN, aplikasi baru akan Crash saat di-Run!
  • Kode Cursor sangat panjang saat membongkar hasil Join dari 2 tabel.

Standar Modern: Room DB

👍 Kelebihan:
  • Verifikasi Aman: Relasi tabel divalidasi sebelum aplikasi dijalankan. Mencegah error fatal.
  • Data JOIN otomatis dikemas menjadi satu Object Java (POJO) yang rapi.
👎 Kekurangan:
  • Aturan sangat ketat (Memaksa penggunaan Background Thread).
  • Butuh pembuatan banyak class tambahan untuk merepresentasikan relasi.

2. Anatomi Komponen & Konsep POJO

Komponen SQLite Murni

Dalam SQLite Murni, semua logika biasanya ditumpuk dalam 1 file "Pawang" database.

1. SQLiteOpenHelper

Class induk yang wajib diwarisi (extends). Bertugas membuat file database pertama kali (onCreate) dan meng-update versinya (onUpgrade).

2. ContentValues

Keranjang belanja khusus tempat menaruh pasangan "Nama Kolom & Nilai" saat akan melakukan operasi Insert atau Update.

3. Cursor

Telunjuk/penunjuk yang bergerak membaca baris demi baris data hasil SELECT. Membacanya harus di-looping (while) secara manual.

Tiga Pilar Room Database

Room memakai teknik ORM (Object-Relational Mapping) yang memecah file agar arsitektur lebih bersih.

1. @Entity (Model Tabel)

Class Java biasa yang ditambahkan anotasi @Entity. Room akan otomatis menyulap variabel di Class ini menjadi nama Kolom Tabel Database.

2. @Dao (Data Access Object)

File berbentuk Interface. Ini adalah "Buku Menu Perintah" yang mendaftarkan operasi SQL (seperti @Insert, @Delete, atau @Query).

3. @Database (Sang Jenderal)

Class abstrak pusat komando yang menyatukan Entity dan DAO, lalu memerintahkan Android untuk membuat file fisik berekstensi .db di HP.

Konsep Emas: Apa itu POJO & ORM?

Dalam dunia Android modern, kalian akan sering mendengar kata POJO dan ORM. Apa sih bedanya dengan query murni?

  • ORM (Object-Relational Mapping): Ini adalah teknik ajaib milik Room. Di SQLite biasa, hasil database berbentuk "tabel mentah" (Cursor) yang harus kamu tunjuk dan bongkar satu-persatu. Dengan ORM, tabel mentah itu otomatis disulap menjadi Class Object di Java!
  • POJO (Plain Old Java Object): POJO adalah wadah/keranjang penampungnya. Dinamakan Plain (polos) karena dia adalah class Java yang sangat sederhana. Ia tidak mewarisi fitur rumit Android (seperti extends Activity). Isinya murni hanya variabel data (seperti int id, String nama) untuk menampung hasil mapping dari database sebelum dikirim ke Adapter RecyclerView.

3. Komparasi Sintaks SQL (Head-to-Head)

Mari kita lihat mengapa developer profesional lebih menyukai Room. Di bawah ini adalah perbandingan penulisan kode untuk operasi CRUD (Create, Read, Update, Delete) serta operasi database lanjutan. Perhatikan bagaimana POJO mempermudah eksekusi SELECT JOIN!

SQLite Murni

Mengeksekusi string teks panjang di onCreate(). Rawan typo koma dan spasi!
@Override
public void onCreate(SQLiteDatabase db) {
    db.execSQL("CREATE TABLE tabel_kategori (" +
               "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
               "nama_kategori TEXT)");
}

Room (Entity)

Sistem ORM otomatis menyulap Class Java ini menjadi Tabel! Sangat rapi.
@Entity(tableName = "tabel_kategori")
public class Kategori {
    @PrimaryKey(autoGenerate = true) 
    public int id;

    @ColumnInfo(name = "nama_kategori") 
    public String namaKategori;
}

4. Simulator Manajemen Cafe

Ubah toggle "Mesin Database". Coba tambahkan menu baru. Perhatikan bagaimana kode SQL dieksekusi memanjang ke bawah dengan rapi, dan lihat status Thread yang merespon perbedaan antara SQLite Murni dan Room!

Admin Cafe

Inventaris Menu

Menu kosong.
Disimpan!

UI Thread (Main)

Pekerjaan di jalur utama (Rawan Lag).

Santai
tabel_kategori (Induk)
id nama_kategori
1 Kopi
2 Non-Kopi
3 Cemilan
tabel_menu (Anak)
id nama_menu harga stok kat_id (FK) Aksi
💻 Kode Di Balik Layar:
// Menunggu aksi...

5. Bedah Kode Lengkap: Form s/d Adapter

Pilih dan pelajari susunan lengkap dari aplikasi berikut! Kode di bawah mencakup UI (XML), Model, Logic Database, sampai ke RecyclerView Adapter!

Info File: activity_main.xml & item_menu.xml

Untuk Apa?

Desain Antarmuka (User Interface).

Cara Kerja

activity_main.xml sebagai form input dan wadah utama. item_menu.xml sebagai "Cetakan" baris data yang akan digandakan.

Catatan Penting

File desain ini 100% SAMA dan akan digunakan baik untuk versi SQLite Murni maupun Room Database nanti.

<!-- 1. activity_main.xml (Layar Utama) -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent"
    android:orientation="vertical" android:padding="16dp">

    <EditText android:id="@+id/etNama" android:hint="Nama Menu"
        android:layout_width="match_parent" android:layout_height="wrap_content"/>
        
    <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content">
        <EditText android:id="@+id/etHarga" android:hint="Harga"
            android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:inputType="number"/>
        <EditText android:id="@+id/etStok" android:hint="Stok"
            android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" android:inputType="number"/>
    </LinearLayout>

    <!-- Data list dari Spinner ini akan kita set dari File Java biar aman! -->
    <Spinner android:id="@+id/spKategori"
        android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginVertical="8dp"/>

    <Button android:id="@+id/btnTambah" android:text="Simpan Menu"
        android:layout_width="match_parent" android:layout_height="wrap_content"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvMenu" android:layout_marginTop="16dp"
        android:layout_width="match_parent" android:layout_height="match_parent"/>
</LinearLayout>


<!-- ======================================================== -->
<!-- 2. item_menu.xml (Desain Baris List) -->
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent" android:layout_height="wrap_content"
    android:layout_margin="8dp" app:cardCornerRadius="12dp" app:cardElevation="4dp">

    <LinearLayout android:orientation="horizontal" android:padding="16dp"
        android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical">

        <LinearLayout android:orientation="vertical" android:layout_weight="1"
            android:layout_width="0dp" android:layout_height="wrap_content">
            <TextView android:id="@+id/tvJudul" android:textStyle="bold" android:textSize="16sp"
                android:layout_width="wrap_content" android:layout_height="wrap_content"/>
            <TextView android:id="@+id/tvKategori" android:textSize="12sp"
                android:layout_width="wrap_content" android:layout_height="wrap_content"/>
            <TextView android:id="@+id/tvHargaStok" android:textColor="#10b981" android:textStyle="bold"
                android:layout_width="wrap_content" android:layout_height="wrap_content"/>
        </LinearLayout>

        <!-- Tombol Delete -->
        <ImageButton android:id="@+id/btnDelete"
            android:layout_width="40dp" android:layout_height="40dp"
            android:src="@android:drawable/ic_menu_delete"
            android:background="?attr/selectableItemBackgroundBorderless"/>
    </LinearLayout>
</androidx.cardview.widget.CardView>

5. Mengintip Isi Database

Karena Room adalah "Pembungkus" dari SQLite, file database yang dihasilkan tetaplah file SQLite biasa (.db) dan tersimpan di lokasi yang sama persis: /data/data/com.package.aplikasi/databases/.

Lalu bagaimana cara kita mengecek apakah data kita benar-benar masuk ke tabel?

App Inspection Logcat Terminal
Databases
cafe_room.db
tabel_menu
tabel_kategori
sqlite_sequence
tabel_menu
id (PK) nama_menu harga stok kategori_id
1 Espresso 20000 50 1
2 Matcha Latte 25000 30 2

1. Database Inspector (Modern)

Ini cara paling ajaib! Saat aplikasi berjalan (Run) di Emulator/HP, klik tab App Inspection di panel bawah Android Studio.

  • Kamu bisa melihat tabel dan isi data secara Real-time.
  • Bisa memodifikasi data langsung dari Android Studio.
  • Bisa mengetik perintah SQL manual untuk testing.

2. Export ke PC (Cara Lama)

Jika kamu ingin mengambil filenya: Buka Device File Explorer ➔ cari path /data/data/<package_name>/databases/.

🚨 PENTING: Karena Room menggunakan sistem Write-Ahead Logging (WAL), kamu WAJIB mendownload 3 file sekaligus (.db, .db-wal, dan .db-shm) agar isinya tidak kosong saat dibuka di aplikasi DB Browser for SQLite!

6. Tugas Super: Kasir Cafe!

Kalian sudah menguasai Relasi Database (Modul 9) dan RecyclerView (Modul 8). Buktikan keahlianmu dengan merakit sistem pencatat pesanan kasir cafe utuh yang fungsional!

🗡️ Misi A: Veteran (Gunakan SQLite Murni)

  1. Buat Tabel KategoriPesanan (Id, Tipe: "Dine-in / Takeaway").
  2. Buat Tabel Pesanan (Id, Nama Pelanggan, Total Harga, kategori_id FK).
  3. Buat fungsi `INNER JOIN` di DB Helper untuk menarik riwayat pesanan beserta tipe pesanannya.
  4. Di `MainActivity`, buat desain Form Input dengan Spinner, simpan data, lalu tampilkan rapi di RecyclerView!

🚀 Misi B: Modern (Gunakan Room Database)

  1. Bangun 2 Pilar Entity (KategoriPesanan.java dan Pesanan.java) yang diikat anotasi `@ForeignKey`.
  2. Isi kategori default (Dine-in & Takeaway) lewat `addCallback` di AppDatabase.
  3. Tulis `@Query("SELECT ... INNER JOIN ...")` di DAO yang mengembalikan POJO PesananDetail.
  4. Di `MainActivity`, gunakan `ExecutorService` untuk Insert dan memuat data, lalu lemparkan ke Adapter RecyclerView menggunakan `runOnUiThread`!