iaa.dev/Laravel8

Laravel 8 - Eager Loading 과 dynamic property

chopper.kid 2022. 1. 27. 00:31

릴레이션 메소드

이름처럼 모델과 모델의 관계를 정의합니다.

아래 User 모델은 Post 를 hasMany로  갖는 1:다의 관계를 말합니다.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

그리고 Post 에서는 BelongsTo로 관계를 정의하고 있습니다. 

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\User;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'user_id',
    ];

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class, 'user_id');
    }

}

릴레이션 메소드는 쿼리빌더로 동작하고 , lazy loading 을 하지 않습니다.

 

다이나믹 프라퍼티: 동적 프라퍼티 (Dynamic Property)

프라퍼티에 억세스하는 것처럼 릴레이션(메소드)에 억세스가능한것을 다이나믹 프라퍼티라고 합니다.

 

use App\Models\User;

$user = User::find(1);

foreach ($user->posts as $post) {

}

위 코드처럼 유저의 포스트를 구할때 마치 프라퍼티값을 가져오는 것처럼 메소드명을 억세스할 수 있다. 

두가지 방법의 차이는 다음과 같다.

$user->posts   //Illuminate\Database\Eloquent\Collection 을 리턴

$user->posts() //릴레이션을 리턴 

$user->posts()->get()   //Illuminate\Database\Eloquent\Collection 을 리턴

다이나믹 프라퍼티는 Illuminate\Database\Eloquent\Collection  을 리턴하고 lazy loading 을 합니다. 그래서 N+1 의 문제가 발생하는것입니다.

N+1 문제

$posts = Post::all();

foreach($posts as $post){
    $post->user;
}

를 하면 디버그바에서 쿼리가 다음과 같이 실행됨을 확인할 수 있습니다.

select * from `posts`

select * from `users` where `users`.`id` = 30 limit 1
select * from `users` where `users`.`id` = 22 limit 1
select * from `users` where `users`.`id` = 41 limit 1
select * from `users` where `users`.`id` = 33 limit 1
select * from `users` where `users`.`id` = 26 limit 1
select * from `users` where `users`.`id` = 38 limit 1
.........

많은 쿼리 발생과 시간지연등의 문제가 생깁니다. 예 N+1 문제입니다.

이걸 해결하는 것이 Eager Loading 입니다. 

Eager Loading

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

foreach($posts as $post){
    $post->user;
}

위와 같이 with('user') 로 데이터를 불러오면 

select * from `posts`

select * from `users` where `users`.`id` in 
(2, 3, 7, 8, 9, 10, 13, 14, 15, 16, 17, 19, 22, 23, 41, 42, 46, 47, 48, 49, 50)

쿼리는 2번 발행됩니다.

 

기본적으로 with 를 통한 릴레이션을 불러내면 N+1은 해결될겁니다.

Eager Loading 의 방법도 여러가지입니다.

모델에서 가져올때 with() 가져오거나,

Model 내에 

protected $with = [ ];

로 지정할 수도 있습니다.

 

Lazy Loading 방지하기

Laravel 8 부터 추가된 

// app/Providers/AppServiceProvider.php
 
public function boot()
{
    Model::preventLazyLoading(! app()->isProduction());
}

를 설정하면 위 예에서 with 없이 데이터 가져오면 lazy loading 을 강제로 못하게 하고  아래와 같은 예외를 냅다 던집니다.

 

물론 개발 서버에서만 사용되니 이걸 설정해놓는것도 괜찮을듯 싶습니다.

 

 

관련링크

https://laravel.com/docs/8.x/eloquent-relationships#relationship-methods-vs-dynamic-properties

 

Eloquent: Relationships - Laravel - The PHP Framework For Web Artisans

Eloquent: Relationships Introduction Database tables are often related to one another. For example, a blog post may have many comments or an order could be related to the user who placed it. Eloquent makes managing and working with these relationships easy

laravel.com

 

반응형
SMALL