들어가기 전에

이전글 영속성 컨텍스트 포스트를 보고 오면 좋습니다.

연관관계 매핑 기초

ORM을 사용하는 데 있어서 가장 중요한 개념 중 하나는 객체와 테이블의 차이를 이해하는 것이라고 생각한다.
사실 DB만 잘 배우는 것도 쉽지 않은데, 안타깝게도 ORM을 사용하려면 기본적인 DB 지식이 있어야 한다.

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

1. 단방향 연관관계 (N:1)

예를 들어, 주문(Order)이 고객(Customer)에게 속하는 경우를 생각해볼 수 있습니다. 여기서 Order 클래스는 Customer 클래스를 참조합니다.

Customer 클래스

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

    private String name;
    // 기타 필드 및 메서드
}

Order 클래스

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

    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;
    // 기타 필드 및 메서드
}

@ManyToOne 애노테이션은 Order 클래스가 Customer에 대해 다대일(N:1) 관계임을 나타냅니다.
@JoinColumn은 실제 데이터베이스의 외래 키 이름을 지정합니다.

2. 양방향 연관관계 (1:N)

양방향 연관관계에서는 Customer 클래스도 Order를 참조할 수 있습니다. 이 경우, 연관관계의 주인은 Order 클래스입니다.

Customer 클래스

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

    private String name;

    // mappedBy를 넣어줘야 된다. JoinColumn만 해서는 안된다.
    @OneToMany(mappedBy = "customer")
    private List<Order> orders = new ArrayList<>();
    // 기타 필드 및 메서드
}

2-1 연관관계의 주인과 mappedBy

@OneToMany 애노테이션에서 mappedBy 속성은 연관관계의 주인을 지정합니다.
여기서 “customer”는 Order 클래스에서 Customer를 참조하는 필드 이름입니다.

DB 테이블에서는 연관관계 주인 개념이 필요가 없다.

외래키만 넣어두면 양방향으로 다 조회가 가능하기 때문에

객체는 양방향 연관관계가 아니라, 사실 단방향 연관관계 2개이다.

  • 객체 연관관계 = 2개
  • 고객 -> 주문 연관관계 1개(단방향)
  • 주문 -> 고객 연관관계 1개(단방향)

  • 테이블 연관관계 = 1개
  • 고객 <-> 주문 연관관계 1개(양방향)

근데 객체는 mappedBy를 넣어서 주인을 설정해줘야 한다.

해당 속성은 비주인쪽에서 작성되어야 한다.

  • 연관관계의 주인(Owner) 양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정) 가능
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 maappedBy 속성 사용 X
  • 주인이 아니면 mappedBy 속성으로 주인 지정

  • 누구를 주인으로 잡아야 하는가?

  • 외래 키가 있는 곳을 주인으로 정해야 한다.
  • 여기서는 Order.customer 가 연관관계의 주인이 된다.
  • 비즈니스 로직을 기준으로 연관관계의 주인을 설정하면 안된다.

3. 일대일 연관관계 (1:1)

각 사용자(User)가 사용자 프로필(UserProfile)을 가지는 경우를 예로 들 수 있습니다.

User 클래스

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

    private String username;

    @OneToOne
    @JoinColumn(name = "profile_id")
    private UserProfile profile;
    // 기타 필드 및 메서드
}

UserProfile 클래스

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

    private String phone;
    // 기타 필드 및 메서드
}

@OneToOne 애노테이션은 일대일 관계를 나타냅니다. 여기서도 @JoinColumn을 사용하여 외래 키를 지정합니다.

4. 다대다 연관관계 (N:M)

다대다 관계는 실제로 사용될 때 중간 엔티티(Join Table)를 통해 구현되곤 합니다. 예를 들어, 학생(Student)과 과목(Course) 간의 관계를 생각해 볼 수 있습니다.

Student 클래스

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

    private String name;

    @ManyToMany
    @JoinTable(
      name = "student_course",
      joinColumns = @JoinColumn(name = "student_id"),
      inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private List<Course> courses = new ArrayList<>();
    // 기타 필드 및 메서드
}

Course 클래스

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

    private String courseName;

    @ManyToMany(mappedBy = "courses")
    private List<Student> students = new ArrayList<>();
    // 기타 필드 및 메서드
}

@ManyToMany 애노테이션을 사용하고, @JoinTable을 통해 중간 테이블을 지정합니다. 이 중간 테이블은 두 엔티티 간의 연관관계를 관리합니다.

양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대 방향으로 조회 (객체 그래프 탐색) 기능이 추가된 것 뿐
  • JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블 영향을 주지 않음)