[Spring] 다대다 매핑

WOOK JONG KIM·2022년 11월 3일
0
post-thumbnail
post-custom-banner

실무에서는 거의 사용되지 않는 구성

다대다 연관관계 관점에서 보자면 한 종류의 상품이 여러 생산 업체에서 생산될 수 있고, 생산 업체 한 곳이 여러 상품을 생산

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


다대다 단방향 매핑

생산업체 엔터티

@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);
    }
}

리스트를 필드로 가지는 객체에서는 외래키를 가지지 않기 때문에 별도로 @JoinColumn은 설정하지 않음

producer 테이블

Product와 producer의 중간 테이블

중간테이블의 경우 product,producer 각각의 id값을 가져와 두 개의 외래키가 설정되어 있다

public interface producerRepository extends JpaRepository<Producer, Long> {
}
public 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);
    }
}

이 경우 리포지토리 사용하게 되면 매번 트랜잭션이 끊어져 생산업체 엔티티에서 상품 리스트를 가져오는 작업이 불가능
-> 이를 해소하기 위해 @Transactional 어노테이션을 통해 트랜잭션이 유지되도록 구성

public 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(super=BaseEntity(createdAt=2022-11-03T14:58:01.418620, updatedAt=2022-11-03T14:58:01.418620), number=1, name=동글펜, price=500, stock=1000)
Product(super=BaseEntity(createdAt=2022-11-03T14:58:01.439191, updatedAt=2022-11-03T14:58:01.439191), number=2, name=네모공책, price=100, stock=2000)]

테스트 데이터 생성시 product 테이블과 producer 테이블에 레코드가 추가되지만 보여지는 내용만으론 연관관계 설정 여부 확인 어렵
-> 중간테이블에 연관관계가 매핑되어 있음

@Transactional 붙이면 DB에 값이 반영 안되는데 이유를 알아보자...

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:

다대다 양방향 매핑

상품 엔티티에서 생산업체 엔티티 연관관게 설정

	@ManyToMany
    @ToString.Exclude
    private List<Producer> producers = new ArrayList<>();
    
    public void addProducer(Producer producer){
        this.producers.add(producer);
    }

필요에 따라 mappedBy 속성을 통해 주인 설정 가능

@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 :[Product(super=BaseEntity(createdAt=2022-11-03T15:31:45.983209, updatedAt=2022-11-03T15:31:45.983209), number=1, name=동글펜, price=500, stock=1000), Product(super=BaseEntity(createdAt=2022-11-03T15:31:46.005777, updatedAt=2022-11-03T15:31:46.005777), number=2, name=네모공책, price=100, stock=2000)]
producers :[Producer(super=BaseEntity(createdAt=2022-11-03T15:31:46.008166, updatedAt=2022-11-03T15:31:46.008166), id=1, code=null, name=flature), Producer(super=BaseEntity(createdAt=2022-11-03T15:31:46.010677, updatedAt=2022-11-03T15:31:46.010677), id=2, code=null, name=wikiBooks)]

다대다 연관관게 설정 시 중간 테이블을 통해 연관된 엔티티 값을 가져올 수 있음

다만 다대다 연관관계에서 중간 테이블이 생성되기 때문에 예기치 못한 쿼리가 생길 수 있다
-> 한계를 극복하기 위해선 중간 테이블 생성하는 대신 일대다 다대일로 연관관계를 맺을 수 있는 중간 엔티티로 승격시켜 JPA에서 관리할 수 있게 생성하는 것이 좋음

profile
Journey for Backend Developer
post-custom-banner

0개의 댓글