# 다대다

* @ManyToMany
  * 실무에서는 사용하지 않는다.
  * 관계형 데이터베이스는 정규화된 테이블 2개라 다대다 관계를 표현할 수 없기 때문이다.
* 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 한다.

![](https://389280719-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LxjHkZu4T9MzJ5fEMNe%2Fsync%2Fcd8cde454907d19571397952d8199f3b4be00fcd.png?generation=1616223195811523\&alt=media)

* DB는 테이블로 다대다를 만들 수 없기 때문에 중간에 member\_product 테이블을 만들어 풀어낸다.

![](https://389280719-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LxjHkZu4T9MzJ5fEMNe%2Fsync%2F8a06691878766f3b51aa8a7b920bde173e84e1c9.png?generation=1616223193829668\&alt=media)

* 객체는 컬렉션을 사용해 객체 2개로 다대다 관계를 표현할 수 있다.
* member가 List를, Product가 List를 가질 수 있다.

## 단방향 매핑

{% tabs %}
{% tab title="Member.java" %}
```java
@Entity
public class Member {

    ...

    @ManyToMany
    // @JoinTable에 연결 테이블의 이름을 적어준다.
    // 그럼 알아서 member_id와 product_id를 pk, fk로 갖는다.
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<Product> products = new ArrayList<>();
}
```
{% endtab %}

{% tab title="Product.java" %}
```java
@Entity
public class Product {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
}
```
{% endtab %}
{% endtabs %}

![](https://389280719-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LxjHkZu4T9MzJ5fEMNe%2Fsync%2F0e73113347c39d705e639380bf8b43fa07331d00.png?generation=1616223192667816\&alt=media)

`MEMBER_PRODUCT`라는 테이블이 자동으로 생성되었다.

## 양방향 매핑

{% tabs %}
{% tab title="Member.java" %}
```java
@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<Product> products = new ArrayList<>();
}
```
{% endtab %}

{% tab title="Product.java" %}
```java
@Entity
public class Product {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    // 일반적인 양방향 매핑과 같이 mappedBy를 해준다.
    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();
}
```
{% endtab %}
{% endtabs %}

* members.products가 연관 관계의 주인임을 mappedBy로 표현한다.

## 한계

![](https://389280719-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LxjHkZu4T9MzJ5fEMNe%2Fsync%2Ff4412e2777e5358e43add54f630f3ac4e7089a4f.png?generation=1616223194713234\&alt=media)

* 편해 보이지만 실무에서 쓰면 안된다.
  * 실무에서는 연결 테이블이 단순이 연결하는 기능만 하지 않는다.
    * 주문 시간, 수량 같은 추가적인 데이터가 필요해도 위의 그림처럼 테이블에 데이터를 추가할 수 없다.
  * 예상치 못한 쿼리가 나갈 수 있다.
    * 중간 테이블이과 조인해서 나가야 하는데, 중간 테이블이 숨겨져 있기 때문에 내가 모르는 쿼리가 나가서 문제가 생길 수 있다.

## 극복 방법

* 연결용 테이블 Entity를 추가한다.
* 즉, 연결 테이블을 Entity로 승격하고 일대다, 다대일로 풀어낸다.

{% tabs %}
{% tab title="Member.java" %}
```java
@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProducts = new ArrayList<>();
}
```
{% endtab %}

{% tab title="MemberProduct.java" %}
```java
// 중간 테이블
@Entity
public class MemberProduct {
    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;

    private int orderAmount;
    private LocalDateTime orderDateTime;
}
```
{% endtab %}

{% tab title="Product.java" %}
```java
@Entity
public class Product {
    @Id
    @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "product")
    private List<MemberProduct> memberProducts = new ArrayList<>();
}
```
{% endtab %}
{% endtabs %}

![](https://389280719-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LxjHkZu4T9MzJ5fEMNe%2Fsync%2Ff315432a6d174bd60f559b63ba67ebb2f31d4997.png?generation=1616223197937926\&alt=media)

* 중간 테이블을 연관 관계의 주인으로 두고 푼다.
* 중간 테이블의 이름은 Orders 등 의미있는 이름으로 변경해도 된다.

![](https://389280719-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LxjHkZu4T9MzJ5fEMNe%2Fsync%2Ff4412e2777e5358e43add54f630f3ac4e7089a4f.png?generation=1616223194713234\&alt=media)

* FK인 member\_id와 product\_id 둘을 묶어 PK가 되게 잡을 수도 있다.
* 하지만 웬만하면 PK는 자동 생성 되는 의미 없는 값을 써야 유연성이 생긴다.
  * id가 어디에 종속되면 걸림돌이 되는 일이 많다.
