일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- HttpSecurity
- 스프링 #스프링 시큐리티 #spring security
- 스프링시큐리티
- Session1이 그 모든 클라이언트의 저올
- WebConfigurerAdapter
- securityconfig
- Spring Security
- WebSecurity
- ㅇㅇㅇㄴㅇ
- ㅂ
- Today
- Total
다오의 개발일지
TIL-22 스프링 연관관계 N:M 관계 1번째 방법 본문
JPA 연관관계 맵핑에서 N:M 관계인 경우 2가지 방법으로 만들 수 있다.
1번째 방법의 단방향, 양방향, 테스트를 먼저 해보자
N:M 관계의 단방향
예로 users테이블과 food 테이블이 있다고 하자 food테이블을 외래키의 주인으로 둔다.
이런 경우 주문(orders)이라는 테이블을 만들어 N:M을 1:N 1:N상태로 만든다.
@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<>();
}
위의 코드블럭은 food entity이다. food entity는 외래키의 주인이므로 여기서 orders테이블을 만들 수가 있다.
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
위의 코드블럭은 users entity이다 단방향이므로 users테이블에는 추가로 할 작업이 없다.
하지만.. 이렇게 작성하는 경우 생성되는 중간 테이블을 컨트롤하기 어렵기 때문에
추후에 중간 테이블의 변경이 발생할 경우 문제가 발생할 가능성이 있다.
다른 또 하나의 방법을 알아보기 전에 양방향도 한번 알아보자
food entity클래스는 위의 코드와 똑같으므로 생략.
@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<>();
}
위의 코드블럭은 users entity이다 양방향이므로 mappedBy가 외래키의 주인 필드명으로 설정되어있다.
반대 방향인 고객 Entity에 @ManyToMany 로 음식 Entity를 연결하고 mappedBy 옵션을 설정하여 외래 키의 주인을 설정하면 양방향 관계 맺음이 가능하다.
단방향 테스트
@Test
@Rollback(value = false)
@DisplayName("N대M 단방향 테스트")
void test1() {
User user = new User();
user.setName("Robbie");
User user2 = new User();
user2.setName("Robbert");
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);
// 자동으로 중간 테이블 orders 가 create 되고 insert 됨을 확인할 수 있습니다.
}
위의 테스트는 유저 2명이 음식 1개를 주문하는 코드이다. 자동으로 orders테이블이 만들어지고 insert된다.
양방향 테스트
@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 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
}
위의 코드블럭은 N:M양방향 테스트중 외래키의 주인이 아닌 user 테이블에서 음식을 추가하는 경우이다.
외래키의 주인이 아닌경우에는 수정 및 삭제가 불가능하다 오직 읽기만 가능하다.
@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);
}
외래키의 주인이 아니지만 food를 저장하고싶을 때는 addFoodList()란 메서드를 정의하여 추가할 수 있다.
user entity 내부
public void addFoodList(Food food) {
this.foodList.add(food);
food.getUserList().add(this); // 외래 키(연관 관계) 설정
}
@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());
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 의 정보를 조회할 수는 없습니다.
}
user 객체에 food 정보를 넣어주지 않았기 때문에 아래부분의 for문은 출력이 되지 않는다.
@Test
@Rollback(value = false)
@DisplayName("N대M 양방향 테스트 : 객체와 양방향의 장점 활용")
void test5() {
User user = new User();
user.setName("Robbie");
User user2 = new User();
user2.setName("Robbert");
// addUserList() 메서드를 생성해 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());
}
}
food entity 내부에 addUserList() 메서드를 추가해 user 정보를 추가한다.
public void addUserList(User user) {
this.userList.add(user); // 외래 키(연관 관계) 설정
user.getFoodList().add(this);
}
아까랑 다르게 for문은 온전히 출력이 된다.
조회 테스트
@Test
@DisplayName("N대M 조회 : Food 기준 user 정보 조회")
void test6() {
Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
// 음식 정보 조회
System.out.println("food.getName() = " + food.getName());
// 음식을 주문한 고객 정보 조회
List<User> userList = food.getUserList();
for (User user : userList) {
System.out.println("user.getName() = " + user.getName());
}
}
@Test
@DisplayName("N대M 조회 : User 기준 food 정보 조회")
void test7() {
User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
// 고객 정보 조회
System.out.println("user.getName() = " + user.getName());
// 해당 고객이 주문한 음식 정보 조회
List<Food> foodList = user.getFoodList();
for (Food food : foodList) {
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
}
'WTIL' 카테고리의 다른 글
TIL-24 테스트에 필요한 어노테이션 (0) | 2023.07.17 |
---|---|
TIL-23 스프링 연관관계 N:M 관계 2번째 방법 중간테이블 직접 생성 (0) | 2023.07.13 |
TIL-21 스프링 특강 5회차 개념 0703 (0) | 2023.07.03 |
WIL-6 7주차 WIL (0) | 2023.07.03 |
TIL-20 JPA, Entity, 영속성 컨텍스트, 트랜잭션 (0) | 2023.07.02 |