Backend/JPA

JPA 연관관계 매핑 - 단방향과 양방향 연관관계 및 연관관계의 주인

개발자-제이 2025. 2. 20. 15:27

 

1. 연관관계가 필요한 이유

조영호(『객체지향의 사실과 오해』)는 객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이라고 말한다.
즉, 객체 간의 관계를 명확히 설정하여 객체들이 서로 원활하게 협력할 수 있도록 하는 것이 중요하다.

객체와 테이블의 관계 차이

비교 항목 객체 테이블
연관관계 표현 참조 필드(team) 사용 외래 키(TEAM_ID) 사용
탐색 방식 객체의 참조(member.getTeam()) 조인(SQL JOIN) 사용
다중성 일대일(1:1), 다대일(N:1), 일대다(1:N), 다대다(N:M) 같은 개념이지만 SQL로 직접 구현

예제 시나리오

  • Member(회원)과 Team(팀)이 존재
  • 회원은 하나의 팀에만 소속될 수 있음
  • 회원과 팀은 다대일(N:1) 관계

 

2. 단방향 연관관계

단방향 연관관계에서는 한쪽에서만 참조할 수 있다.
즉, Member는 Team을 참조하지만, Team은 Member를 알지 못한다.

❌ 연관관계가 없는 객체 모델링 (잘못된 방법)

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

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

    @Column(name = "TEAM_ID")
    private Long teamId; // FK를 직접 사용 (비객체지향적)
}

문제점

  • teamId를 직접 다루기 때문에 객체 관계가 아닌 데이터 중심적인 설계가 됨
  • find(Member.class, memberId)로 Member를 조회한 후,
    다시 find(Team.class, teamId)를 해야만 Team을 가져올 수 있음
  • 객체 그래프 탐색이 불가능 (member.getTeam() 불가)

 
단방향 연관관계 - 객체 지향적인 설계

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID") // 외래 키 매핑
    private Team team;
}
@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;
}

장점

  • Member에서 Team을 직접 참조 (member.getTeam())
  • SQL을 몰라도 객체 그래프 탐색 가능

조회 예제

Member member = em.find(Member.class, memberId);
Team team = member.getTeam(); // 객체 참조로 접근 가능

 

3. 양방향 연관관계와 연관관계의 주인

양방향 연관관계에서는 양쪽에서 참조가 가능하다.
즉, Member는 Team을 참조하고, Team도 Member 목록을 가질 수 있다.

양방향 연관관계 매핑

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

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

    @ManyToOne
    @JoinColumn(name = "TEAM_ID") // 연관관계의 주인
    private Team team;
}
@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team") // team 필드를 참조하는 주인이 있음
    private List<Member> members = new ArrayList<>();
}

 

연관관계의 주인 (mappedBy)가 Member가 되어야 하는 이유

1. 외래 키(FK)가 Member 테이블에 존재하기 때문

  • 데이터베이스에서 TEAM_ID 컬럼이 Member 테이블에 있으므로,
    Member 엔티티가 외래 키를 관리하는 것이 논리적으로 맞음.

2. 객체의 일관성을 유지할 수 있음

  • 연관관계 주인이 아니면 값을 저장할 수 없으므로,
    team.getMembers().add(member); 같은 코드만 실행해도
    실제 데이터베이스에는 반영되지 않음.
  • member.setTeam(team);으로 명확하게 설정해야만 저장됨.

조회 예제 (반대 방향 조회)

Team team = em.find(Team.class, teamId);
int memberSize = team.getMembers().size(); // 역방향 탐색 가능

연관관계 편의 메소드

public void setTeam(Team team) {
    this.team = team;
    if (!team.getMembers().contains(this)) {
        team.getMembers().add(this);
    }
}

 

4. 정리

  • 객체와 테이블은 연관관계를 맺는 방식이 다름
  • 단방향 연관관계: 객체에서 참조를 활용하여 외래 키를 매핑 (@ManyToOne)
  • 양방향 연관관계: 객체 그래프 탐색이 가능 (@OneToMany(mappedBy))
  • 연관관계의 주인은 외래 키가 있는 엔티티가 되어야 함 (Member)
  • 연관관계 편의 메소드를 사용하여 관계를 명확하게 관리
반응형