Cara Menggunakan JSONB PostgreSQL di Laravel

Cara Menggunakan JSONB PostgreSQL di Laravel


NEW
postgresql laravel database

PostgreSQL menawarkan tipe data JSONB (JSON Binary) yang sangat ampuh untuk menyimpan data semi-terstruktur dengan performa tinggi. Dalam pengembangan aplikasi web modern menggunakan Laravel, integrasi antara fitur Eloquent ORM dan kolom JSONB PostgreSQL memberikan fleksibilitas skema basis data NoSQL tanpa harus mengorbankan keandalan database relasional (ACID).

Artikel ini membahas cara menggunakan JSONB PostgreSQL di Laravel secara praktis, mulai dari pembuatan migrasi, manipulasi model Eloquent, pembuatan indeks GIN untuk optimasi pencarian, hingga studi kasus nyata.


Mengapa Memilih JSONB Dibandingkan JSON Biasa?

Sebelum masuk ke implementasi kode, penting untuk memahami perbedaan mendasar antara tipe data JSON dan JSONB pada PostgreSQL:

  1. Format Penyimpanan: Tipe JSON menyimpan teks JSON persis sesuai format aslinya (termasuk spasi dan urutan kunci). Tipe JSONB menyimpan data dalam bentuk biner yang terurai (decompressed binary format).
  2. Kecepatan Write vs Read: JSON lebih cepat saat proses pemrosesan awal (insertion/write) karena tidak memproses ulang data. Sebaliknya, JSONB sedikit lebih lambat saat insert, namun jauh lebih cepat saat query/read karena data tidak perlu di-parse ulang setiap kali diakses.
  3. Dukungan Indexing: Kolom JSONB mendukung indeks GIN (Generalized Inverted Index), yang memungkinkan pencarian ekspresi data dalam struktur JSON dalam hitungan milidetik.

Jika Anda berencana melakukan pencarian, penyaringan, atau manipulasi kunci di dalam payload JSON, JSONB adalah pilihan utama.


1. Membuat Migration kolom JSONB di Laravel

Secara default, method $table->json() pada Laravel Migration akan membuat tipe kolom json atau jsonb tergantung driver database yang aktif. Saat menggunakan PostgreSQL, Anda bisa menggunakan method $table->jsonb() secara eksplisit untuk memastikan kolom dibuat sebagai JSONB.

Berikut adalah contoh skema tabel products yang menyimpan spesifikasi teknis dinamis dalam kolom attributes:

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('sku')->unique();
            $table->decimal('price', 12, 2);
            $table->jsonb('attributes')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

2. Konfigurasi Model Eloquent dan Casting

Untuk mempermudah konversi otomatis antara array PHP dan JSONB saat membaca atau menyimpan data, manfaatkan fitur Attribute Casting bawaan Laravel.

Edit file model Product.php:

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\AsArrayObject;

class Product extends Model
{
    use HasFactory;

    protected $fillable = [
        'name',
        'sku',
        'price',
        'attributes',
    ];

    /**
     * Cast kolom attributes ke ArrayObject agar manipulasi array lebih fluent.
     */
    protected function casts(): array
    {
        return [
            'attributes' => AsArrayObject::class,
            'price' => 'decimal:2',
        ];
    }
}

Dengan mengggunakan AsArrayObject::class, Anda dapat mengubah properti sampel di dalam atribut secara langsung tanpa perlu melakukan mutasi array manual.

$product = Product::find(1);

// Membaca data nested
echo $product->attributes['color']; // "Black"

// Mengubah atau menambah kunci nested secara langsung
$product->attributes['storage'] = '512GB';
$product->attributes['warranty']['years'] = 2;

// Simpan perubahan ke database
$product->save();

3. Querying Data JSONB Menggunakan Eloquent

Laravel menyediakan syntax yang sangat bersih menggunakan notasi panah (->) untuk mengakses dan memfilter data di dalam kolom JSONB.

A. Filter Berdasarkan Kunci Teks atau Angka

Untuk mencari produk dengan warna tertentu:

$blackProducts = Product::where('attributes->color', 'Black')->get();

