Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 스프링 부트 기능
- REST API 규칙
- spring서버
- jwt메서드
- Spring Spring boot 차이
- 인텔리제이
- 최종 프로젝트
- JoinColumn
- Unsatisfied dependency
- git
- queryDSL
- github
- Q 클래스
- 1차캐시
- json
- 빈생성안됨
- @IdClass
- ERD 작성
- JPA주의사항
- 스프링부트오류
- jpa회원가입
- Error creating bean with name
- uncheck Exception
- 스프링 부트 공식 문서
- 복합키
- Filter
- jpa에러
- JPA
- REST란
- json gson 차이
Archives
- Today
- Total
Everyday Dev System
@ManyToMany 양방향 관계 이해하기 본문
중간에 생긴 orders 테이블에 PK도 없고 직접 생성한 것이 아니라 자동으로 생긴 테이블이므로
추가 변경 사항이 발생할 경우 관리하기가 힘들다는 단점이 있다.
위의 단점을 해결할 수 있는 방법
다음 링크 참조:
단방향 이전 글 참조:
2023.06.21 - [내배캠 주요 학습/Spring 심화] - @ManyToMany 단방향 관계 이해하기
N : M 관계 양방향 전체 Code :
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@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);
}
}
package com.sparta.jpaadvance.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@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); // 외래 키(연관 관계) 설정
}
}
package com.sparta.jpaadvance.relation;
import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@SpringBootTest
public class ManyToManyTest {
@Autowired
UserRepository userRepository;
@Autowired
FoodRepository foodRepository;
@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 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
}
@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());
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 의 정보를 조회할 수는 없습니다.
}
@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.java 코드는 단방향 코드와 같으나, addUserList(User user) 메서드가 추가되었다.
@ManyToMany(mappedBy = "userList")
User.java에서는 양방향이므로 FK 주인 Entity를 위 코드를 통해 설정한다.
private List<Food> foodList = new ArrayList<>();
private List<User> userList = new ArrayList<>();
N : M 관계이므로 Food.java와 User.java 모두 상대 타입의 List를 필드로 갖습니다.
Food.java
@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);
}
User.java
@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 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 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
}
해당 코드는 orders Table에 insert가 실행되지 않습니다.
해결 방법 :
아래 테스트 코드로 하면 orders Table에 insert가 잘 실행된다.
@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);
}
Hibernate:
/* insert for
com.sparta.jpaadvance.entity.User */insert
into
users (name)
values
(?)
Hibernate:
/* insert for
com.sparta.jpaadvance.entity.Food */insert
into
food (name,price)
values
(?,?)
Hibernate:
/* insert for
com.sparta.jpaadvance.entity.Food */insert
into
food (name,price)
values
(?,?)
Hibernate:
/* insert for
com.sparta.jpaadvance.entity.Food.userList */insert
into
orders (food_id,user_id)
values
(?,?)
Hibernate:
/* insert for
com.sparta.jpaadvance.entity.Food.userList */insert
into
orders (food_id,user_id)
values
(?,?)
조회하기 :
1. Food 기준 User 정보 조회
@Test
@DisplayName("N대M 조회 : Food 기준 user 정보 조회")
void tests() {
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.getId() = " + user.getId());
System.out.println("user.getName() = " + user.getName());
}
}
food 객체를 생성후 DB에서 해당 id에 따른 food 데이터를 가져온다.
가져온 food 데이터의 user 데이터를 가져온다.
List의 형태로 가져온 user 데이터를 for()문을 통해 출력한다.
Hibernate:
select
f1_0.id,
f1_0.name,
f1_0.price
from
food f1_0
where
f1_0.id=?
food.getName() = 아보카도 피자
Hibernate:
select
u1_0.food_id,
u1_1.id,
u1_1.name
from
orders u1_0
join
users u1_1
on u1_1.id=u1_0.user_id
where
u1_0.food_id=?
user.getId() = 1
user.getName() = Robbie
user.getId() = 2
user.getName() = Robbert
2. User 기준 Food 정보 조회
@Test
@DisplayName("N대M 조회 : user 기준 Food 정보 조회")
void tests() {
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.getId() = " + food.getId());
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
}
}
user 객체를 생성후 DB에서 해당 id에 따른 user 데이터를 가져온다.
가져온 user 데이터의 food 데이터를 가져온다.
List의 형태로 가져온 food 데이터를 for()문을 통해 출력한다.
Hibernate:
select
u1_0.id,
u1_0.name
from
users u1_0
where
u1_0.id=?
user.getName() = Robbie
Hibernate:
select
f1_0.user_id,
f1_1.id,
f1_1.name,
f1_1.price
from
orders f1_0
join
food f1_1
on f1_1.id=f1_0.food_id
where
f1_0.user_id=?
food.getId() = 1
food.getName() = 아보카도 피자
food.getPrice() = 50000.0
food.getId() = 2
food.getName() = 고구마 피자
food.getPrice() = 30000.0
'내배캠 주요 학습 > Spring 숙련' 카테고리의 다른 글
My Select Shop 프로젝트 초기 설정 (0) | 2023.06.22 |
---|---|
N : M 양방향 관계에서 중간 테이블 직접 생성 (1) | 2023.06.21 |
@ManyToMany 단방향 관계 이해하기 (0) | 2023.06.21 |
@OneToMany 단방향 (0) | 2023.06.21 |
@ManyToOne 활용하기 (0) | 2023.06.21 |