본문 바로가기
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

    댓글