@ManyToMany@ManyToMany 애너테이션은 N 대 M 관계를 맺어주는 역할을 합니다.
N : M 관계를 풀어내기 위해 중간 테이블(orders)을 생성하여 사용합니다.
@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;
}
음식 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<>();
}@ManyToMany 로 음식 Entity를 연결하고 mappedBy 옵션을 설정하여 외래 키의 주인을 설정하면 양방향 관계 맺음이 가능합니다.@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 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
}
@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
@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
@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 의 정보를 조회할 수는 없습니다.
}
@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());
}
}

중간 테이블 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;
}