연관관계 매핑
객체 모델과 관계형 모델의 차이
- 객체 모델(Object Model): 객체 간의 관계는 참조(Reference)를 통해 연결됩니다.
- 예: lecture.getInstructor() → 강의가 참조하는 강사 객체를 반환.
- 관계형 모델(Relational Model): 테이블 간의 관계는 외래 키(Foreign Key)를 통해 연결됩니다.
- 예: lecture 테이블의 instructor_id는 강사를 참조.
멘탈 모델: 객체는 참조를 통해 관계를 표현하지만, 데이터베이스는 외래 키를 통해 관계를 표현한다는 점을 항상 염두에 둡니다.
엔티티 간의 관계와 역할
연관관계 매핑은 데이터베이스와 객체의 관계를 매핑하는 것이므로, 두 가지를 이해해야 합니다:
- 연관 관계의 방향성
- 단방향: 한쪽 엔티티에서만 관계를 정의.
- 양방향: 양쪽 엔티티에서 서로를 참조하며 관계를 정의.
- 연관 관계의 주인(Owner)
- 데이터베이스에 외래 키를 관리하는 엔티티.
- 주인은 항상 @JoinColumn을 사용하여 외래 키를 명시적으로 설정.
연관관계 매핑을 위한 주요 질문
멘탈 모델 형성을 위해 연관관계 매핑을 설계할 때 고려해야 할 질문들:
- 연관 관계의 방향성
- 이 관계는 단방향으로 충분한가, 아니면 양방향 관계가 필요한가?
- 예: 강의와 강사 관계에서 강의 → 강사 단방향 참조로 충분한가?
- 외래 키 관리 주인
- 누가 외래 키를 관리할 것인가?
- 예: 강의-강사 관계에서 강의(Lecture)가 외래 키를 가지고 있으므로 주인은 강의.
- 연관 관계의 데이터 로딩 전략
- 데이터를 언제 로드해야 하는가? (즉시 로딩 vs. 지연 로딩)
- 예: 강의와 강사 관계에서 강사는 즉시 로딩이 적합하지만 학생 목록은 지연 로딩이 적합할 수 있음.
현실 세계의 관계를 데이터 모델로 변환
현실 세계의 관계 → 객체 모델 → 관계형 데이터베이스 설계로 이어지는 과정을 생각합니다.
- 예시: "한 명의 강사(Instructor)가 여러 강의(Lecture)를 담당한다."
- 현실 세계: 강사 ↔ 강의 (1:N 관계)
- 객체 모델: Instructor 객체 → lectures: List<Lecture>
- 관계형 모델: lecture 테이블에 instructor_id 외래 키 추가.
멘탈 모델: 현실 세계의 관계를 객체의 참조로, 그리고 외래 키로 변환하는 단계를 시각화합니다.
연관관계 매핑의 종류와 특징
1) 1:1(One-to-One)
- 한 엔티티가 다른 하나의 엔티티와 연관된 경우.
- 외래 키는 주 테이블 또는 대상 테이블에 설정할 수 있음.
2) 1:N(One-to-Many)
- 한 엔티티가 여러 엔티티와 연관된 경우.
- 외래 키는 대상 테이블에 존재.
3) N:1(Many-to-One)
- 여러 엔티티가 한 엔티티와 연관된 경우.
- 외래 키는 현재 테이블에 존재.
4) N:M(Many-to-Many)
- 두 엔티티가 서로 여러 개의 연관 관계를 가지는 경우.
- 중간 테이블을 통해 매핑.
예제
- 학원 관리 시스템에서 강의(Lecture)와 학생(Student), 강사(Instructor) 간의 관계를 정의.
- 한 명의 강사가 여러 강의를 맡을 수 있음. (1:N 관계)
- 한 강의에 여러 명의 학생이 참여할 수 있음. (N:M 관계)
- 각 강의는 고유한 강의 정보를 가짐.
classDiagram
class Instructor {
Long id
String name
}
class Lecture {
Long id
String title
int duration
}
class Student {
Long id
String name
}
Instructor "1" --> "N" Lecture : "강사와 강의의 관계"
Lecture "N" --> "M" Student : "강의와 학생의 관계"
JPA 코드 예제
1. 강사(Instructor) 엔티티
@Entity
public class Instructor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "instructor", cascade = CascadeType.ALL)
private List<Lecture> lectures = new ArrayList<>();
// Getters, Setters
}
2. 강의(Lecture) 엔티티
@Entity
public class Lecture {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private int duration;
@ManyToOne
@JoinColumn(name = "instructor_id")
private Instructor instructor;
@ManyToMany
@JoinTable(
name = "lecture_student",
joinColumns = @JoinColumn(name = "lecture_id"),
inverseJoinColumns = @JoinColumn(name = "student_id")
)
private List<Student> students = new ArrayList<>();
// Getters, Setters
}
3. 학생(Student) 엔티티
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "students")
private List<Lecture> lectures = new ArrayList<>();
// Getters, Setters
}
데이터베이스 테이블 구조
강사 테이블
Column | Type | Constraint |
id | BIGINT | Primary Key |
name | VARCHAR | Not Null |
강의 테이블
Column | Type | Constraint |
id | BIGINT | Primary Key |
title | VARCHAR | Not Null |
duration | INT | Not Null |
instructor_id | BIGINT | Foreign Key |
학생 테이블
Column | Type | Constraint |
id | BIGINT | Primary Key |
name | VARCHAR | Not Null |
강의-학생 중간 테이블
Column | Type | Constraint |
lecture_id | BIGINT | Primary Key, Foreign Key |
student_id | BIGINT | Primary Key, Foreign Key |
주요 JPA 연관관계 매핑 설정
관계 | 애너테이션 | 외래 키 위치 | 주의사항 |
1:N (강사-강의) | @OneToMany | 대상 테이블 | mappedBy로 연관 관계의 주인 설정 필요. |
N:M (강의-학생) | @ManyToMany | 중간 테이블 | @JoinTable로 중간 테이블 정의 필요. |
N:1 (강의-강사) | @ManyToOne | 현재 테이블 | @JoinColumn으로 외래 키 명시. |
연관 관계의 종류
관계 유형 | 객체지향 관점 | 데이터베이스 관점 |
1:1 | 한 객체가 다른 객체 하나를 참조함 | 한 테이블의 행이 다른 테이블의 행과 1:1 |
1:N | 한 객체가 여러 객체를 참조함 | 한 테이블의 행이 여러 행과 연결됨 |
N:M | 여러 객체가 여러 객체와 참조 관계를 가짐 | 중간 테이블을 통해 다대다 매핑 |
연관 관계의 방향성을 간단한 예로 이해
- 단방향:강의 엔티티에서 강사 엔티티를 참조하며 강사가 누구인지 알 수 있음.
- 강의 → 강사
- 양방향:강의 엔티티는 강사를 참조하고, 강사 엔티티는 자신이 담당하는 강의 목록을 참조.
- 강의 ↔ 강사
// 단방향
@ManyToOne
@JoinColumn(name = "instructor_id")
private Instructor instructor;
// 양방향
@OneToMany(mappedBy = "instructor")
private List<Lecture> lectures;
멘탈 모델: 단방향은 단순한 조회에 적합하고, 양방향은 양쪽에서 데이터를 쉽게 탐색할 수 있도록 도와줍니다.
객체의 생명주기와 데이터 일관성
- 객체를 추가하거나 수정할 때 JPA가 관리하는 영속성 컨텍스트에서 모든 관계가 적절히 동기화되어야 합니다.
- 외래 키는 항상 연관 관계의 주인에서만 업데이트됩니다.
@Transactional
public void addLectureToInstructor(Instructor instructor, Lecture lecture) {
lecture.setInstructor(instructor); // 외래 키 설정 (주인에서 관계 설정)
entityManager.persist(lecture); // 저장
}
지연 로딩 vs. 즉시 로딩
- 즉시 로딩(FetchType.EAGER): 관계 데이터를 즉시 가져옴.
- 장점: 필요한 데이터를 한 번에 가져올 수 있음.
- 단점: 필요 없는 데이터를 항상 로드하므로 성능 저하 가능.
- 지연 로딩(FetchType.LAZY): 관계 데이터를 실제로 사용할 때 로드.
- 장점: 성능 최적화.
- 단점: 사용 시점에 추가 쿼리가 실행됨.
양방향 연관 관계의 주의점
- 양방향 매핑에서 연관 관계 주인을 명확히 설정하지 않으면 데이터 불일치 문제가 발생할 수 있음.
- mappedBy 속성을 사용하여 주인을 설정해야 함.
// 강사가 주인
@OneToMany(mappedBy = "instructor")
private List<Lecture> lectures;
다대다 관계의 중간 테이블 활용
- 다대다 관계에서는 중간 테이블을 직접 정의하여 추가 정보를 저장할 수 있음.
@Entity
public class LectureStudent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Lecture lecture;
@ManyToOne
private Student student;
private String enrollmentStatus; // 추가 정보
}
'Spring' 카테고리의 다른 글
Spring Data JPA 개요 (0) | 2025.01.15 |
---|---|
JPA - JPQL (Java Persistence Query Language) (0) | 2025.01.15 |
영속성 컨텍스트와 엔티티의 생명 주기 (1) | 2025.01.14 |
JPA - Entity 관련 애너테이션 (0) | 2025.01.14 |
JPA - @Column 데이터베이스 컬럼 매핑 (0) | 2025.01.14 |