Laravel Eager Loading: Cara Mengatasi N+1 Query dengan with()

Apa Itu Laravel Eager Loading?

Laravel Eager Loading adalah teknik untuk mengambil data relasi Eloquent secara lebih efisien sebelum data tersebut digunakan.

Dalam Laravel, Eager Loading biasanya dilakukan menggunakan method with().

Contohnya:

$posts = Post::with('author')->get();

Kode tersebut meminta Laravel mengambil data Post sekaligus data author yang berelasi dengan post tersebut.

Eager Loading sangat berguna untuk menghindari masalah N+1 Query, yaitu kondisi ketika aplikasi menjalankan satu query untuk mengambil data utama, kemudian menjalankan query tambahan berulang kali untuk mengambil data relasinya.

Jika jumlah data semakin banyak, masalah ini dapat membuat aplikasi Laravel menjadi lambat.

Apa Itu N+1 Query di Laravel?

Misalnya terdapat model Post yang memiliki relasi dengan model User.

Pada model Post:

class Post extends Model
{
    public function author()
    {
        return $this->belongsTo(User::class, 'user_id');
    }
}

Kemudian kita mengambil semua post:

$posts = Post::all();

Lalu menampilkan nama author di Blade:

@foreach ($posts as $post)
    <h2>{{ $post->title }}</h2>
    <p>Ditulis oleh: {{ $post->author->name }}</p>
@endforeach

Sekilas kode tersebut terlihat normal.

Namun, Laravel dapat menjalankan query seperti:

SELECT * FROM posts;

Kemudian untuk setiap post, Laravel mengambil data author:

SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
SELECT * FROM users WHERE id = 3;
SELECT * FROM users WHERE id = 4;

Jika terdapat 100 post, aplikasi dapat menjalankan:

  • 1 query untuk mengambil posts
  • 100 query untuk mengambil author

Totalnya menjadi 101 query.

Inilah yang disebut N+1 Query Problem.

Mengapa N+1 Query Bisa Menjadi Masalah?

N+1 Query tidak selalu terasa ketika aplikasi hanya memiliki beberapa data.

Masalah mulai terlihat ketika jumlah data meningkat.

Misalnya sebuah halaman menampilkan 1.000 post. Jika relasi author diakses tanpa Eager Loading, aplikasi berpotensi menjalankan ratusan atau ribuan query tambahan.

Dampaknya dapat berupa:

  • Response aplikasi menjadi lebih lambat
  • Beban database meningkat
  • Penggunaan resource server bertambah
  • Waktu loading halaman meningkat
  • Performa aplikasi menurun ketika traffic bertambah

Karena itu, penggunaan Eager Loading menjadi salah satu teknik penting dalam optimasi Laravel Eloquent.


Cara Mengatasi N+1 Query dengan with()

Solusi paling umum adalah menggunakan with().

Dari kode sebelumnya:

$posts = Post::all();

ubah menjadi:

$posts = Post::with('author')->get();

Sekarang Laravel mengetahui bahwa relasi author akan digunakan.

Secara sederhana, query yang dihasilkan menjadi:

SELECT * FROM posts;

SELECT * FROM users WHERE id IN (1, 2, 3, 4);

Daripada menjalankan query author satu per satu, Laravel mengambil data yang diperlukan dalam query relasi yang lebih efisien.

Blade tetap dapat menggunakan kode yang sama:

@foreach ($posts as $post)
    <h2>{{ $post->title }}</h2>
    <p>Ditulis oleh: {{ $post->author->name }}</p>
@endforeach

Perbedaannya adalah data author sudah dimuat sebelumnya.

with() pada Eloquent Relationship

Eager Loading dapat digunakan pada berbagai jenis relationship Laravel.

One to One

Misalnya User memiliki satu Profile:

$users = User::with('profile')->get();

Kemudian:

@foreach ($users as $user)
    <p>{{ $user->name }}</p>
    <p>{{ $user->profile->bio }}</p>
@endforeach

One to Many

Misalnya satu User memiliki banyak Post:

$users = User::with('posts')->get();

Kemudian:

@foreach ($users as $user)
    <h2>{{ $user->name }}</h2>

    @foreach ($user->posts as $post)
        <p>{{ $post->title }}</p>
    @endforeach
@endforeach

