본문 바로가기
iaa.dev/Laravel8

Laravel 8 - Eager Loading 과 dynamic property

by chopper.kid 2022. 1. 27.

릴레이션 메소드

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

아래 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

댓글