N대M관계(@ManyToMany)

Sunghun Kim·2024년 11월 5일

Jpa

목록 보기
8/10

@ManyToMany

  • @ManyToMany 애너테이션은 N 대 M 관계를 맺어주는 역할을 합니다.
  • 음식 Entity와 고객 Entity가 N 대 M 관계라 가정하여 관계를 맺어보겠습니다.

단방향 관계

DB 사진

  • N : M 관계를 풀어내기 위해 중간 테이블(orders)을 생성하여 사용합니다.

    • 음식 Entity가 외래 키의 주인
    • 음식
       @Entity
       @Table(name = "food")
       public class Food {
           @Id
           @GeneratedValue(strategy = GenerationType.IDENTITY)
           private Long id;
           private String name;
           private double price;
       
           @ManyToMany
           @JoinTable(name = "orders", // 중간 테이블 생성
           joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
           inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
           private List<User> userList = new ArrayList<>();
       }
      • 고객
        @Entity
        @Table(name = "users")
        public class User {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private Long id;
            private String name;
        }
    • 생성되는 중간 테이블을 컨트롤하기 어렵기 때문에 추후에 중간 테이블의 변경이 발생할 경우 문제가 발생할 가능성이 있습니다.

양방향 관계

DB 사진

  • 음식 Entity가 외래 키의 주인

    • 음식
       @Entity
       @Table(name = "food")
       public class Food {
           @Id
           @GeneratedValue(strategy = GenerationType.IDENTITY)
           private Long id;
           private String name;
           private double price;
       
           @ManyToMany
           @JoinTable(name = "orders", // 중간 테이블 생성
           joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
           inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
           private List<User> userList = new ArrayList<>();
       }
    • 고객
      @Entity
      @Table(name = "users")
      public class User {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          private Long id;
          private String name;
      
          @ManyToMany(mappedBy = "userList")
          private List<Food> foodList = new ArrayList<>();
      }
    • 반대 방향인 고객 Entity에 @ManyToMany 로 음식 Entity를 연결하고 mappedBy 옵션을 설정하여 외래 키의 주인을 설정하면 양방향 관계 맺음이 가능합니다.

양방향 관계 외래키 저장 Test

orders 테이블에 외래키 저장 실패

@Test
    @Rollback(value = false)
    @DisplayName("N대M 양방향 테스트 : 외래 키 저장 실패")
    void test2() {

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

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

        // 외래 키의 주인이 아닌 User 에서 Food 를 저장해보겠습니다.
        User user = new User();
        user.setName("Robbie");
        // 외래키 설정을 안하니 데이터베이스에 전혀 영향을 끼치지 않는다.
        user.getFoodList().add(food);
        user.getFoodList().add(food2);

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

        // 확인해 보시면 orders 테이블에 food_id, user_id 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
    }

addFootList메서드 User Entity에 생성

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

    @ManyToMany(mappedBy = "userList")
    private List<Food> foodList = new ArrayList<>();

    public void addFoodList(Food food) {
        this.foodList.add(food);
        food.getUserList().add(this); // 외래 키(연관 관계) 설정
    }

그 후 Test → 외래키 저장 성공

@Test
    @Rollback(value = false)
    @DisplayName("N대M 양방향 테스트 : 외래 키 저장 실패 -> 성공")
    void test3() {

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

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

        // 외래 키의 주인이 아닌 User 에서 Food 를 쉽게 저장하기 위해 addFoodList() 메서드를 생성해서 사용합니다.
        // 외래 키(연관 관계) 설정을 위해 Food 에서 userList 를 호출해 user 객체 자신을 add 합니다.
        User user = new User();
        user.setName("Robbie");
        user.addFoodList(food);
        user.addFoodList(food2);

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

양방향 관계 저장 후 조회 Test

Food에만 외래 키 설정, User는 X

@Test
    @Rollback(value = false)
    @DisplayName("N대M 양방향 테스트")
    void test4() {

        User user = new User();
        user.setName("Robbie");

        User user2 = new User();
        user2.setName("Robbert");

        Food food = new Food();
        food.setName("아보카도 피자");
        food.setPrice(50000);
        food.getUserList().add(user); // 외래 키(연관 관계) 설정
        food.getUserList().add(user2); // 외래 키(연관 관계) 설정

        Food food2 = new Food();
        food2.setName("고구마 피자");
        food2.setPrice(30000);
        food2.getUserList().add(user); // 외래 키(연관 관계) 설정

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

        // User 를 통해 food 의 정보 조회
        System.out.println("user.getName() = " + user.getName());
				
				// 외래키 주인이 아닌 user 객체에 food 정보를 넣어주지 않아서 
				// 데이터베이스에 전혀 영향을 끼치지 않는다.
        List<Food> foodList = user.getFoodList();
        for (Food f : foodList) {
            System.out.println("f.getName() = " + f.getName());
            System.out.println("f.getPrice() = " + f.getPrice());
        }

        // 외래 키의 주인이 아닌 User 객체에 Food 의 정보를 넣어주지 않아도 DB 저장에는 문제가 없지만
        // 이처럼 User 를 사용하여 food 의 정보를 조회할 수는 없습니다.
    }

    

Food Entity 메서드 추가

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

    @ManyToMany
    @JoinTable(name = "orders", // 중간 테이블 생성
            joinColumns = @JoinColumn(name = "food_id"), // 현재 위치인 Food Entity 에서 중간 테이블로 조인할 컬럼 설정
            inverseJoinColumns = @JoinColumn(name = "user_id")) // 반대 위치인 User Entity 에서 중간 테이블로 조인할 컬럼 설정
    private List<User> userList = new ArrayList<>();

    public void addUserList(User user) {
        this.userList.add(user); // 외래 키(연관 관계) 설정
        user.getFoodList().add(this); // 이 코드는 객체지향의 좋은점을 보여줌
    }
}

메서드 안에서 외래 키 설정을 해 조회 성공

@Test
    @Rollback(value = false)
    @DisplayName("N대M 양방향 테스트 : 객체와 양방향의 장점 활용")
    void test5() {

        User user = new User();
        user.setName("Robbie");

        User user2 = new User();
        user2.setName("Robbert");

        // addUserList() 메서드를 생성해 food클래스에 user 정보를 추가(외래키 설정)
        // 해당 메서드에 객체 활용을 위해 user 객체에 food 정보를 추가하는 코드를 추가합니다. user.getFoodList().add(this);
        Food food = new Food();
        food.setName("아보카도 피자");
        food.setPrice(50000);
        food.addUserList(user); // 객체 지향 최대한 활용
        food.addUserList(user2);

        Food food2 = new Food();
        food2.setName("고구마 피자");
        food2.setPrice(30000);
        food2.addUserList(user);

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

        // User 를 통해 food 의 정보 조회
        System.out.println("user.getName() = " + user.getName());

        List<Food> foodList = user.getFoodList();
        for (Food f : foodList) {
            System.out.println("f.getName() = " + f.getName());
            System.out.println("f.getPrice() = " + f.getPrice());
        }
    }

중간 테이블 직접 생성해서 관리

DB 사진

  • 중간 테이블 orders를 직접 생성하여 관리하면 변경 발생 시 컨트롤하기 쉽기 때문에 확장성에 좋습니다.

  • Food 테이블에서 Order 테이블을 통해 User를 구해올 필요가 없으면 Food 테이블에 관계를 맺을 필요가 없고

    반대로 User테이블에서 Order 테이블을 통해 Food를 구해올 필요가 없으면 User테이블에 관계를 맺을 필요가 없다.

  • 지금은 양쪽다 조회할 수 있도록 양방향으로 걸려있는 것이다.

  • 음식

    @Entity
    @Table(name = "food")
    public class Food {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
        private double price;
    
        @OneToMany(mappedBy = "food")
        private List<Order> orderList = new ArrayList<>();
    }
  • 고객

    @Entity
    @Table(name = "users")
    public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
    
        @OneToMany(mappedBy = "user")
        private List<Order> orderList = new ArrayList<>();
    }
  • 주문

    @Entity
    @Table(name = "orders")
    public class Order {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        @ManyToOne
        @JoinColumn(name = "food_id")
        private Food food;
    
        @ManyToOne
        @JoinColumn(name = "user_id")
        private User user;
    }
profile
BackEnd Developer!!

0개의 댓글