본문 바로가기
JPA

[JPA] 다대다 연관관계 (양방향)

by 2nyong 2023. 4. 29.

목차


테이블 설계

다대다 연관관계를 고객이 주문한 음식(Food) 고객(Member)을 예시로 설명하겠습니다.

  • 여러 가지의 음식(Food)은 여러 명의 고객(Member)에게 주문될 수 있다.
  • 음식(Food) : 고객(Member) = N : M

 

양방향 연관관계를 위한 추가 조건은 다음과 같습니다.

  • Food Entity는 Food.memberList 필드를 통해 음식을 주문한 고객 목록(List<Member)에 접근할 수 있다.
  • Member Entity는 Member.foodList 필드를 통해 고객이 주문한 음식 목록(List<Food>)에 접근할 수 있다.


Entity 관계 매핑

- Food Entity

  • 다대다 양방향 연관관계에서 외래키의 주인입니다.
@Entity
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<>();

    public void addMemberList(Member member) {
        this.memberList.add(member);
    }
    
    // constructor, getter, setter, ...
}

 

- Member Entity

@Entity
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "member")
    private List<Order> orderList = new ArrayList<>();
        
    // constructor, getter, setter, ...
}

 

- Orders Entity

  • 다대다 양방향 연관관계에서 연결 테이블입니다.
@Entity
public class Orders {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "food_id")
    private Food food;

    // constructor, getter, setter, ...
}

 

  • 다대다 양방향 매핑에서는 외래키의 주인(Food)에 해당하는 Entity에 연결(Orders) Entity를 Collection 프레임워크로 감싼 형태의 참조 필드로 작성합니다.
  • 이 때 참조 필드 위에 @OneToMany(mappedBy = "{연결(Orders) Entity가 참조하는 필드 이름}") 애노테이션을 추가로 작성합니다.
  • mappedBy 속성은 양방향 연관관계에서 사용하며, 이 연관관계에 대해 외래 키의 주인이 참조하고 있는 필드 명을 넣어줍니다.
  • 마찬가지로 외래키의 주인의 반대편(Member)에 해당하는 Entity에도 연결(Orders) Entity를 Collection 프레임워크로 감싼 형태의 참조 필드로 작성하며, @OneToMany(mappedBy = "{연결(Orders) Entity가 참조하는 필드 이름}")을 추가로 작성합니다.
  • 마지막으로 연결(Orders) Entity의 참조 필드 위에 각각 @ManyToOne 애노테이션과 @JoinColumn(name = "{foreign key name}") 애노테이션을 추가로 작성합니다.
  • 위의 Entity 관계를 적용하여 테이블을 생성하였을 때, 실행되는 SQL은  아래와 같습니다.
CREATE TABLE FOOD (
    ID BIGINT NOT NULL AUTO_INCREMENT,
    NAME VARCHAR(255),
    PRICE FLOAT(53) NOT NULL,
    PRIMARY KET (ID)
);

CREATE TABLE MEMBER (
    ID BIGINT NOT NULL AUTO_INCREMENT,
    NAME VARCHAR(255),
    PRIMARY KET (ID)
);

CREATE TABLE ORDERS (
    ID BIGINT NOT NULL AUTO_INCREMENT,
    FOOD_ID BIGINT NOT NULL,
    MEMBER_ID BIGINT NOT NULL
);

ALTER TABLE ORDERS
    ADD CONSTRAINT FK5g4j2r53ncoltplogbnqlpt30
    FOREIGN KEY (FOOD_ID)
    REFERENCES FOOD (ID)

ALTER TABLE ORDERS
    ADD CONSTRAINT FKpktxwhj3x9m4gth5ff6bkqgeb
    FOREIGN KEY (MEMBER_ID)
    REFERENCES MEMBER (ID)

Entity 관계 사용법

- 저장

@Transactional
@SpringBootTest
public class ManyToManyTest {

    @Autowired FoodRepository foodRepository;
    @Autowired MemberRepository memberRepository;
    @Autowired OrderRepository orderRepository;

    @Test
    @Rollback(value = false)
    @DisplayName("N대M 단방향 저장 테스트")
    void OneWaySaveTest() {
        // 고객(Member)
        Member member = new Member("Inyong");
        memberRepository.save(member);
        
        // 음식(Food)
        Food food = new Food("후라이드 치킨", 15000);
        foodRepository.save(food);

        // 주문(Order)
        Order order = new Order();
        order.setMember(member); // 외래 키(연관관계) 설정
        order.setFood(food); // 외래 키(연관관계) 설정
        orderRepository.save(order);
    }
}

 

  • 위 코드 실행 시, 적용되는 SQL은 다음과 같습니다.
INSERT INTO MEMBER (NAME) VALUES ('Inyong');
INSERT INTO FOOD (MEMBER_ID, NAME, PRICE) VALUES (NULL, "후라이드 치킨", 15000);
INSERT INTO ORDERS (FOOD_ID, MEMBER_ID) VALUES (1, 1);

 

- 조회

@Transactional
@SpringBootTest
public class ManyToManyTest {

    @Autowired FoodRepository foodRepository;
    @Autowired MemberRepository memberRepository;
    @Autowired OrderRepository orderRepository;

    @Test
    @DisplayName("N대M 양방향 조회 테스트")
    void TwoWayViewTest() {
        // 1번 주문 조회
        Order order = orderRepository.findById(1L).orElseThrow(NullPointerException::new);

        // order 객체를 사용하여 고객 정보 조회
        Member member = order.getMember();
        System.out.println("user.getName() = " + member.getName());

        // order 객체를 사용하여 음식 정보 조회
        Food food = order.getFood();
        System.out.println("food.getName() = " + food.getName());
        System.out.println("food.getPrice() = " + food.getPrice());
    }
}

// 실행 결과
// user.getName() = Inyong
// food.getName() = 후라이드 치킨
// food.getPrice() = 15000.0

 

  • 위 코드 실행 시, 적용되는 SQL은 다음과 같습니다.
SELECT o.ID, f.ID, f.NAME, f.PRICE, m.ID, m.NAME FROM ORDERS o
    LEFT JOIN FOOD f ON f.ID = o.FOOD_ID
    LEFT JOIN MEMBER m ON m.ID = o.MEMBER_ID
WHERE o.ID = 1

댓글