본문 바로가기
해피 코딩/Today I Learned

[TIL 8] @ManyToOne, @OneToMany 정복하기

by happy-coding 2024. 8. 10.

Today I Learned

🔥 오늘은 @ManyToOne과 @OneToMany를 공부하고, 간단한 사용 코드 예시도 함께 보며 연관 관계를 정복하자!!

[ @ManyToOne ]

예시를 위해 음식 Entity와 고객 Entity가 N 대 1 관계라 가정하여 관계를 맺어보겠습니다.

 

  • @ManyToOne
    • @ManyToOne 애너테이션은 N 대 1 관계를 맺어주는 역할을 합니다.

[ 단방향 관계 ]

어제 TIL에서도 강요했지만 연관 관계는 외래 키 개념만 확실히 잡게 되면 이해하는 것은 어렵지 않다!
  • 외래 키의 주인 정하기
    • 중요❗ - 외래 키 주인만이 외래 키 를 등록, 수정, 삭제할 수 있으며, 주인이 아닌 쪽은 오직 외래 키 읽기만 가능합니다.

 

음식 Entity가 N의 관계로 외래 키의 주인

음식과 고객의 N대 1 단방향 관계

  • 음식
    • @JoinColumn()은 외래 키의 주인이 활용하는 애너테이션입니다.
@Entity
@Table(name = "food")
public class Food {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;
  private double price;

  @ManyToOne
  @JoinColumn(name = "user_id")
  private User user;
}

 

  • 고객
@Entity
@Table(name = "users")
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;
}

[ @ManyToOne 단방향 테스트 ]

  • 테스트 코드
    1. @JoinColumn()을 사용하는 음식 테이블이 외래 키 주인임을 알 수 있습니다.
    2. 외래 키 주인만이 외래 키 를 등록, 수정, 삭제할 수 있습니다.
    3. 정리: 외래 키 주인인 음식 테이블에서 food.setUser(user)를 통하여 데이터를 등록할 수  있습니다.
@Test
@DisplayName("N대1 단방향 테스트")
void test1() {
  User user = new User();
  user.setName("병준");

  Food food = new Food();
  food.setName("후라이드 치킨");
  food.setPrice(15000);
  food.setUser(user); // 외래 키(연관 관계) 설정

  Food food2 = new Food();
  food2.setName("양념 치킨");
  food2.setPrice(20000);
  food2.setUser(user); // 외래 키(연관 관계) 설정

  userRepository.save(user);
  foodRepository.save(food);
  foodRepository.save(food2);
}

 

음식
food table
id name price user_id
1 후라이드 치킨 15000 1
2 양념 치킨 20000 1

 

고객
users table
id name
1 병준

[ 양방향 관계 ] 

  • 양방향 관계에서는 mappedBy 옵션을 사용하여 상대 Entity의 필드명을 참조하여 관계를 설정해 줍니다.
  • 고객 입장에서는 @OneToMany이기 때문에, Many 상태의 음식 객체 참조하기 위해 List<Food> 사용합니다.
음식 Entity가 N의 관계로 외래 키의 주인

음식과 고객의 N대 1 양방향 관계

  • 고객
@Entity
@Table(name = "users")
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;

  @OneToMany(mappedBy = "user")
  private List<Food> foodList = new ArrayList<>();

  public void addFoodList(Food food) {
    this.foodList.add(food);
    food.setUser(this); // 외래 키(연관 관계) 설정
  }
}
  • addFoodList: 외래 키의 주인이 아닌 User 객체에서 데이터를 저장하기 위한 방법 
    1. 외래 키의 주인이 아닌 User 에서 Food 를 저장하기 위해 addFoodList() 메서드를 생성합니다.
    2. 파라미터로 받아온 food를  this.foodList.add(food)를 통하여 foodList에 넣어줍니다.
    3. 받아온 파라미터를 보면 알 수 있듯이, food는 Food객체 임으로 외래 키의 주인 입니다.
    4. 외래 키의 주인은 데이터를 등록, 수정, 삭제할 수 있습니다.
    5.  food.setUser(this)를 통하여 데이터를 등록해 줍니다.
    6. 여기서 this는 User객체의 값 전체를 의미합니다.

[ 테스트 코드 ]

