아래는 "관계 매핑"에 대해 예시와 함께 설명하는 글입니다. 롬복을 사용해 코드를 간결하게 작성했습니다.

---

### 관계 매핑

JPA에서 관계 매핑(Relationship Mapping)은 엔티티 간의 연관성을 정의하는 방법으로, 객체지향 프로그래밍과 관계형 데이터베이스의 다리를 잇는 핵심 기능입니다. 데이터베이스에서는 외래 키(Foreign Key)를 통해 테이블 간 관계를 표현하지만, JPA에서는 어노테이션(`@OneToMany`, `@ManyToOne` 등)을 사용해 객체 간 관계를 자연스럽게 매핑합니다. 이를 통해 개발자는 SQL 조인을 직접 작성하지 않고도 연관된 데이터를 쉽게 조회하고 관리할 수 있습니다.

#### 관계 매핑의 주요 유형
JPA는 네 가지 주요 관계 매핑을 지원합니다:
1. **1:1 (One-to-One)**: 한 엔티티가 다른 엔티티와 단일 관계를 가짐.
2. **1:N (One-to-Many)**: 한 엔티티가 여러 엔티티와 관계를 가짐.
3. **N:1 (Many-to-One)**: 여러 엔티티가 한 엔티티와 관계를 가짐.
4. **N:M (Many-to-Many)**: 여러 엔티티가 서로 여러 엔티티와 관계를 가짐.

#### 주요 어노테이션
- **`@OneToMany`**: 1:N 관계를 정의.
- **`@ManyToOne`**: N:1 관계를 정의.
- **`@OneToOne`**: 1:1 관계를 정의.
- **`@ManyToMany`**: N:M 관계를 정의.
- **`@JoinColumn`**: 외래 키 컬럼을 지정.

#### 예시 1: 1:N과 N:1 관계 (팀과 회원)
아래는 `Team`과 `Member` 엔티티 간의 1:N 및 N:1 관계를 보여주는 예시입니다.

```java
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Column;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Team {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "team_id")
    private Long id;

    @Column(name = "team_name")
    private String name;

    @OneToMany(mappedBy = "team") // 1:N 관계, Member의 team 필드에 의해 매핑
    private List<Member> members = new ArrayList<>();

    public Team(String name) {
        this.name = name;
    }
}
```

```java
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Column;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "member_id")
    private Long id;

    @Column(name = "username")
    private String name;

    @ManyToOne // N:1 관계
    @JoinColumn(name = "team_id") // 외래 키 컬럼 지정
    private Team team;

    public Member(String name, Team team) {
        this.name = name;
        this.team = team;
    }
}
```

#### 코드 설명
1. **`Team` (1:N)**  
   - `@OneToMany(mappedBy = "team")`: `Team`은 여러 `Member`를 가질 수 있으며, 관계의 주인은 `Member`의 `team` 필드입니다. `mappedBy`는 양방향 매핑에서 사용되며, 외래 키를 직접 관리하지 않음을 나타냅니다.

2. **`Member` (N:1)**  
   - `@ManyToOne`: `Member`는 하나의 `Team`에 속합니다.
   - `@JoinColumn(name = "team_id")`: `Member` 테이블에 `team_id`라는 외래 키 컬럼이 생성됩니다.

#### 매핑된 테이블 구조
- `Team` 테이블:
  ```sql
  CREATE TABLE team (
      team_id BIGINT PRIMARY KEY AUTO_INCREMENT,
      team_name VARCHAR(255)
  );
  ```
- `Member` 테이블:
  ```sql
  CREATE TABLE member (
      member_id BIGINT PRIMARY KEY AUTO_INCREMENT,
      username VARCHAR(255),
      team_id BIGINT,
      FOREIGN KEY (team_id) REFERENCES team(team_id)
  );
  ```

#### 동작 방식
- `Team team = new Team("개발팀");`
- `Member member = new Member("홍길동", team);`
- `team.getMembers().add(member);`
- 저장 시 `Member` 테이블에 `team_id`가 `Team`의 `team_id`를 참조하는 레코드가 삽입됩니다.

#### 예시 2: N:M 관계 (학생과 강의)
N:M 관계는 중간 테이블을 통해 구현됩니다.

```java
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Column;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long id;

    @Column(name = "student_name")
    private String name;

    @ManyToMany
    private List<Course> courses = new ArrayList<>();

    public Student(String name) {
        this.name = name;
    }
}
```

```java
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Column;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Course {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "course_id")
    private Long id;

    @Column(name = "course_name")
    private String name;

    @ManyToMany(mappedBy = "courses")
    private List<Student> students = new ArrayList<>();

    public Course(String name) {
        this.name = name;
    }
}
```

#### 코드 설명
- `@ManyToMany`: `Student`와 `Course`는 다대다 관계입니다.
- JPA는 자동으로 중간 테이블(예: `student_course`)을 생성해 외래 키를 관리합니다.

#### 매핑된 테이블 구조
- `student_course` (중간 테이블):
  ```sql
  CREATE TABLE student_course (
      student_id BIGINT,
      course_id BIGINT,
      FOREIGN KEY (student_id) REFERENCES student(student_id),
      FOREIGN KEY (course_id) REFERENCES course(course_id)
  );
  ```

#### 주의사항
- **양방향 매핑**: `mappedBy`를 사용해 관계의 주인을 명확히 지정해야 중복 매핑을 방지합니다.
- **지연 로딩(Lazy Loading)**: `@OneToMany`와 `@ManyToMany`는 기본적으로 지연 로딩이 적용되며, 필요 시 `fetch = FetchType.EAGER`로 즉시 로딩을 설정할 수 있습니다.
- **N:M의 한계**: 실무에서는 N:M 대신 중간 엔티티를 만들어 1:N, N:1로 분리하는 경우가 많습니다.

---

관계 매핑은 JPA의 강력한 기능으로, 객체 간 관계를 데이터베이스에 자연스럽게 반영합니다. 다음 장에서는 성능 최적화를 위한 페치 전략과 N+1 문제를 다뤄보겠습니다.

---

책의 흐름에 맞는지, 추가 예시나 설명이 필요하면 말씀해 주세요!