메인 콘텐츠로 건너뛰기

관계란

데이터베이스의 테이블은 서로 관련되어 있는 경우가 많습니다. 예를 들어 블로그 게시물에는 다수의 댓글이 있거나, 주문은 그것을 수행한 사용자와 관련되어 있습니다. Eloquent는 이러한 테이블 간의 관계를 간단하게 정의·조작할 수 있는 구조를 제공합니다. 관계는 Eloquent 모델의 메서드로 정의합니다.
이 페이지에서는 User, Post, Comment, Tag 같은 구체적인 모델을 예로 사용합니다. 이 모델들과 테이블은 미리 생성되어 있다는 전제로 진행합니다.
Eloquent가 대응하는 주요 관계는 다음과 같습니다.
관계설명
hasOne1대1(부모가 하나의 자식을 가짐)
belongsTo1대1의 역방향(자식이 부모에 속함)
hasMany1대다(부모가 여러 자식을 가짐)
belongsToMany다대다(중간 테이블을 사용)

hasOne(1대1)

hasOne은 한 모델이 다른 모델을 하나만 가지는 관계를 나타냅니다. 예를 들어 User가 하나의 Profile을 가지는 경우입니다.

관계 정의

User 모델에 profile 메서드를 정의합니다.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;

class User extends Model
{
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class);
    }
}
Eloquent는 부모 모델 이름을 기반으로 외래 키를 자동으로 추측합니다. 이 경우 profiles 테이블에 user_id 컬럼이 있다고 가정합니다.

관련 데이터 가져오기

정의된 관계는 프로퍼티로 접근할 수 있습니다.
$user = User::find(1);
$profile = $user->profile;
관계 메서드를 프로퍼티로 호출하면 Eloquent가 자동으로 쿼리를 발행해 관련 데이터를 반환합니다. 이를 “다이내믹 관계 프로퍼티”라고 합니다.

belongsTo(1대1의 역방향)

belongsTohasOne의 역방향으로, 외래 키를 가진 쪽의 모델에 정의합니다. ProfileUser에 속하는 관계를 나타냅니다.

관계 정의

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Profile extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}
Eloquent는 관계 메서드명에 _id를 붙인 컬럼명(이 경우 user_id)을 외래 키로 사용합니다.

관련 데이터 가져오기

$profile = Profile::find(1);
$user = $profile->user;

echo $user->name;

hasMany(1대다)

hasMany는 하나의 부모 모델이 여러 자식 모델을 가지는 가장 자주 사용되는 관계입니다. 예를 들어 하나의 Post가 여러 Comment를 가지는 경우입니다.

관계 정의

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Post extends Model
{
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}
Eloquent는 comments 테이블에 post_id 컬럼이 있다고 가정합니다.

관련 데이터 가져오기

hasMany의 관계는 컬렉션을 반환합니다.
$post = Post::find(1);

foreach ($post->comments as $comment) {
    echo $comment->body;
}
쿼리에 조건을 추가할 수도 있습니다.
$recentComments = Post::find(1)
    ->comments()
    ->latest()
    ->take(5)
    ->get();

역방향 관계(belongsTo)

댓글에서 게시물을 참조하려면 Comment 모델에 belongsTo를 정의합니다.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Comment extends Model
{
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}
$comment = Comment::find(1);
echo $comment->post->title;

belongsToMany(다대다)

다대다 관계는 양쪽 모델이 서로 여러 관련을 가지는 경우에 사용합니다. 예를 들어 Post가 여러 Tag를 가지고, Tag도 여러 Post에 속하는 경우입니다.

테이블 구조

다대다에는 중간 테이블이 필요합니다. PostTag의 경우 post_tag라는 중간 테이블을 준비합니다.
posts
    id - integer
    title - string

tags
    id - integer
    name - string

post_tag
    post_id - integer
    tag_id - integer
중간 테이블명은 Eloquent가 관련된 두 모델명을 알파벳 순으로 결합해 자동으로 추측합니다(post + tagpost_tag).

관계 정의

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Post extends Model
{
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class);
    }
}
Tag 쪽에도 동일하게 정의함으로써 역방향에서도 참조할 수 있습니다.
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Tag extends Model
{
    public function posts(): BelongsToMany
    {
        return $this->belongsToMany(Post::class);
    }
}

관련 데이터 가져오기

$post = Post::find(1);

foreach ($post->tags as $tag) {
    echo $tag->name;
}

관련 데이터 추가·삭제

attach로 중간 테이블에 레코드를 추가하고, detach로 삭제합니다.
$post = Post::find(1);

// 태그 추가
$post->tags()->attach($tagId);

// 태그 삭제
$post->tags()->detach($tagId);

// 현재 관련을 모두 교체
$post->tags()->sync([$tagId1, $tagId2]);
sync를 사용하면 지정한 ID 이외의 중간 테이블 레코드를 자동으로 삭제하고, 전달한 ID만 관련으로 남도록 갱신합니다.

Eager 로딩

N+1 문제란

관계를 프로퍼티로 접근하면 Eloquent는 그때마다 쿼리를 발행합니다. 이를 “지연 로드(Lazy Loading)“라고 합니다. 루프 안에서 관계에 접근하면 N+1 문제가 발생합니다.
// 전체 게시물을 가져오는 1회의 쿼리
$posts = Post::all();

foreach ($posts as $post) {
    // 각 게시물마다 사용자를 가져오는 쿼리가 발행된다(N회)
    echo $post->user->name;
}
게시물이 100건이면 총 101회의 쿼리가 발행됩니다. 이는 성능에 큰 영향을 줍니다.

with()에 의한 Eager 로딩

with() 메서드를 사용하면 관련 데이터를 한 번에 가져올 수 있습니다. 이를 통해 쿼리는 2회만으로 억제됩니다.
// 게시물과 사용자를 2회의 쿼리로 가져온다
$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 추가 쿼리는 발행되지 않는다
    echo $post->user->name;
}
실행되는 SQL은 다음 두 가지뿐입니다.
select * from posts

select * from users where id in (1, 2, 3, ...)

여러 관계를 동시에 Eager 로드

배열로 여러 관계를 지정할 수 있습니다.
$posts = Post::with(['user', 'comments', 'tags'])->get();

중첩된 Eager 로드

점 표기법으로 중첩된 관계도 Eager 로드할 수 있습니다.
// 게시물의 댓글과 각 댓글의 사용자를 Eager 로드
$posts = Post::with('comments.user')->get();
Eager 로딩은 N+1 문제를 방지하기 위해 매우 중요합니다. 루프 안에서 관계에 접근할 때는 항상 with()를 사용하는 습관을 들입시다.

다음 단계

Eloquent 입문

Eloquent의 기본 CRUD 조작으로 돌아가 복습합니다.
마지막 수정일 2026년 7월 13일