見出し画像

[laravel8] 再びN+1問題を解決

前回の記事でブロガーごとの記事の一覧を表示することができました。

しかし数カ所にN+1問題が発生してしまっているので1つずつ修正していきます。

Clockwork

TOPページのviewにも使っている

resources/views/posts.blade.php

のviewにブロガーの名前を表示するようにしたためにN+1問題が起きています。

上の記事でも紹介したClockworkを使い確認してみましょう。

スクリーンショット 2021-06-22 19.43.53

5つの記事を取得するのに7つのクエリが実行されています。

問題の箇所

routes/web.php

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

カテゴリはwithを使いeager loadingされているのですが、authorはlazy loadingになってしまっていることが原因です。authorもeager loadingするために次のように変更します。

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

実行されたクエリの数を確認します。

スクリーンショット 2021-06-22 20.00.51

7つだったクエリが3つに減っていますね。

モデルのリレーションのN+1

ブロガーの記事一覧のページを見てみましょう。

/authors/ebert (http://localhost:8000/authors/ebert)

スクリーンショット 2021-06-22 20.30.44

johnの5つの記事を取得するのに12のクエリを実行しています。

ソースコードを確認してみると

routes/web.php

Route::get('/authors/{author:username}', function (User $author){
   return view('posts', [
       'posts' => $author->posts
   ]);
});

リレーションを使って記事を取得しています。

このような場合はloadメソッドを使用しeager loadingすることが可能です。

Route::get('/authors/{author:username}', function (User $author){
   return view('posts', [
       'posts' => $author->posts->load(['category', 'author'])
   ]);
});

ページをリロードしてみると

スクリーンショット 2021-06-22 20.33.48

12のクエリは4つまで減ることができました。

しかしこのようにeager loadするために、様々な箇所で

['category', 'author']

と記述していくのは大変です。

他の方法を紹介します。

with プロパティ

モデルのクラスでwithプロパティを設定することでeager loadingを設定することができます。実際にモデルのクラスに設定してみます。

app/Models/Post.php

<?php

namespace App\Models;

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

class Post extends Model
{
   use HasFactory;

   protected $guarded = [];

   protected $with = ['category', 'author']; // withプロパティ追加

   public function category()
   {
       return $this->belongsTo(Category::class);
   }

   public function author(){
       return $this->belongsTo(User::class, 'user_id');
   }
}
protected $with = ['category', 'author'];

を設定することで、記事を取得する度に毎回categoryとauthorをデフォルトでeager loadingします。

ただこれだとcategoryやauthorが必要ない時でも自動的に取得してきてしまいます。

例えばauthorが必要ない時は

App\Models\Post::without('author')->first();

withoutメソッドを利用することでeager loadされないようになります。

最後に

今回のソースコードは


この記事が気に入ったらサポートをしてみませんか?