경계의 경계

Hibernate의 참조 관계 설정(Referential Relationship Mapping) 본문

Spring Data/Hibernate

Hibernate의 참조 관계 설정(Referential Relationship Mapping)

gigyesik 2024. 5. 8. 20:28

들어가며

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  (0) 2024.05.08
Hibernate와 Transaction 관리  (0) 2024.05.03
Spring Boot와 Hibernate  (0) 2024.05.02