티스토리 뷰

Overview

객체와 테이블과 연관관계의 차이를 이해하기합니다.

예제 시나리오

단방향 연관관계에 대해 설명하기 위해 아래와 같이 세 가지 상황을 가정합니다.

  • 회원과 팀
  • 회원은 하나의 팀에만 소속될 수 있음
  • 회원:팀=N:1

연관관계가 필요한 이유

예제 시나리오의 상황을 다이어그램으로 나타내면 다음과 같습니다.

  • 객체 연관관계
  • 테이블 연관관계

테이블에 맞춰서 객체를 모델링하게되면 서로 아무 관계가 없는 객체가 되어버립니다.

테이블끼리는 FK를 이용해 관계를 나타낼 수 있지만 객체는 다른 객체의 ID를 갖고있다고해도 서로 관계가 있다고 보긴 힘듭니다.

이를 자바 코드로 나타내면 다음과 같습니다.

package com.tistory.jaimenote.jpa.domain.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Member {

  @Id
  @GeneratedValue
  private Long id;

  private String name;

  private Long teamId;

}
package com.tistory.jaimenote.jpa.domain.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Team {

  @Id
  @GeneratedValue
  private Long id;

  private String name;

}

Member객체만 있을 때 Team객체를 참조할 수 없고 역으로도 마찬가지입니다.

애플리케이션을 실행시켜서 테이블이 생성된 것을 확인해보겠습니다.

2022-06-25 01:40:51.223 DEBUG 4182 --- [           main] org.hibernate.SQL                        : 

    create table member (
       id bigint not null,
        name varchar(255),
        team_id bigint,
        primary key (id)
    )
2022-06-25 01:40:51.227 DEBUG 4182 --- [           main] org.hibernate.SQL                        : 

    create table team (
       id bigint not null,
        name varchar(255),
        primary key (id)
    )

두 개의 테이블이 생성되었는데 FK로 설정되어있지 않고, 객체를 다룰 때도 FK에 해당하는 Team ID를 직접 다뤄야하는 불편함이 있습니다.

간단한 테스트로 확인해보면,

package com.tistory.jaimenote.jpa;

import com.tistory.jaimenote.jpa.domain.entity.Member;
import com.tistory.jaimenote.jpa.domain.entity.Team;
import com.tistory.jaimenote.jpa.infra.repository.MemberRepository;
import com.tistory.jaimenote.jpa.infra.repository.TeamRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.annotation.Rollback;

@DataJpaTest
public class WithoutRelationTest {

  @Autowired
  MemberRepository memberRepository;

  @Autowired
  TeamRepository teamRepository;

  @Test
  @Rollback(false) // 로그 출력을 위해 설정
  void saveTest() {
    Team team = Team.withName("토트넘");
    teamRepository.save(team);
    Member member = Member.withName("손흥민").withTeamId(team.getId());
    memberRepository.save(member);
  }
}
Hibernate: 
    insert 
    into
        team
        (name, id) 
    values
        (?, ?)
2022-06-25 02:05:14.566 TRACE 6080 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [토트넘]
2022-06-25 02:05:14.569 TRACE 6080 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
Hibernate: 
    insert 
    into
        member
        (name, team_id, id) 
    values
        (?, ?, ?)
2022-06-25 02:05:14.576 TRACE 6080 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [손흥민]
2022-06-25 02:05:14.576 TRACE 6080 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
2022-06-25 02:05:14.577 TRACE 6080 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [2]

정상적으로 DBinsert 되었고, 쿼리를 이용해서는 FK를 사용해 JOIN 할 수 있게 되었습니다.

하지만 직접 쿼리를 작성하지 않고 Repository를 통해 조회하려면 아래와 같은 방법으로밖에 조회할 수 없습니다.

Member member = memberRepository.findById(2L)
    .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 ID"));
Team team = teamRepository.findById(member.getTeamId())
    .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 ID"));

매번 이런식으로 해야한다면 너무 불편하겠죠?

따라서 객체를 테이블에 맞춰 데이터 중심으로 모델링하게 되면 관계를 제대로 형성할 수 없습니다.

테이블이 FK를 이용해 JOIN으로 연관된 테이블을 찾을 수 있다면 객체는 객체간 참조를 사용해서 연관된 객체에 접근할 수 있습니다.


이러한 연관관계가 JPA를 이용하여 어떻게 표현될 수 있는지 다음 포스팅부터 다뤄보도록 하겠습니다.

댓글