初心者向け。LaravelのEagerロードとリレーションの違い・コードの書き方

1498 回閲覧されました
みなさんこんにちは、jonioです。
Laravelでリレーションをする時ですがEagerロードという物があります。
これはざっくりした説明だと普通にリレーションをするとサーバーに負荷がかかるのを減らせる物ですがEagerロードとは何かとどう記述するかについて調べたのでまとめます。
調べた内容なので間違っている部分があるかもしれませんがご了承下さい。
目次 [隠す]
普通のリレーションではどうなるか
普通にリレーションをした時のデータベースへのアクセスがどうなっているかを確認します。
Laravel-debugbarを使ってデータベースのアクセスを調べますがLaravel-debugbarを知らない方は↓の記事で導入方法を解説していますので導入してみて下さい。
ECサイトの商品一覧の画面でカートに商品を入れた商品をマイカートで表示する時にリレーションを使っているのでその時で確認します。
↓の6つの商品をカートに追加します。

この時のモデルは↓です。(モデルでリレーションをしていますがコントローラーでしてもいいです)
<?php | |
namespace App\Models; | |
use Illuminate\Database\Eloquent\Factories\HasFactory; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Support\Facades\Auth; | |
class Cart extends Model | |
{ | |
use HasFactory; | |
protected $fillable = [ | |
'stock_id', | |
'user_id' | |
]; | |
public function showCart() | |
{ | |
$user_id = Auth::id(); | |
$data['my_carts'] = $Cart->where('user_id',$user_id)->get(); | |
return $data; | |
} | |
public function stock() | |
{ | |
return $this->belongsTo('\App\Models\Stock'); | |
} | |
public function addCart($stock_id) | |
{ | |
$user_id = Auth::id(); | |
$cart_add_info = Cart::firstOrCreate(['stock_id' => $stock_id,'user_id' => $user_id]); | |
if($cart_add_info->wasRecentlyCreated){ | |
$message = 'カートに追加しました'; | |
}else{ | |
$message = 'カートに登録済みです'; | |
} | |
return $message; | |
} | |
} |
18行目のshowCartメソッドをコントローラーで呼び出します。
<?php | |
namespace App\Http\Controllers; | |
use Illuminate\Http\Request; | |
use App\Models\Stock; | |
use App\Models\Cart; | |
class ShopController extends Controller | |
{ | |
public function index() | |
{ | |
return view('shop'); | |
} | |
public function myCart(Cart $cart) | |
{ | |
$data = $cart->showCart(); | |
return view('mycart',$data); | |
} | |
public function addMycart(Request $request,Cart $cart) | |
{ | |
$stock_id = $request->stock_id; | |
$message = $cart->addCart($stock_id); | |
$my_carts = $cart->showCart(); | |
return redirect()->route('mycart')->with(compact('my_carts','message')); | |
} | |
} |
この時のビューは↓です。
<div class="mycart-container"> | |
<p class="message"> | |
@if(session('message')) | |
{{session('message')}} | |
@endif | |
</p> | |
<div> | |
<h1>{{Auth::user()->name}}さんのカートの中身</h1> | |
<p class="message">{{ $message ?? '' }}</p> | |
<div class="items"> | |
@forelse($my_carts as $my_cart) | |
<div class="item"> | |
<p>{{$my_cart->stock->name}}</p> | |
<p>{{ number_format($my_cart->stock->fee)}}円</p> | |
<img src="/image/{{$my_cart->stock->imgpath}}" alt=""> | |
</div> | |
@empty | |
<p>何も購入していません</p> | |
@endforelse | |
</div> | |
<a href="{{route('shop')}}" class="index">商品一覧へ</a> | |
</div> | |
</div> |
この場合のデータベースへのアクセスがどうなっているかを確認したら↓でした。

↑の赤枠を確認して欲しいのですが6回SQL文を実行(クエリを発行)しているのが分かります。(緑枠については後で説明します)
6回サーバーに負荷がかかっていますがデータが多ければ多いほど、サイトだったら閲覧する人が多くなればなるほどサーバーへの負担がかかる事になります。
その結果ページの読み込みに時間がかかり過ぎて表示が遅くなる場合があります。
リレーションの仕組み
今は実際に試して普通のリレーションだとサーバーに負荷がかかるのが分かりましたがリレーションの仕組みは以下となっています。
リレーションをする時に一旦リレーションをするモデルの情報を取得する(↓の緑枠)
そしてリレーションのたびにSQL文を実行する(↓の赤枠)

6回SQL文を実行するだけでなくモデルの情報を取得するために1回多くSQL文を実行するのですがこれをN+1問題と言います。
遅延読み込み
↓の青枠を見るとリレーションする際に時間がかかっているのが確認できます。

