be_better

[Springboot] 연관관계 매핑 - 다대다 매핑 본문

Springboot/이론

[Springboot] 연관관계 매핑 - 다대다 매핑

NiceTry 2023. 11. 18. 17:27

목차

    다대다 매핑

    다대다(M:N) 연관관계는 실무에서 거의 사용하지 않는 구성이다. 다대다 연관관계를 상품과 생산업체의 예로 들자면 한 종류의 상품이 여러 생산업체를 통해 생산될 수 있고, 생산업체 한 곳이 여러 상품을 생산할 수도 있다.

     

    다대다 연관관계에서는 각 엔티티에서 서로를 리스트로 가지는 구조가 만들어진다. 이런 경우에는 교차 엔티티라고 부르는 중간 테이블을 생성해서 다대다 관계를 일대다 또는 다대일 관계로 해소한다.

     

    다대다 단방향 매핑

    생산업체 엔티티 Producer

    @Entity
    @Getter
    @Setter
    @NoArgsConstructor
    @ToString(callSuper = true)
    @EqualsAndHashCode(callSuper = true)
    @Table(name = "producer")
    public class Producer extends BaseEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
    
        private String code;
    
        private String name;
    
        @ManyToMany
        @ToString.Exclude
        private List<Product> products = new ArrayList<>();
    
        public void addProduct(Product product) {
            products.add(product);
        }
    }

    상품 엔티티에 적용한 것과 같이 다대다 연관관계는 @ManyToMany 어노테이션으로 설정한다. 리스트로 필드를 가지는 객체에서는 외래키를 가지지 않기 때문에 별도의 @JoinColumn을 설정하지 않아도 된다.

     

    데이터베이스 확인

    생산업체 테이블에는 별도의 외래키가 추가되지 않은 것과 중간 테이블이 생성된 것을 확인할 수 있다. 별도의 설정을 하지 않았다면 테이블은 producer_products라는 이름으로 설정되며, 만약 테이블의 이름을 관리하고 싶다면 @ManyToMany 어노테이션 아래에 @JoinTable(name = "이름")의 형식으로 어노테이션을 정의하면 된다.

     

    ProducerRepository Testcode
    @SpringBootTest
    class ProducerRepositoryTest {
    
        @Autowired
        ProducerRepository producerRepository;
    
        @Autowired
        ProductRepository productRepository;
    
        @Test
        @Transactional
        void relationshipTest() {
    
            Product product1 = saveProduct("동글펜", 500, 1000);
            Product product2 = saveProduct("네모 공책", 100, 2000);
            Product product3 = saveProduct("지우개", 152, 1234);
    
            Producer producer1 = saveProducer("flature");
            Producer producer2 = saveProducer("wikibooks");
    
            producer1.addProduct(product1);
            producer1.addProduct(product2);
    
            producer2.addProduct(product2);
            producer2.addProduct(product3);
    
            producerRepository.saveAll(Lists.newArrayList(producer1, producer2));
    
            System.out.println(producerRepository.findById(1L).get().getProducts());
    
        }
    
        private Product saveProduct(String name, Integer price, Integer stock) {
            Product product = new Product();
            product.setName(name);
            product.setPrice(price);
            product.setStock(stock);
    
            return productRepository.save(product);
        }
    
        private Producer saveProducer(String name) {
            Producer producer = new Producer();
            producer.setName(name);
    
            return producerRepository.save(producer);
        }
    
    }

     

    실행 결과

    이와 같이 테스트 데이터를 생성하면 다대다 연관관계 설정을 통해 생성된 중간 테이블에 연관관계 매핑이 되어 있기 때문에 product 테이블과 producer 테이블에 레코드가 추가되지만, 보여지는 내용만으로는 연관관계 설정 여부를 확인하기 어렵게 된다.

     

    다대다 양방향 매핑

    양방향 매핑을 위한 상품 엔티티 수정
    @ManyToMany
    @ToString.Exclude
    private List<Producer> producers = new ArrayList<>();
    
    public void addProducer(Producer producer) {
        this.producers.add(producer);
    }

    mappedBy 속성을 사용해 두 엔티티 간 연관관계의 주인을 설정해준다.

     

    Test
    @Test
    @Transactional
    void relationshipTest2() {
        Product product1 = saveProduct("동글펜", 500, 1000);
        Product product2 = saveProduct("네모 공책", 100, 2000);
        Product product3 = saveProduct("지우개", 152, 1234);
    
        Producer producer1 = saveProducer("flature");
        Producer producer2 = saveProducer("wikibooks");
    
        producer1.addProduct(product1);
        producer1.addProduct(product2);
        producer2.addProduct(product2);
        producer2.addProduct(product3);
    
        product1.addProducer(producer1);
        product2.addProducer(producer1);
        product2.addProducer(producer2);
        product3.addProducer(producer2);
    
        producerRepository.saveAll(Lists.newArrayList(producer1, producer2));
        productRepository.saveAll(Lists.newArrayList(product1, product2, product3));
    
        System.out.println("products : " + producerRepository.findById(1L).get().getProducts());
    
        System.out.println("producers : " + productRepository.findById(2L).get().getProducers());
    }

     

    실행 결과 products, producers 확인

     

    이렇게 다대다 연관관계를 설정하면 중간 테이블을 통해 연관된 엔티티의 값을 가져올 수 있다. 다만 다대다 연관관계에서는 중간 테이블이 생성되기 때문에 예기치 못한 쿼리가 생길 수도 있다. 즉, 관리하기 힘든 포인트가 발생한다는 문제가 있다.

     

    그렇기 때문에 이러한 다대다 연관관계의 한계를 극복하기 위해서는 중간 테이블을 생성하는 대신 일대다 다대일로 연관관계를 맺을 수 있는 중간 엔티티로 승격시켜 JPA에서 관리할 수 있게 생성하는 것이 좋다.