2024. 5. 8. 20:28ㆍSpring Data/Hibernate
들어가며
Hibernate를 사용하면서 엔티티 간에 관계를 설정하고 싶다면 PK(Primary Key)와 FK(Foreign Key) 관계로 이루어진 테이블들이 필요하다.
테이블 간 관계를 설정해두면, PK가 존재하는 테이블의 데이터를 수정하면 FK가 존재하는 테이블의 데이터도 같은 로직으로 수정된다는 장점이 있다.
Hibernate에서 지원하는 엔티티간 관계 설정 유형에는 4가지가 있다.
- 일대일 (One-To-One)
- 다대일 (Many-To-One)
- 다대다 (Many-To-Many)
- 일대다 (One-To-Many)
참조 관계
예시로 학교를 생각해 보자. 학교의 구성 요소를 수업, 선생님, 학생이라 하면 아래와 같은 테이블들을 만들 수 있다.
create table course (
title varchar(255)
)
create table course_material (
url varchar(255)
)
create table teacher (
first_name varchar(255),
last_name varchar(255)
)
create table student (
first_name varchar(255),
last_name varchar(255),
birth_date date,
wants_newsletter boolean
)
create table gender (
value varchar(255)
)
create table address (
street varchar(255),
number varchar(255),
city varchar(255)
)
테이블들을 살펴보면 테이블 간에 관계 설정이 가능함을 알 수 있다.
- ‘교사’는 ‘수업’을 한다
- ‘수업’은 ‘강의 계획서’를 가지고 있다
- ‘학생’은 ‘수업’을 선택한다
One-To-Many/Many-To-One
둘의 관계는 동전의 양면같은 것으로, 하나의 엔티티가 다른 많은 엔티티와 연결되어 있는 관계를 의미한다.
위의 예시를 생각해보자.
- 한 명(One)의 ‘교사’는 여러 개(Many)의 ‘수업’을 할 수 있다. (One-To-Many)
- 여러 개(Many)의 ‘수업’은 한 명(One)의 ‘교사’에게 할달될 수 있다. (Many-To-One)
게시판을 생각해봐도 된다. 하나의 게시글에는 여러 개의 댓글이 달릴 수 있지만, 댓글은 하나의 게시글에 속하게 된다.
One-To-Many
테이블의 ‘교사’와 ‘수업’을 엔티티로 표현해보면
@Entity
public class Teacher {
private String firstName;
private String lastName;
@OneToMany
@JoinColumn(name = "teacher_id", referencedColumnName = "id")
private List<Course> courses;
}
@Entity
public class Course {
private String title;
}
- 한명의 교사는 여러 개의 수업을 가질 수 있으므로 @OneToMany 어노테이션을 사용했다.
- Course 테이블은 Teacher 테이블을 참조하게 되므로 교사의 id(PK)를 teacher_id 라는 이름으로 참조(FK)하도록 @JoinColumn 어노테이션을 사용했다.
Many-To-One
One-To-Many 관계에서 Teacher 테이블이 피참조 역할(Owning Side)이었다면, Course 테이블은 참조 역할(Referencing Side)이다.
이를 Teacher 테이블 입장이 아닌 Course 테이블 입장에서 엔티티로 표현해보면
@Entity
public class Teacher {
private String firstName;
private String lastName;
}
@Entity
public class Course {
private String title;
@ManyToOne
@JoinColumn(name = "teacher_id", referencedColumnName = "id")
private Teacher teacher;
}
- @OneToMany 어노테이션 대신 @ManyToOne 어노테이션을 사용하였다.
- 일반적으로는 PK 테이블인 Teacher 보다 FK 테이블은 Course에 참조관계를 설정하는 것이 선호된다.
- Teacher 엔티티에서 Course 엔티티를 꼭 사용하고 싶다면, 양방향 참조(Bi-ridirectional)로 해결할 수 있다.
Eager Loading vs Lazy Loading
참조 관계를 설정한 경우 조회시 연관된 테이블을 함께 조회하기 때문에, 서버 메모리에 큰 부하를 줄 수 있다.
‘수업’이 아주 많이 개설된 상태라면, ‘교사’를 조회하는 것 만으로 서버 성능에 큰 영향을 줄 수 있다는 이야기이다.
이를 방지하기 위해 JPA는 One-To-Many 관계의 로딩 타입 기본값을 지연 로딩(Lazy Loading)으로 설정하여 ‘수업’을 조회하여도 ‘교사’를 즉시 조회하지 않는다.
반대로 Many-To-One 관계의 로딩 타입 기본값은 즉시 로딩(Eager Loading)으로, ‘교사’를 조회하면 ‘수업’ 목록이 함께 조회된다.
기본값 설정은 아래의 어노테이션 파라미터 수정을 통해 교체할 수 있다.
@OneToMany(mappedBy = "teacher", fetch = FetchType.EAGER)
private List<Course> courses;
@ManyToOne(fetch = FetchType.LAZY)
private Teacher teacher;
관계의 강제성/임의성 (Optionality)
- 기본적으로 One-To-Many 관계는 강제성이 없다. (’교사’는 ‘수업’이 없어도 존재할 수 있다)
- 기본적으로 Many-To-One 관계는 강제성이 있다. (’수업’은 ‘교사’ 없이는 존재할 수 없다)
JPA의 기본값은 모든 관계에 대해 강제성을 설정하고 있지 않다. 즉, Course 테이블에 데이터를 입력할 때 Teacher가 존재하지 않아도 입력할 수 있다.
Many-To-One 관계에 강제성을 설정하면 아래와 같다.
@ManyToOne(optional = false)
@JoinColumn(name = "teacher", referencedColumnName = "id")
private Teacher teacher;
영속성 전이 (Cascading Operation)
Many-To-One 관계에서 ‘수업’에 강제성을 설정했던 것처럼, 이번엔 ‘교사’의 변화를 ‘수업’에 전파할 필요가 있다.
JPA의 기본값은 전이시키지 않는 것이다. 즉, Teacher 테이블의 데이터가 삭제되어도 Course 테이블은 알지 못한다.
One-To-Many 관계에서 참조 테이블에 상태를 전파하려면 아래와 같다.
@ManyToOne(optional = false, cascade = CascadeType.PERSIST)
@JoinColumn(name = "teacher_id", referencedColumnName = "id")
private Teacher teacher;
One-To-One
일대일(One-To-One) 관계는 말 그대로 관계를 갖는 엔티티간 갯수가 1:1인 경우를 의미한다.
이 글의 예시에서는 ‘강의 계획서’를 들 수 있다. 하나의 ‘수업’은 하나의 ‘강의 계획서’를 갖는다.
엔티티와 양방향 관계 설정을 아래와 같이 표현할 수 있다. 로딩 방식과 상태 전이 또한 설정할 수 있다.
@Entity
public class CourseMaterial {
@Id
private Long id;
private String url;
}
@OneToOne(optional = false)
@JoinColumn(name = "course_id", referencedColumnName = "id")
private Course course;
@OneToOne(mappedBy = "course")
private CourseMaterial material;
Many-To-Many
다대다(Many-To-Many) 관계는 엔티티간 관계에 서로 다수의 row가 매핑된 경우이다.
이 글에서는 ‘학생’과 ‘수업’의 관계. 하나의 ‘수업’은 여러 명의 ‘학생’이 들을 수 있고, 한 명의 ‘학생’은 여러 개의 ‘수업’을 들을 수 있다.
DB와 엔티티에서는 이를 중간 테이블(students_courses)로 정의한다.
@ManyToMany
@JoinTable(
name = "student_courses",
joinColumns = @JoinColumn(name = "course_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id")
)
private List<Student> students;
Resources
'Spring Data > Hibernate' 카테고리의 다른 글
Hibernate의 Entity Lifecycle (1) | 2024.05.08 |
---|---|
Hibernate와 Transaction 관리 (0) | 2024.05.03 |
Spring Boot와 Hibernate (0) | 2024.05.02 |