본문 바로가기

Backend/JPA

[JPA] 다대다 연관관계 매핑

728x90

개요

관계형 데이터베이스는 테이블 2개로 다대다 관계를 표현할 수 없다.

그래서 중간에 연결 테이블을 두고, 2개의 다대일 관계를 통해 다대다 관계를 표현한다.

위 연관관계를 어떻게 구현할까??

 

이전 기록에서 다대일 연관관계를 구현하는 방법에 대해서 살펴봤다.

 

[JPA] 다대일 연관관계 매핑

개요어플리케이션 내 객체는 다른 객체와 협력 관계를 통해 문제를 해결한다.해결하고자 하는 문제에 따라 객체들은 다양한 협력 관계를 가지며 이에 따라 연관관계도 다양하다.동일하게 엔티

choi-records.tistory.com

다대다 연관관계도 @ManyToMany라는 어노테이션을 지원하지만, 한계가 있어 쓰지 않고 중간 테이블 엔티티를 만들어 2개의 다대일 연관관계 매핑을 이용한다.

 

@ManyToMany 어노테이션이 어떤 한계를 가지고, 다대다 연관관계를 구체적으로 어떻게 구현하는지 살펴보자.

@ManyToMany

먼저 @ManyToMany 어노테이션을 이용해 구현해 보자.

Product.java

@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    public Product(final String name) {
        this.name = name;
    }

    protected Product() {

    }
}

Member.java

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    @ManyToMany
    @JoinTable(
        name = "MEMBER_PRODUCT",
        joinColumns = @JoinColumn(name = "MEMBER_ID"),
        inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")
    )
    private List<Product> products = new ArrayList<>();

    public Member(final String name) {
        this.name = name;
    }

    protected Member() {
    }
}

회원과 상품 엔티티를 @ManyToMany 어노테이션으로 매핑했다. ManyToMany의 속성을 살펴보자.

  • name : 연결 테이블 이름을 지정한다. 위 코드에선 MEMBER_PRODUCT 라고 지정했다.
  • joinColumns : 연결 테이블에서 현재 테이블의 PK를 가져갈 FK의 컬럼명을 지정한다. 위 코드에선 MEMBER_ID이다.
  • inverseJoinColumns : 반대 테이블인 상품의 PK를 가져갈 FK의 컬럼명을 지정한다. 위 코드에선 PRODUCT_ID이다.

연결 테이블이 어떻게 생성되는지 보자.

@ManyToMany 어노테이션 속성에 지정해 준 대로 연결 테이블이 생성됐다.

한계

@ManyToMany 어노테이션을 이용하면 따로 엔티티를 생성하지 않고 속성 지정만으로도 연결 테이블을 쉽게 생성할 수 있다.

하지만 연결 테이블이라는 것은 두 테이블의 다대다 관계를 설명하는 테이블이다.

따라서 대부분의 경우 그 테이블에 저장해야 할 추가 정보와 그 테이블 엔티티가 담당해야 할 책임이 따로 있다. 

 

위의 예시로 보면, 상품과 고객의 다대다 관계의 연결 테이블은 주문 테이블일 것이다. 주문에는 주문일자, 시간, 주문량 등 저장해야 할 데이터들이 많을 것이고, 주문 도메인이 담당해야 할 책임이 따로 존재할 것이다. 이런 이유로 별도의 연결 엔티티를 만들어 다대다 관계를 구현하는 것이 좋다.

 

연결 테이블 생성을 통한 다대다 연관관계

다대다 관계를 2개의 다대일 관계로 풀어내기 위해 연결 테이블을 생성할 때 연결 테이블의 식별자 구성에 2가지 방법이 있다.

  • 식별 관계 : 받아온 식별자를 기본 키 + 외래 키로 사용한다.
  • 비식별 관계 : 받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가한다.

두 방법 모두 연결 테이블인 Order 엔티티만 살펴보자. Member와 Product 엔티티에선 다대일 연관관계에서 일 쪽과 동일하다.

 

[JPA] 다대일 연관관계 매핑

개요어플리케이션 내 객체는 다른 객체와 협력 관계를 통해 문제를 해결한다.해결하고자 하는 문제에 따라 객체들은 다양한 협력 관계를 가지며 이에 따라 연관관계도 다양하다.동일하게 엔티

choi-records.tistory.com

식별 관계

Order.java

@Entity
@IdClass(OrderId.class)
public class Order {

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

    @Id
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    private int orderAmount;
    ...
}

OrderId.java

public class OrderId implements Serializable {
    
    private String member;
    private String product;

    @Override
    public boolean equals(final Object object) {
        if (this == object) {
            return true;
        }
        if (object == null || getClass() != object.getClass()) {
            return false;
        }
        OrderId orderId = (OrderId) object;
        return Objects.equals(member, orderId.member) && Objects.equals(product,
            orderId.product);
    }

    @Override
    public int hashCode() {
        return Objects.hash(member, product);
    }
}

연결 테이블 엔티티에서 기존과 다른 방식으로 Id를 생성한 것을 볼 수 있다.

PRODUCT_ID와 MEMBER_ID를 이용해 만든 복합 키를 사용한다. 이처럼 부모 테이블의 기본 키를 받아와 자신의 기본 키 + 외래 키로 사용하는 것을 식별 관계라고 한다.

 

복합 키를 위한 식별자 클래스, 위 코드에서 OrderId는 아래와 같은 특징이 있다.

  • 복합 키는 별도의 식별자 클래스로 만들어야 한다.
  • Serializable을 구현해야 한다.
  • 기본 생성자가 있어야 한다.
  • 식별자 클래스는 public이어야 한다.
  • equals와 hashCode를 구현해야 한다.

비식별 관계

두 테이블의 기본 키를 사용하지 않고, 연결 테이블 별도의 기본 키를 사용하는 방식이다.

Order.java

@Entity
public class Order {
	
    @Id @GeneratedValue
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
    
    private int orderAmount;
	...
}

앞서 봤던 식별 관계보다 훨씬 단순하고 이해하기 쉽다. 

다른 엔티티처럼 별도의 식별자를 가지기 때문에 Member와 Product의 식별자 없이 Order의 식별자만으로도 조회가 가능하다.

 

별도의 식별자 클래스를 만들지 않아도 되고 훨씬 단순하게 ORM 매핑을 할 수 있는 비식별 관계가 더 많이 이용된다.

마무리

잘못된 정보에 대한 지적과 피드백은 환영입니다. 감사합니다 🙇‍♂️

참고

 

자바 ORM 표준 JPA 프로그래밍 - 예스24

자바 ORM 표준 JPA는 SQL 작성 없이 객체를 데이터베이스에 직접 저장할 수 있게 도와주고, 객체와 관계형 데이터베이스의 차이도 중간에서 해결해준다. 이 책은 JPA 기초 이론과 핵심 원리, 그리고

www.yes24.com

 

728x90