@Test
@DisplayName("N대1 양방향 테스트 : USER에서 외래 키 저장 성공")
void test2() {

  Food food = new Food();
  food.setName("후라이드 치킨");
  food.setPrice(15000);

  Food food2 = new Food();
  food2.setName("양념 치킨");
  food2.setPrice(20000);

  // 외래 키의 주인이 아닌 User 에서 Food 를 쉽게 저장하기 위해 addFoodList() 메서드 생성하고
  // 해당 메서드에 외래 키(연관 관계) 설정 food.setUser(this); 추가
  User user = new User();
  user.setName("병준");
  user.addFoodList(food);
  user.addFoodList(food2);

  userRepository.save(user);
  foodRepository.save(food);
  foodRepository.save(food2);
}

 

음식
food table
id name price user_id
1 후라이드 치킨  15000 1
2 양념 치킨 20000 1

 

고객
users table
id name
1 병준

 


[ 1 대 N 관계 ]

음식 Entity와 고객 Entity가 1 대 N 관계라 가정하여 관계를 맺어보겠습니다.
  • @OneToMany
    • @OneToMany 애너테이션은 1 대 N 관계를 맺어주는 역할을 합니다.
  • 1대 N 관계는 양방향이 존재하지 않습니다.
    • 1대 N관계는 @OneToMany 임으로, 상대 테이블에서 @ManyToOne(mappedBy ="name")을 사용하여 관계를 맺어줄꺼 같지만 @ManyToOne은 mappedBy를 지원하지 않습니다

[ 단방향 관계 ]

  • 중요
    • 외래 키를 관리하는 주인은 음식 Entity이지만 실제 외래 키는 고객 Entity가 가지고 있습니다.
    • 1 : N에서, N 관계의 테이블이 외래 키를 가질 수 있기 때문에 외래 키는 N 관계인 users 테이블에 외래 키 컬럼을 만들어 추가하지만 외래 키의 주인인 음식 Entity를 통해 관리합니다.
외래 키의 주인은 음식Entity 이지만 Database에는 고객 Entity에 외래 키 컬럼이 추가 되는 모습을 볼 수 있다.

 

음식과 고객의 1대 N 단방향 관계

  • 음식
@Entity
@Table(name = "food")
public class Food {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;
  private double price;

  @OneToMany
  @JoinColumn(name = "food_id") // users 테이블에 food_id 컬럼
  private List<User> userList = new ArrayList<>();
}

 

  • 고객
@Entity
@Table(name = "users")
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String name;
}

 

외래 키를 음식 Entity가 직접 가질 수 있다면 INSERT 발생 시 한번에 처리할 수 있지만 실제 DB에서 외래 키를 고객 테이블이 가지고 있기 때문에 추가적인 UPDATE가 발생된다는 단점이 존재합니다.

[ 테스트 코드 ]

@Test
@DisplayName("1대N 단방향 테스트")
void test1() {
  User user = new User();
  user.setName("병준");

  User user2 = new User();
  user2.setName("상우");

  Food food = new Food();
  food.setName("후라이드 치킨");
  food.setPrice(15000);
  food.getUserList().add(user); // 외래 키(연관 관계) 설정
  food.getUserList().add(user2); // 외래 키(연관 관계) 설정

  userRepository.save(user);
  userRepository.save(user2);
  foodRepository.save(food);

  // 추가적인 UPDATE 쿼리 발생을 확인할 수 있습니다.
}

 

음식
food table
id name price
1 후라이드 치킨 15000

 

고객
users table
id name food_id
1 병준 1
2 상우 1

 

  • 음식 Entity가 외래 키의 주인임으로, food.getUsersList().add(user)를 통하여 데이터를 저장합니다.
    • 이 과정에서 추가적인 UPDATE가 발생 합니다
  • Database에서는 고객 table에 외래 키 컬럼이 생기는 모습을 볼 수 있습니다. 

@ManyToOne, @OneToMany를 글로 정리하여 작성하다 보니, 글을 읽는 사람 입장에서는 오히려 헷갈릴 수도 있겠다는 생각이 들었다. 그치만! 개념만 익히면 생각보다 쉬운 주제이므로 자신감을 가지고 쉽게 생각하자!! 어렵게 생각하면 어렵게 생각할수록 어려워진다!!

 

읽어주셔서 감사합니다 🙏

 

  • GitHub 커밋 확인하기

😺 GitHub: https://github.com/mad-cost/Sparta-EntityRelationship