본문 바로가기
JPA

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

by 2nyong 2023. 4. 29.

목차


테이블 설계

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

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

 

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

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


Entity 관계 매핑

- Food Entity

  • 다대다 단방향 연관관계에서 외래키의 주인입니다.
@Entity
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"),
            inverseJoinColumns = @JoinColumn(name = "member_id"))
    private List<Member> memberList = 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;
        
    // constructor, getter, setter, ...
}

 

  • 다대다 단방향 매핑에서는 외래키의 주인에 해당하는 Entity에 반대편 Entity를 Collection 프레임워크로 감싼 형태의 참조 필드로 작성합니다.
  • 참조 필드 위에 @ManyToOne 애노테이션과 @JoinColumn 애노테이션을 추가로 작성합니다.
  • @JoinColumnname 속성은 다대다 관계를 풀어줄 연결 테이블의 이름을 지정합니다.
  • @JoinColumnjoinColumns 속성은 다대다 연관관계에서 외래키의 주인에 해당하는 외래키를 매핑합니다.
  • @JoinColumninverseJoinColumns 속성은 다대다 연관관계에서 외래키의 주인에 해당하지 않는 외래키를 매핑합니다.
  • 위의 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 (
    FOOD_ID BIGINT NOT NULL,
    MEMBER_ID BIGINT NOT NULL
);

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

 

- @ManyToMany의 한계점

  • @JoinColumn으로 인해 연결 테이블이 Orders 테이블이 생성되었습니다.
  • 자동 생성된 Orders 테이블은 food_id와 member_id만을 컬럼으로 가지고 있는데, 실제 중간 테이블을 활용하기 위해서는 주문 시각이나 주문 번호(order_id)등의 추가 정보가 필요할 수 있습니다.
  • 또, 연결 테이블인 orders 테이블은 숨겨진 테이블이기 때문에 예상할 수 없는 쿼리들이 실행될 수 있습니다.

 

- @ManyToMany의 한계점 해결 방법

  • 연결용 테이블의 Entity를 만들어 줍니다.
  • @ManyToMany를 @ManyToOne@OneToMany를 활용하여 일대다, 다대일 관계로 풀어줍니다.
  • 위 방식은 여기에서 설명하겠습니다.

Entity 관계 사용법

- 저장

@Transactional
@SpringBootTest
public class ManyToManyTest {

    @Autowired FoodRepository foodRepository;
    @Autowired MemberRepository memberRepository;

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

        // 고객(Member)1
        Member member1 = new Member("Inyong");
        memberRepository.save(member1);
        food.addMemberList(member1);

        // 고객(Member)1
        Member member2 = new Member("Robert");
        memberRepository.save(member2);
        food.addMemberList(member2);
    }
}

 

  • 위 코드 실행 시, 적용되는 SQL은 다음과 같습니다.
  • 외래 키를 받아 중간 테이블인 Orders에 INSERT SQL이 실행됩니다.
INSERT INTO FOOD (MEMBER_ID, NAME, PRICE) VALUES (NULL, "후라이드 치킨", 15000);
INSERT INTO MEMBER (NAME) VALUES ('Inyong');
INSERT INTO MEMBER (NAME) VALUES ('Robert');
INSERT INTO ORDERS (FOOD_ID, MEMBER_ID) VALUES (1, 1);
INSERT INTO ORDERS (FOOD_ID, MEMBER_ID) VALUES (1, 2);

댓글