들어가기 전에
이전글 영속성 컨텍스트 포스트를 보고 오면 좋습니다.
연관관계 매핑 기초
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에서 역방향으로 탐색할 일이 많음
- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블 영향을 주지 않음)