들어가면서
개인적으로 공부한 내용과 생각한 내용을 담아 작성한 포스팅입니다.
틀린 내용이 있다면 댓글로 공유해주세요!
데이터베이스에서는 외래 키로 연관 관계를 나타내고, 객체 지향 프로그래밍에서는 객체 간의 참조로 나타낸다.
JPA를 활용한다는 것은 RDB 외래 키를 객체 간 연관관계로 매핑한다는 것.
엔티티: 데이터베이스의 테이블과 1:1로 대응되는 개념이다.
양방향: 두 객체가 서로 참조용 필드를 가지고 있다.
단방향: 두 객체 사이에 하나의 객체만 참조용 필드를 갖고 참조한다.
DB 구조상 단방향이면 반드시 JPA Entity도 단방향으로 구현하는가?
연관관계의 주인: 두 객체의 관계 중 제어의 권한(데이터 조회, 저장, 수정, 삭제)을 가지는 객체
- FK를 관리하는(FK를 필드로 가지고 있는) 엔티티를 연관관계의 주인으로 본다.
한 개의 엔티티가 다른 하나의 엔티티와 단 하나의 관게를 가지는 경우이다.
즉, 엔티티 A의 인스턴스 하나는 엔티티 B의 인스턴스 하나와 대응된다.
단방향인 경우와 양방향인 경우를 나누어 생각해보겠다.
DB에서 모든 사용자는 users 테이블에, 서비스에 등록된 모든 사진은 photos 테이블에 저장되어 있다고 하자. photos 테이블에는 사용자의 프로필 이미지도 포함되어 있다.
사용자(User) 1명은 1개의 프로필 이미지(Photo)을 가질 수 있다.
1개의 프로필 이미지는 사용자 1명에게 속한다.
단방향인 이유
일반적인 서비스에서 프로필 사진은 사용자 정보를 불러올 때만 가져온다.
프로필 사진이 따로 독립적으로 필요한 경우, 프로필 사진이 사용자 정보까지 가져와야 하는 경우는 없다.
User는 자신이 가진 Photo를 알아야 하지만
Photo는 자신을 가진 User를 몰라도 된다.
이러한 이유로 User -> Photo 단방향으로 설정한다.
JPA에서 단방향, 양방향을 정할 때는 "객체가 서로를 알아야 하는가?"를 생각해보면 된다.
일반적으로 FK의 주인은 1:N인 관계에서는 N인 쪽에 속하지만,
1:1인 관계에서는 어느 쪽이 FK를 관리할지,
즉 어느 엔티티가 연관 관계의 주인이 될지 정해야 한다.
다양한 이유가 있겠지만
다음과 같은 이유로 정했다.
1. 의미적 관계: 누가 이 관계를 관리할 수 있나?
가장 먼저 떠오르는 이유다.
User와 Photo의 의미는 사용자와 사용자의 프로필 이미지이다.
사용자가 사용자의 프로필 이미지에 포함되기 보다는, 프로필 이미지가 사용자 정보에 포함되는 쪽이 옳다.
사진이 "소속될 사용자"를 등록하고 관리하는 것보다
사용자가 프로필 사진을 설정하거나 변경하는 개념이 자연스럽다.
User가 Photo 객체를 소유하고, Photo 객체는 특정 User 객체에 종속된다.

💡 주체가 되는 엔티티(소유자)가 엔티티를 관리하는 게 일반적 -> 소유자가 FK를 가진다.
이 경우에는 User가 Photo의 id 컬럼과 매핑되는 photo_id를 가지는 게 맞다.
2. 조회 성능 고려
대부분의 경우 User 정보를 가져올 때 프로필 사진을 함께 조회할 것이다.
이 상황이 프로필 사진을 조회할 때 유저 정보를 가져오는 개념보다 익숙하다.
이때 User에 FK가 있다면 즉시 Photo와 join해서 User 정보와 특정 Photo를 조회하기 편하다.
만약 Photo에 FK를 둔다면, 먼저 User 정보를 가져온 후 user_id를 가진 Photo를 테이블 전체에서 조회해야 한다.
User Entity
@Entity
@Table(name="users")
@NoargsConstructor // Entity를 만들기 위해서는 기본 생성자가 필수적임
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne
@JoinColumn(name = "photo_id")
private Photo photo;
...
}
Photo Entity
@Entity
@Table(name="photos")
@NoargsConstructor
public class Photo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String fileName;
private String contentType;
private Long size;
...
}
그렇다면 엔티티가 서로 1:1로 대응하고, 서로의 존재를 알아야 하는 예시는 무엇이 있을까?
식당에서 1명의 고객은 1개의 음식만 주문할 수 있다고 해보자.
고객은 나중에 결제하려면 자신이 주문한 음식을 알아야 한다.
음식도 웨이터가 서빙하려면 자신을 주문한 고객을 알아야 한다.
따라서 고객 - 음식은 서로 객체를 알고 있어야 하는 관계이므로 양방향이다.
이때는 서로의 엔티티를 참조하고 있어야 한다.
고객을 Customer, 음식을 Food라고 하자.

