Laravel Eloquent Relationship Eager Loading Query Pattern
Definisi
Eloquent relationship adalah cara Laravel memodelkan hubungan antar tabel database melalui model. Dengan relasi ini, model bisa saling mengambil data tanpa harus menulis join manual di setiap tempat.
Eager loading adalah teknik untuk memuat relasi sejak awal agar Laravel tidak menjalankan query tambahan berulang-ulang saat data relasi diakses. Tujuannya utama adalah mencegah masalah N+1 query.
Query pattern adalah pola menyusun query Eloquent supaya tetap jelas, efisien, dan mudah dirawat. Di praktiknya, ini berarti tahu kapan memakai with, kapan memakai load, kapan membatasi kolom relasi, dan kapan memindahkan logika query ke tempat yang lebih tepat.
Kenapa penting
Kalau relasi Eloquent dipakai tanpa strategi yang jelas, aplikasi biasanya terlihat normal di data kecil, lalu mulai lambat saat jumlah record naik. Penyebab paling umum adalah N+1 query: satu query untuk data utama, lalu satu query tambahan untuk setiap baris ketika relasi diakses.
Masalah ini sering tidak terlihat saat development karena data masih sedikit. Begitu dataset membesar, query membengkak, response time naik, dan database ikut terbebani.
Relasi Dasar
Laravel menyediakan beberapa tipe relasi utama.
hasOne
Dipakai saat satu model memiliki satu model terkait.
Contoh: User punya satu Profile.
hasMany
Dipakai saat satu model memiliki banyak model terkait.
Contoh: User punya banyak Post.
belongsTo
Dipakai di sisi yang memiliki foreign key.
Contoh: Post milik satu User.
belongsToMany
Dipakai untuk relasi many-to-many melalui tabel pivot.
Contoh: User punya banyak Role, dan Role dimiliki banyak User.
Relasi turunan lain
Laravel juga punya relasi seperti morphOne, morphMany, dan morphTo untuk kasus polymorphic. Untuk dokumentasi dasar, empat relasi di atas biasanya sudah jadi fondasi utama.
Contoh Struktur Model
// app/Models/User.php
class User extends Model
{
public function profile()
{
return $this->hasOne(Profile::class);
}
public function posts()
{
return $this->hasMany(Post::class);
}
}
// app/Models/Post.php
class Post extends Model
{
public function user()
{
return $this->belongsTo(User::class);
}
}
// app/Models/Role.php
class Role extends Model
{
public function users()
{
return $this->belongsToMany(User::class);
}
}Diagram Relasi Model
Diagram ini menunjukkan arah hubungan, bukan detail implementasi SQL. Saat membaca Eloquent, arah relasi penting karena menentukan model mana yang mendefinisikan method relasi dan model mana yang menyimpan foreign key.
Cara Kerja Eager Loading
Tanpa eager loading, pola yang sering terjadi seperti ini:
- ambil daftar
posts - saat loop, akses
$post->user - Laravel menjalankan query user untuk setiap post
Kalau ada 100 post, bisa muncul 101 query: satu query untuk post, lalu 100 query untuk user.
Dengan eager loading, Laravel mengambil data utama dan relasi dalam query terpisah yang lebih sedikit, lalu menghubungkannya di memory.
Contoh:
$posts = Post::with('user')->get();
foreach ($posts as $post) {
echo $post->user->name;
}Pada pola ini, Laravel umumnya menjalankan:
- 1 query untuk
posts - 1 query untuk semua
usersyang dibutuhkan
Jadi jumlah query tidak bertambah seiring jumlah item di loop.
Alur Query
Alur ini menggambarkan perbedaan utama eager loading: data relasi diambil sebelum loop, bukan saat loop berjalan.
N+1 Query
N+1 query adalah pola ketika aplikasi menjalankan 1 query untuk data utama, lalu N query tambahan untuk relasi di dalam loop.
Contoh yang rawan:
$posts = Post::all();
foreach ($posts as $post) {
echo $post->user->name;
}Kalau user belum dimuat, setiap akses $post->user memicu query baru.
Mengapa ini buruk
- jumlah query naik sebanding dengan jumlah data
- database menerima beban yang tidak perlu
- response time meningkat
- performa sulit diprediksi saat data tumbuh
Cara mencegah
- pakai eager loading dengan
with()saat mengambil data - pastikan relasi yang dipakai di loop sudah dimuat
- hindari akses relasi secara spontan di view atau resource tanpa pengecekan
Kapan Memakai with
Gunakan with() saat relasi sudah diketahui sejak query awal.
Contoh:
$posts = Post::with('user')->latest()->paginate(10);with() cocok untuk:
- halaman daftar data
- API response yang perlu relasi sejak awal
- query yang akan dipakai di view, resource, atau transformer
- kasus ketika kamu sudah tahu relasi apa saja yang dibutuhkan
Nested eager loading
Kalau relasi bertingkat, with() juga bisa dipakai secara nested.
$posts = Post::with(['user', 'comments.user'])->get();Ini berguna saat sebuah relasi punya relasi lagi, misalnya komentar beserta penulis komentar.
Select kolom relasi seperlunya
Kalau data yang dibutuhkan sedikit, batasi kolom yang diambil.
$posts = Post::with(['user:id,name'])->get();Pola ini sehat karena mengurangi payload yang tidak perlu. Yang penting, kolom key seperti id tetap disertakan agar relasi bisa dipetakan dengan benar.
Kapan Memakai load
Gunakan load() saat model atau collection sudah terlanjur diambil, lalu kamu ingin memuat relasi setelahnya.
Contoh:
$post = Post::findOrFail($id);
$post->load('user', 'comments.user');load() cocok untuk:
- data yang sudah ada di memory
- penambahan relasi secara kondisional setelah query utama selesai
- kasus di service layer atau setelah validasi logic tertentu
Bedanya dengan with
with()dipasang saat query belum dieksekusiload()dipakai setelah model atau collection sudah didapat
Secara praktis:
- kalau kamu masih membangun query, pakai
with() - kalau datanya sudah ada, pakai
load()
Kapan Memakai loadMissing
loadMissing() berguna kalau kamu hanya ingin memuat relasi yang belum ada.
$post->loadMissing('user');Ini cocok saat layer sebelumnya mungkin sudah memuat sebagian relasi, dan kamu ingin menghindari query ulang yang tidak perlu.
Pola Query yang Sehat
1. Ambil data sesuai kebutuhan, jangan terlalu banyak
$posts = Post::select('id', 'title', 'user_id')
->with('user:id,name')
->latest()
->paginate(10);Pola ini menjaga query tetap ramping. Jangan ambil kolom yang tidak dipakai.
2. Pindahkan logika query ke level yang tepat
Kalau query makin kompleks, jangan tumpuk semuanya di controller.
Lebih sehat kalau:
- filtering dasar tetap dekat dengan query
- logic reuse dipindah ke scope, repository, atau service
- controller hanya merangkai input dan output
3. Hindari relasi di dalam loop tanpa persiapan
// kurang sehat
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}Kalau perlu jumlah post, lebih baik eager load atau pakai agregasi.
$users = User::withCount('posts')->get();4. Pakai agregasi saat yang dibutuhkan cuma angka
Kalau yang dibutuhkan hanya jumlah, jangan ambil seluruh koleksi relasi.
$users = User::withCount('posts')->get();Kalau yang dibutuhkan cuma total, ini lebih efisien daripada memuat semua posts lalu menghitung di PHP.
5. Gunakan eager loading secara sadar, bukan otomatis di semua tempat
Eager loading memang membantu, tapi bukan berarti semua relasi harus selalu dimuat.
Tambahkan relasi hanya jika memang dipakai. Terlalu banyak eager loading juga bisa membuat query besar dan payload membengkak.
Contoh Perbandingan
Tanpa eager loading
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count();
}Dengan eager loading
$users = User::with('posts')->get();
foreach ($users as $user) {
echo $user->posts->count();
}Dengan agregasi
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo $user->posts_count;
}Urutan pilihan yang sehat biasanya begini:
- pakai
withCountkalau cuma butuh hitungan - pakai
withkalau perlu data relasi - pakai akses relasi langsung hanya kalau data memang sudah pasti dimuat
Catatan Implementasi
- Definisikan relasi di model dengan nama method yang konsisten dan mudah dibaca.
- Pakai
with()saat query awal sudah tahu relasi yang dibutuhkan. - Pakai
load()kalau model atau collection sudah ada di memory. - Pakai
loadMissing()untuk menghindari query ulang yang tidak perlu. - Hindari akses relasi di loop tanpa eager loading.
- Batasi kolom dengan selektif, terutama pada halaman daftar dan API.
- Gunakan
withCount,withSum, atau agregasi lain kalau yang dibutuhkan hanya nilai ringkas. - Jangan memindahkan semua query ke view atau resource; itu sering membuat N+1 sulit dilacak.
- Cek jumlah query saat debug kalau performa mulai terasa aneh.
Istilah
- Relationship: hubungan antar model Eloquent.
- Eager loading: memuat relasi lebih awal agar query berulang tidak terjadi di loop.
- N+1 query: satu query utama ditambah banyak query relasi per item.
with(): memuat relasi saat query dibangun.load(): memuat relasi setelah model atau collection sudah didapat.loadMissing(): memuat hanya relasi yang belum ada.- Pivot: tabel penghubung pada relasi many-to-many.
- Aggregasi: ringkasan data seperti count, sum, atau average tanpa memuat semua record.
Referensi terkait
- Dokumentasi resmi Laravel: Eloquent Relationships
- Dokumentasi resmi Laravel: Eager Loading
- Dokumentasi resmi Laravel: Collections dan Aggregates