DEV ℧ Developer Diary

[JPA] 단방향 연관관계

해당 포스트는 인프런 김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 을 듣고 정리한 글입니다.

단방향 연관관계

Entity의 기초를 정립했으니, 이제 Entity간 매핑방법에 대해 알아볼 차례이다.

해당 정리에 앞서 용어를 간단하게 설명하고자 한다.

  • 방향 (Direction) : 단방향, 양방향
  • 다중성 (Multiplicity) : 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:N) 이해
  • 연관관계의 주인(Owner) : 객체 양방향 연관관계는 관리 주인이 필요

연관관계가 필요한 이유

객체지향 성계의 목표는 자율적인 객체의 협력 공동체를 만들기 위함이기 때문이다.

예제 시나리오

간단한 예제를 통해 연관관계에 대해 알아보도록 하자

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 다대일 관계이다.

해당 내용을 각각 자바의 객체와 DB의 테이블의 연관관계를 잡아보자.

관계

@Entity
@Getter @Setter
public class Member {

    @Id @GeneratedValue
    private long id;

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

    @Column(name = "TEAM_ID")
    private Long teamId;
}

@Entity
@Getter @Setter
public class Team {

    @Id @GeneratedValue
    private Long id;

    private String name;
}

각각 Member와 Team Entity를 생성해 주고, DB에 값을 넣어주자.

Team team = new Team();
team.setName("TeamA");
entityManager.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setTeamId(team.getId());
entityManager.persist(member);

해당 결과를 조회하면 다음과 같은 결과를 얻을 수 있다.

관계

이렇게 ID값을 외래키로 가지고 있는것은 객체지향 설계에 문제점을 가지게 된다.

/* Member 객체 조회 */
Member findMember = entityManager.find(Member.class, member.getId());
/* 연관관계가 없음 */
Team findTeam = entityManager.find(Team.class, team.getId());

위와 같이 Member 객체에 대한 Team 정보를 조회하기 위해서는 Member를 조회한 뒤, 해당 정보를 이용해 Team의 식별자를 다시 조회해야 하기 때문이다.

테이블은 외래키를 조인해서 연관된 테이블을 찾지만, 객체는 참조를 사용해서 연관된 객체를 찾기 때문이다. 테이블과 객체 사이의 관계는 이런 차이를 가지고 있다.

단방향 연관관계

위의 예제를 객체지향의 관점으로 연관관계를 맵핑해보자. 객체의 연관관계를 사용하여 맵핑하는 것은 아래와 같이 하면 된다.

관계

@Entity
@Getter @Setter
public class Member {

    @Id @GeneratedValue
    private long id;

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

    /* 객체 위주의 연관관계 맵핑으로 변경 */
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
}

@Entity
@Getter @Setter
public class Team {

    @Id @GeneratedValue
    private Long id;

    private String name;
}
Team team = new Team();
team.setName("TeamA");
entityManager.persist(team);

Member member = new Member();
member.setUsername("member1");
/* Team객체를 직접 Member의 Entity에 넣어준다. */
member.setTeam(team);
entityManager.persist(member);

INSERT를 할 경우에도 Team의 Id를 넣어주는것이 아닌 Team의 객체를 넣어주면 JPA가 자동으로 JOIN 쿼리를 생성해 ID를 맵핑해서 테이블에 넣어준다.

조회의 경우에도 기존의 Team_id를 조회해서 재조회 하는것이 아닌 기존 Member 객체에서 Team 객체를 바로 불러올 수 있다.

Member findMember = entityManager.find(Member.class, member.getId());
/* 재 조회하는것이 아닌, 기존 Member객체에서 맵핑된 Team 객체를 알아서 가져온다. */
Team findTeam = findMember.getTeam();
System.out.println("findTeam = " + findTeam.getName());
select
    member0_.id as id1_0_0_,
    member0_.TEAM_ID as team_id3_0_0_,
    member0_.USERNAME as username2_0_0_,
    team1_.id as id1_1_1_,
    team1_.name as name2_1_1_
from
    Member member0_
left outer join
    Team team1_
        on member0_.TEAM_ID=team1_.id
where
    member0_.id=?

findTeam = TeamA