Belongs To

Misalnya Post memiliki satu author:

$posts = Post::with('author')->get();

Ini adalah salah satu contoh Eager Loading yang paling sering digunakan.

Many to Many

Misalnya Post memiliki banyak Tag:

$posts = Post::with('tags')->get();

Kemudian:

@foreach ($posts as $post)
    <h2>{{ $post->title }}</h2>

    @foreach ($post->tags as $tag)
        <span>{{ $tag->name }}</span>
    @endforeach
@endforeach

Eager Loading Lebih dari Satu Relationship

with() juga dapat digunakan untuk mengambil beberapa relationship sekaligus.

$posts = Post::with([
    'author',
    'tags',
    'comments'
])->get();

Dengan pendekatan ini, Laravel akan memuat relationship yang memang diperlukan sejak awal.

Namun, jangan memasukkan semua relationship secara otomatis hanya untuk menghindari N+1 Query.

Eager Loading sebaiknya digunakan berdasarkan data yang benar-benar dibutuhkan oleh halaman atau proses tersebut.

Nested Eager Loading

Laravel juga mendukung Eager Loading untuk relationship bertingkat.

Misalnya sebuah Post memiliki author, dan author memiliki profile.

Kita dapat menggunakan:

$posts = Post::with('author.profile')->get();

Atau menggunakan array:

$posts = Post::with([
    'author.profile'
])->get();

Kemudian relationship dapat diakses:

$post->author->profile->bio;

Nested Eager Loading sangat berguna ketika aplikasi membutuhkan beberapa tingkat relationship sekaligus.

Eager Loading dengan Kolom Tertentu

Tidak selalu semua kolom dari relationship diperlukan.

Misalnya kita hanya membutuhkan id dan name dari user:

$posts = Post::with('author:id,name')->get();

Pendekatan ini dapat membantu mengurangi data yang diambil dari database.

Perhatikan bahwa kolom foreign key yang dibutuhkan relationship harus tetap disertakan.

Contohnya, jika Post menggunakan user_id untuk relationship author, model User setidaknya perlu memiliki id agar Laravel dapat menghubungkan datanya.

Eager Loading dengan Kondisi

Kita juga dapat memberikan kondisi pada relationship yang di-load.

Contohnya:

$users = User::with([
    'posts' => function ($query) {
        $query->where('published', true);
    }
])->get();

Dengan kode tersebut, Laravel hanya mengambil post yang sudah dipublikasikan.

Pada Laravel versi modern, sintaks arrow function juga dapat digunakan:

$users = User::with([
    'posts' => fn ($query) => $query->where('published', true)
])->get();

Teknik ini berguna ketika tidak semua data relationship diperlukan.

with() vs Lazy Loading

Perbedaan sederhananya adalah:

Lazy Loading

$posts = Post::all();

Relationship baru diambil ketika diakses:

$post->author;

Eager Loading

$posts = Post::with('author')->get();

Relationship sudah diminta sejak query awal.

Jika sebuah halaman memang membutuhkan relationship tersebut untuk banyak data, Eager Loading biasanya menjadi pilihan yang lebih baik.

Kapan Sebaiknya Menggunakan Eager Loading?

Gunakan Eager Loading ketika:

  • Relationship akan digunakan pada banyak model
  • Data ditampilkan dalam loop
  • API mengembalikan data beserta relationship
  • Halaman membutuhkan beberapa relationship sekaligus
  • Query Laravel mulai mengalami masalah N+1
  • Aplikasi memiliki data dalam jumlah besar

Contoh yang umum:

$products = Product::with('category')->get();

Kemudian di Blade:

@foreach ($products as $product)
    <h2>{{ $product->name }}</h2>
    <p>{{ $product->category->name }}</p>
@endforeach

Daripada membiarkan setiap $product->category memicu query tambahan, relationship tersebut sudah dimuat menggunakan with().

Kapan Tidak Perlu Menggunakan Eager Loading?

Eager Loading bukan berarti semua relationship harus selalu menggunakan with().

Misalnya kita hanya mengambil satu model:

$post = Post::find(1);

Jika relationship hanya digunakan sekali, perbedaan performanya mungkin tidak signifikan.

Masalah utama biasanya muncul ketika relationship diakses berulang kali dalam sebuah collection.