참고로 DB에서는 단방향 관계로 설계가 가능하지만
조회(음식 서빙) 기능을 편하게 하기 위해 JPA에서 양방향 관계로 설정하는 것이다.
이 관계의 주인은 누구일까?
의미적으로 봤을 때, 고객이 특정 음식을 주문하거나 선택하는 "관리 주체"이므로 Customer가 주인이다. Customer 클래스가 FK를 가져야 한다.
Customer Entity
@Entity
@Table(name="customers")
@NoargsConstructor
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int seatIndex;
...
// 고객이 시키는 1개의 메뉴
@OneToOne
@JoinColumn(name="food_id")
private Food food;
}
Food Entity
주인인 Customer에서 Food를 참조하는 필드는 food이다.
따라서 Food 엔티티에서 mappedBy 옵션으로 Customer에서 food 필드가 연관관계의 주인임을 명시한다.
@Entity
@Table(name="food")
@NoargsConstructor
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int price;
...
// 고객이 시키는 1개의 메뉴
@OneToOne(mappedBy = "food")
private Customer customer;
}
엔티티 A의 1개의 인스턴스가 엔티티 B의 N개의 인스턴스와 대응할 때 A와 B의 관계를 1:N이라고 한다.
1명의 사용자가 여러 개의 게시글을 작성할 수 있다.
사용자를 User, 게시글을 Post라고 해보자.

DB 상에서의 구조는 위와 같다.
Post에서 FK를 가지므로 연관관계의 주인은 Post이다.
Post는 자신을 작성한 User 정보를 알아야 하지만
User는 자신이 작성한 Post 정보가 필수적인 건 아닐 때는 단방향으로 가능하다.
User Entity
@Entity
@Table(name="users")
@NoargsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
...
}
Post Entity
@Entity
@Table(name="posts")
@NoargsConstructor
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
...
@ManyToOne
@JoinColumn(name="author_id")
private User user;
}
만약 Post가 작성자 User를 알고 있어야 하고,
User도 자신이 쓴 Post를 알아야 할 때는 양방향 관계로 설계한다.
일반적인 서비스에서는 이 경우가 많지 않을까?
User Entity
1:N인 관계니 N인 객체를 List, 배열 같은 값으로 저장한다.
@Entity
@Table(name="users")
@NoargsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
...
@OneToMany(mappedBy="author")
List<Post> posts;
}
Post Entity
@Entity
@Table(name="posts")
@NoargsConstructor
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
...
@ManyToOne
@JoinColumn(name="author_id")
private User author;
}
엔티티 A의 N개의 인스턴스가 엔티티 B의 M개의 인스턴스와 대응할 때 A와 B의 관계를 N:M이라고 한다.
가장 많이 드는 예시는 대학교에서 "학생과 강의"의 관계이다.
한 명의 학생은 여러 개의 수업을 수강할 수 있다. → N:1 관계 (Student → Course)
한 개의 수업에는 여러 명의 학생이 등록될 수 있다. → 1:N 관계 (Course → Student)
따라서, N:M 관계를 형성한다.
이때는 두 객체 간의 연결 테이블이 따로 필요하다.
N:M 관계는 직접 표현할 수 없기 때문이다.
이 테이블은 실제로 유의미한 객체를 저장하는 테이블이 아니라
학생과 강의의 관계만을 저장하는 역할을 한다.

이때 연관 관계의 주인을 명확히 말하기는 조금 어렵다.
students, courses 테이블 모두 서로를 FK로 갖고 있지 않기 때문이다.
그냥 둘 중 편한 것을 주인으로 한다.
후에 기술할 @JoinTable을 쓴 쪽을 주인이라고 본다.
실무에서는 다대다 관계를 지양하므로 이 정도만 해도 충분한 듯 하다.
참고: 인프런 QnA
Student Entity
@Entity
@Table(name="students")
@NoargsConstructor
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String department;
private String email;
...
@ManyToMany // N:M 관계 명시
// 연결 테이블 지정
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private List<Course> courses;
}
Course Entity
@Entity
@Table(name="courses")
@NoargsConstructor
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String department;
private String semester;
private String professor;
...
@ManyToMany(mappedBy="courses") // N:M 관계 명시
private List<Student> students;
}
[JPA] 연관관계 매핑 주인에 대해서 (mappedBy)
TIL-15 JPA Entity 연관관계 1대1 관계
+기타 수업 자료