실무에서는 거의 사용되지 않는 구성
다대다 연관관계 관점에서 보자면 한 종류의 상품이 여러 생산 업체에서 생산될 수 있고, 생산 업체 한 곳이 여러 상품을 생산
다대다 연관관계에서는 각 엔티티에서 서로를 리스트로 가지는 구조가 만들어짐
-> 이 경우 교차 엔티티라고 부르는 중간 테이블을 생성해서 다대다 관계를 일대다 또는 다대일 관계로 해소
생산업체 엔터티
@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에서 관리할 수 있게 생성하는 것이 좋음