Untuk menyaring berdasarkan atribut angka (misalnya garansi minimal 2 tahun):

$longWarrantyProducts = Product::where('attributes->warranty->years', '>=', 2)->get();

B. Menggunakan whereJsonContains

Metode ini menggunakan operator @> bawaan PostgreSQL untuk memeriksa apakah document JSON memuat pecahan JSON tertentu:

// Mencari produk yang memiliki fitur Bluetooth
$bluetoothProducts = Product::whereJsonContains('attributes->features', 'bluetooth')->get();

// Mencari produk dengan kombinasi atribut tertentu
$specificProducts = Product::whereJsonContains('attributes', [
    'color' => 'Black',
    'brand' => 'Logitech'
])->get();

C. Memeriksa Panjang Array JSON dengan whereJsonLength

Jika kolom JSONB menyimpan array data (misalnya daftar tag atau varian), Anda dapat menyaring record berdasarkan jumlah elemennya:

// Mencari produk yang memiliki lebih dari 3 pilihan warna
$multiColorProducts = Product::whereJsonLength('attributes->available_colors', '>', 3)->get();

4. Optimasi Performa Query dengan GIN Indexing

Tanpa indeks, pencarian di dalam kolom JSONB pada tabel berukuran ribuan hingga jutaan baris akan memaksa PostgreSQL melakukan Sequential Scan yang lambat. Untuk mengatasinya, buatlah GIN Index.

Anda dapat menambahkan indeks GIN melalui file migration Laravel menggunakan metode DB::statement():

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        // Membuat GIN Index pada seluruh payload JSONB
        DB::statement('CREATE INDEX idx_products_attributes_gin ON products USING gin (attributes);');
        
        // Atau buat GIN Index spesifik pada jsonb_path_ops untuk efisiensi memori yang lebih baik
        // DB::statement('CREATE INDEX idx_products_attributes_path ON products USING gin (attributes jsonb_path_ops);');
    }

    public function down(): void
    {
        DB::statement('DROP INDEX IF EXISTS idx_products_attributes_gin;');
    }
};

Tips Performa: Indeks jsonb_path_ops membutuhkan ruang penyimpanan yang lebih kecil dan memberikan performa query @> yang lebih cepat dibandingkan indeks GIN standar. Namun, jsonb_path_ops hanya mendukung query yang menggunakan operator pembanding eksak (@>), bukan pencarian berdasarkan keberadaan kunci (key existence).


5. Kapan Harus Menggunakan JSONB dan Kapan Harus Menghindarinya?

Meskipun JSONB PostgreSQL sangat fleksibel, menggunakannya secara berlebihan dapat menjadi anti-pattern.

Kapan Menggunakan JSONB:

  • E-Commerce Product Attributes: Spesifikasi produk beragam (misal: smartphone punya kapasitas RAM/CPU, baju punya ukuran/bahan).
  • Payload API Integrasi: Menyimpan tanggapan dari pihak ketiga (misal: webhook payment gateway atau ERP).
  • User Preferences & Settings: Konfigurasi dashboard atau tema pengguna yang tidak memerlukan relasi antar tabel.

Kapan Menghindari JSONB:

  • Data yang membutuhkan batasan integritas relasional (Foreign Key Constraints).
  • Kolom yang sangat sering diperbarui secara terpisah (high-frequency atomic updates).
  • Kunci yang membutuhkan pencarian konsisten pada seluruh entitas aplikasi (lebih baik dijadikan kolom standar).

Kesimpulan

Penggunaan kolom JSONB PostgreSQL di Laravel memberikan kombinasi terbaik antara fleksibilitas skema NoSQL dan kekuatan database relasional. Dengan memanfaatkan fitur casts() Eloquent dan indeks GIN di PostgreSQL, Anda dapat membangun aplikasi modern berkinerja tinggi tanpa mengorbankan kesederhanaan kode.

Pastikan untuk selalu mengukur performa query menggunakan EXPLAIN ANALYZE saat bekerja dengan himpunan data skala besar!

© 2026 D.N. Zaki Zamani