理由は以下になります。
リレーションの情報を取得してブラウザに表示するまでの流れはまずコントローラーでデータベースにアクセスしてテーブルの情報を取得します。

コントローラーで取得した情報をビューに渡してビューで表示する際にもう一度データベースにアクセスしてから表示するのでリレーションに時間がかかります。

これを遅延読み込みと言います。
だから通常のリレーションだとリレーションをする量が多ければ多いほど・サイトを閲覧する人が増えれば増える程時間がかかりますがこれを防ぐのがEagerロードです。
Eagerロード
Eagerロードを使うと↓の赤枠が一回で済みます。

カートの内容を表示するモデルでリレーションを使っているのは↓です。
public function showCart() | |
{ | |
$user_id = Auth::id(); | |
$data['my_carts'] = Cart->where('user_id',$user_id)->get(); | |
return $data; | |
} |
これをEagerロードの書き方に変えます。
whereを使う場合
<?php | |
namespace App\Models; | |
use Illuminate\Database\Eloquent\Factories\HasFactory; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Support\Facades\Auth; | |
class Cart extends Model | |
{ | |
use HasFactory; | |
protected $fillable = [ | |
'stock_id', | |
'user_id' | |
]; | |
public function showCart() | |
{ | |
$user_id = Auth::id(); | |
$data['my_carts'] = Cart::with('stock')->where('user_id',$user_id)->get(); //この行を修正 | |
return $data; | |
} | |
public function stock() | |
{ | |
return $this->belongsTo('\App\Models\Stock'); | |
} | |
public function addCart($stock_id) | |
{ | |
$user_id = Auth::id(); | |
$cart_add_info = Cart::firstOrCreate(['stock_id' => $stock_id,'user_id' => $user_id]); | |
if($cart_add_info->wasRecentlyCreated){ | |
$message = 'カートに追加しました'; | |
}else{ | |
$message = 'カートに登録済みです'; | |
} | |
return $message; | |
} | |
} |
元々のモデルの「Cart->where」の「→where」の前に「::with(‘stock‘)」を追加します。
stockは記述を変更したモデルの27行目のstockメソッドの事で「::with(‘メソッド名’)」と書けばEagerロードになります。(↓を参照)
public function stock() | |
{ | |
return $this->belongsTo('\App\Models\Stock'); | |
} |
この時にサーバーへのアクセスを見ると↓に変わっているのが確認できます。

Eagerロードは1回しかデータベースにアクセスしてないので6回サーバーにアクセスするのと比べて負荷が圧倒的にかかっていません。
情報が100個とかあった時のサーバーへの負荷を考えると大きな違いが出てくるのが分かります。
whereを使わない場合
今回のリレーションはwhereを使っていたのですがwhereを使わない場合はほんの少しだけモデルの書き方が変わります。
<?php | |
namespace App\Models; | |
use Illuminate\Database\Eloquent\Factories\HasFactory; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Support\Facades\Auth; | |
class Cart extends Model | |
{ | |
use HasFactory; | |
protected $fillable = [ | |
'stock_id', | |
'user_id' | |
]; | |
public function showCart() | |
{ | |
$user_id = Auth::id(); | |
$data['my_carts'] = Cart::with('stock')->get(); //この行を修正 | |
return $data; | |
} | |
public function stock() | |
{ | |
return $this->belongsTo('\App\Models\Stock'); | |
} | |
public function addCart($stock_id) | |
{ | |
$user_id = Auth::id(); | |
$cart_add_info = Cart::firstOrCreate(['stock_id' => $stock_id,'user_id' => $user_id]); | |
if($cart_add_info->wasRecentlyCreated){ | |
$message = 'カートに追加しました'; | |
}else{ | |
$message = 'カートに登録済みです'; | |
} | |
return $message; | |
} | |
} |
「>where(‘user_id’,$user_id)」を取りました。
これでwhereを使わない場合のEagerロードができました。
条件付きのEagerロード
他のコードで説明します。
リレーションをする時に下記のコードを書いたとします。
SeeMovie::whereIn('movie_id', $select_movie_id)
->where('user_id', Auth::id())
->get();
ただEagerロードをするだけならコードは下記になります。
SeeMovie::whereIn('movie_id', $select_movie_id)
->where('user_id', Auth::id())
->with('recommendMovies')
->get();
条件付きのEagerロードをするとコードは下記になります。
SeeMovie::whereIn('movie_id', $select_movie_id)
->where('user_id', Auth::id())
->with(['recommendMovies' => function ($query) {
$query->where('user_id', Auth::id());
}])
->get();
無名関数(クロージャ)で条件を付けることができます。