> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Eloquent 관계 입문

> Eloquent를 사용해 테이블 간 관계(1대1·1대다·다대다)를 정의하고, 관련 데이터를 효율적으로 가져오는 방법을 설명합니다.

## 관계란

데이터베이스의 테이블은 서로 관련되어 있는 경우가 많습니다. 예를 들어 블로그 게시물에는 다수의 댓글이 있거나, 주문은 그것을 수행한 사용자와 관련되어 있습니다.

Eloquent는 이러한 테이블 간의 관계를 간단하게 정의·조작할 수 있는 구조를 제공합니다. 관계는 Eloquent 모델의 메서드로 정의합니다.

<Info>
  이 페이지에서는 `User`, `Post`, `Comment`, `Tag` 같은 구체적인 모델을 예로 사용합니다.
  이 모델들과 테이블은 미리 생성되어 있다는 전제로 진행합니다.
</Info>

Eloquent가 대응하는 주요 관계는 다음과 같습니다.

| 관계              | 설명                   |
| --------------- | -------------------- |
| `hasOne`        | 1대1(부모가 하나의 자식을 가짐)  |
| `belongsTo`     | 1대1의 역방향(자식이 부모에 속함) |
| `hasMany`       | 1대다(부모가 여러 자식을 가짐)   |
| `belongsToMany` | 다대다(중간 테이블을 사용)      |

```mermaid theme={null}
erDiagram
    users ||--o| profiles : "hasOne / belongsTo (profiles.user_id)"
    posts ||--o{ comments : "hasMany / belongsTo (comments.post_id)"
    posts }o--o{ tags : "belongsToMany (post_tag 중간 테이블)"
```

## hasOne(1대1)

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

### 관계 정의

`User` 모델에 `profile` 메서드를 정의합니다.

```php theme={null}
<?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` 컬럼이 있다고 가정합니다.

### 관련 데이터 가져오기

정의된 관계는 프로퍼티로 접근할 수 있습니다.

```php theme={null}
$user = User::find(1);
$profile = $user->profile;
```

<Tip>
  관계 메서드를 프로퍼티로 호출하면 Eloquent가 자동으로 쿼리를 발행해 관련 데이터를 반환합니다. 이를 "다이내믹 관계 프로퍼티"라고 합니다.
</Tip>

## belongsTo(1대1의 역방향)

`belongsTo`는 `hasOne`의 역방향으로, 외래 키를 가진 쪽의 모델에 정의합니다. `Profile`이 `User`에 속하는 관계를 나타냅니다.

### 관계 정의

```php theme={null}
<?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`)을 외래 키로 사용합니다.

### 관련 데이터 가져오기

```php theme={null}
$profile = Profile::find(1);
$user = $profile->user;

echo $user->name;
```

## hasMany(1대다)

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

### 관계 정의

```php theme={null}
<?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`의 관계는 컬렉션을 반환합니다.

```php theme={null}
$post = Post::find(1);

foreach ($post->comments as $comment) {
    echo $comment->body;
}
```

쿼리에 조건을 추가할 수도 있습니다.

```php theme={null}
$recentComments = Post::find(1)
    ->comments()
    ->latest()
    ->take(5)
    ->get();
```

### 역방향 관계(belongsTo)

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

```php theme={null}
<?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);
    }
}
```

```php theme={null}
$comment = Comment::find(1);
echo $comment->post->title;
```

## belongsToMany(다대다)

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

### 테이블 구조

다대다에는 중간 테이블이 필요합니다. `Post`와 `Tag`의 경우 `post_tag`라는 중간 테이블을 준비합니다.

```text theme={null}
posts
    id - integer
    title - string

tags
    id - integer
    name - string

post_tag
    post_id - integer
    tag_id - integer
```

<Info>
  중간 테이블명은 Eloquent가 관련된 두 모델명을 알파벳 순으로 결합해 자동으로 추측합니다(`post` + `tag` → `post_tag`).
</Info>

### 관계 정의

```php theme={null}
<?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 theme={null}
<?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);
    }
}
```

### 관련 데이터 가져오기

```php theme={null}
$post = Post::find(1);

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

### 관련 데이터 추가·삭제

`attach`로 중간 테이블에 레코드를 추가하고, `detach`로 삭제합니다.

```php theme={null}
$post = Post::find(1);

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

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

// 현재 관련을 모두 교체
$post->tags()->sync([$tagId1, $tagId2]);
```

<Tip>
  `sync`를 사용하면 지정한 ID 이외의 중간 테이블 레코드를 자동으로 삭제하고, 전달한 ID만 관련으로 남도록 갱신합니다.
</Tip>

## Eager 로딩

### N+1 문제란

관계를 프로퍼티로 접근하면 Eloquent는 그때마다 쿼리를 발행합니다. 이를 "지연 로드(Lazy Loading)"라고 합니다. 루프 안에서 관계에 접근하면 N+1 문제가 발생합니다.

```php theme={null}
// 전체 게시물을 가져오는 1회의 쿼리
$posts = Post::all();

foreach ($posts as $post) {
    // 각 게시물마다 사용자를 가져오는 쿼리가 발행된다(N회)
    echo $post->user->name;
}
```

게시물이 100건이면 총 101회의 쿼리가 발행됩니다. 이는 성능에 큰 영향을 줍니다.

### with()에 의한 Eager 로딩

`with()` 메서드를 사용하면 관련 데이터를 한 번에 가져올 수 있습니다. 이를 통해 쿼리는 2회만으로 억제됩니다.

```php theme={null}
// 게시물과 사용자를 2회의 쿼리로 가져온다
$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 추가 쿼리는 발행되지 않는다
    echo $post->user->name;
}
```

실행되는 SQL은 다음 두 가지뿐입니다.

```sql theme={null}
select * from posts

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

### 여러 관계를 동시에 Eager 로드

배열로 여러 관계를 지정할 수 있습니다.

```php theme={null}
$posts = Post::with(['user', 'comments', 'tags'])->get();
```

### 중첩된 Eager 로드

점 표기법으로 중첩된 관계도 Eager 로드할 수 있습니다.

```php theme={null}
// 게시물의 댓글과 각 댓글의 사용자를 Eager 로드
$posts = Post::with('comments.user')->get();
```

<Warning>
  Eager 로딩은 N+1 문제를 방지하기 위해 매우 중요합니다. 루프 안에서 관계에 접근할 때는 항상 `with()`를 사용하는 습관을 들입시다.
</Warning>

## 다음 단계

<Card title="Eloquent 입문" icon="database" href="/ko/eloquent">
  Eloquent의 기본 CRUD 조작으로 돌아가 복습합니다.
</Card>


## Related topics

- [Eloquent 입문](/ko/eloquent.md)
- [인증 입문](/ko/authentication.md)
- [Eloquent 시리얼라이제이션](/ko/eloquent-serialization.md)
- [React 입문 — Inertia × Laravel에서 사용하는 기초 지식](/ko/blog/react-introduction.md)
- [Svelte 입문 — Inertia × Laravel에서 사용하는 기초 지식](/ko/blog/svelte-introduction.md)