Karena itu, prinsip sederhananya adalah:

Load relationship yang memang akan digunakan, tetapi jangan memuat data yang tidak diperlukan.

Cara Mendeteksi N+1 Query di Laravel

Salah satu cara sederhana untuk mendeteksi masalah query adalah menggunakan Laravel Debugbar atau tools profiling database lainnya pada environment development.

Kita juga dapat menggunakan query listener untuk melihat query yang dijalankan:

DB::listen(function ($query) {
    logger($query->sql, $query->bindings);
});

Dengan melihat jumlah query, kita dapat mengetahui apakah sebuah proses menjalankan query database secara berlebihan.

Contohnya, jika halaman hanya membutuhkan daftar post dan author tetapi menghasilkan puluhan atau ratusan query, kemungkinan terdapat masalah N+1 Query.

Catatan: jangan mengaktifkan logging query secara sembarangan di production karena dapat menghasilkan log yang sangat banyak dan berdampak pada performa.

Eager Loading pada Laravel API

Masalah N+1 juga dapat terjadi ketika membuat API.

Contohnya:

$posts = Post::all();

return response()->json($posts);

Jika API atau Resource kemudian mengakses relationship:

$post->author

relationship tersebut dapat menyebabkan query tambahan.

Lebih baik mengambil relationship yang memang diperlukan:

$posts = Post::with('author')->get();

return response()->json($posts);

Untuk API yang lebih kompleks, Eager Loading dapat dikombinasikan dengan Laravel API Resource agar struktur response tetap konsisten.

Kesalahan yang Sering Terjadi

1. Menggunakan with() tetapi Relationship Tidak Dipakai

Contoh:

$posts = Post::with([
    'author',
    'comments',
    'tags',
    'category'
])->get();

Jika halaman hanya membutuhkan author, maka relationship lainnya sebenarnya tidak diperlukan.

Lebih baik:

$posts = Post::with('author')->get();

2. Mengira with() Selalu Membuat Satu Query

Eager Loading bukan berarti Laravel hanya menjalankan satu query.

Contohnya:

Post::with('author')->get();

Secara umum Laravel tetap perlu menjalankan query untuk mengambil posts dan query untuk mengambil users.

Yang menjadi masalah adalah jumlah query yang tidak perlu, bukan sekadar jumlah query harus selalu satu.

3. Hanya Mengoptimasi Query Utama

Kadang developer sudah mengoptimasi query utama tetapi lupa relationship.

Contohnya:

$orders = Order::latest()->get();

Kemudian:

@foreach ($orders as $order)
    {{ $order->customer->name }}
@endforeach

Jika customer belum di-load, loop tersebut dapat menyebabkan N+1 Query.

Solusinya:

$orders = Order::with('customer')
    ->latest()
    ->get();

Best Practice Eager Loading Laravel

Beberapa prinsip yang dapat digunakan:

  1. Identifikasi relationship yang digunakan di dalam loop.
  2. Gunakan with() untuk relationship yang memang diperlukan.
  3. Hindari memuat relationship yang tidak digunakan.
  4. Gunakan nested Eager Loading jika membutuhkan relationship bertingkat.
  5. Batasi kolom jika tidak semua data diperlukan.
  6. Gunakan query profiling untuk mencari N+1 Query.
  7. Uji performa dengan jumlah data yang realistis.

Kesimpulan

Laravel Eager Loading merupakan salah satu cara penting untuk meningkatkan efisiensi query database pada aplikasi Laravel.

Masalah N+1 Query biasanya muncul ketika aplikasi mengambil collection model, kemudian mengakses relationship satu per satu di dalam loop.

Daripada:

$posts = Post::all();

dan kemudian mengakses:

$post->author

berulang kali, gunakan:

$posts = Post::with('author')->get();

Dengan Eager Loading, Laravel dapat mengambil relationship secara lebih efisien sehingga jumlah query yang tidak perlu dapat dikurangi.

Jika aplikasi Laravel mulai memiliki banyak data, memahami Eloquent Relationship, Lazy Loading, Eager Loading, dan N+1 Query menjadi penting untuk menjaga performa aplikasi.

Intinya: jangan hanya melihat apakah kode Laravel berjalan. Periksa juga berapa banyak query yang sebenarnya dijalankan oleh kode tersebut.