STUDY/JPA

다양한 연관관계 매핑

Anne of Green Galbes 2020. 1. 30. 13:12

이 글은 김영한의 자바 ORM 표준 JPA 프로그래밍을 보고 작성한 글임을 알려드립니다.

관련 강의 : 인프런스 자바 ORM 표준 JPA 프로그래밍 - 기본편


1. 다대일

1) 단방향 [ N : 1 ]

 

2) 양방향 [ N : 1 ], [ 1 : N ]

  • 양방향은 외래 키가 있는 쪽이 연관관계의 주인이다.
    • 일대다와 다대일 연관관계는 항상 다(N)에 외래 키가 있다.
    • 여기서는 다쪽인 MEMBER 테이블이 외래 키를 가지고 있으므로 Member.team이 연관관계의 주인이다.
    • JPA는 외래 키를 관리할 때 연관관계의 주인만 사용한다.
    • 주인이 아닌쪽은 조회를 위한 jpql이나 객체 그래프 탐색할 때 사용한다.
  • 양방향 연관관계는 항상 서로를 참조해야 한다.
    • 양방향 연관관계는 항상 서로 참조해야 한다,
    • 한 쪽만 참조하면 양방향 연관관계가 성립되지 않는다.
    • 항상 서로 참조하게 하기 위해서는 연관관계 편의 메소드를 만드는 것이 좋다.

 

2. 일대다

1) 단방향 [ 1 : N ]

일대다 단방향은 일(1)이 연관관계의 주인이다.

테이블 일대다 관계는 다(N)쪽에 외래키가 잇따. 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조이다. 일대다 단방향 관계를 매핑할 때는 @JoinColumn을 꼭 사용해야 한다. 그렇지 않으면 JPA는 연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 정략을 기본으로 사용해서 매핑하기 때문이다.

  • 일대다 단방향 매핑의 단점
    • 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다
    • 본인 테이블에 외래 키가 있으면 엔티티의 저장과 연관관계 처리를 INSERT 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있어 연관관계 처리를 위한 UPDATE를 추가로 실행해야 한다.
  • 일대다 단방향 매핑보다는 다대일 양방향 메핑을 사용
    • 엔티티를 매핑한 테이블이 아닌 다른 테이블의 외래 키를 관리해야 한다.
    • 성능 문제뿐만 아니라 관리도 부담스러울 수 있다.

 

2) 양방향 [ 1 : N ], [ N : 1 ]

일대다 양방향 매핑은 존재하지 않는다. 대신 다대일 양방향 매핑을 사용한다.

@JoinColumn(insertable=false, updatable=false)로 설정을 하여 읽기 전용 필드를 사용해서 양방향처럼 사용하는 방법이다. 이런 매핑은 공식적으로 존재하지 않는다.

 

3. 일대일

일대일 관계는 양쪽이 서로 하나의 관계만 가진다. 예를들어 회원은 하나의 사물함만 이용 가능하고, 사물함도 하나의 회원에 의해서만 사용된다. 다음은 일대일 관계의 특징이다.

  • 일대일 관계는 그 반대도 일대일 관계이다.
  • 일대일 관계는 주 테이블이나 대상 테이블이나 둘 중 어느 곳이나 외래 키를 가질 수 있다.
  • 외래 키에 데이터베이스 유니크 제약조건이 추가되어야 한다.

주 테이블이든 대상 테이블이든 외래 키 하나만 있으면 양쪽으로 조회할 수 있다. 따라서 누가 외래 키를 가질지 정해야 한다.

  • 주 테이블에 외래 키 有
    • 주 객체가 대상 객체를 참조하는 것처럼 주 테이블에 외래 키를 두고 대상 테이블을 참조한다.
    • 외래 키를 참조와 비슷하게 사용할 수 있어 객체지향 개발자들이 선호한다.
    • 장점 : 주 테이블이 외래 키를 가지고 있으므로 주 테이블만 확인해도 대상 테이블과 연관관계가 있는지 알 수 있다.
    • 단점 : 값이 없으면 외래 키에 null값을 허용한다.
  • 대상 테이블에 외래 키 有
    • 전통적인 데이터베이스 개발자들이 선호하는 방법이다.
    • 장점 : 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있다.
    • 단점 : 프록세 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩된다.

1) 주 테이블에 외래 키

주 테이블에 외래 키가 있는 방식은 일대일 관계를 구성할 때 객체지향 개발바들이 선호하는 방식이다. JPA도 주 테이블에 외래키가 있으면 더 편하게 매핑을 할 수 있다. 이제부터 주 테이블에 외래키가 있는 단방향 관계와 양방향 관계를 알아보자.

 

① 단방향

MEMBER가 주 테이블이고 LOCKER가 대상 테이블이다. 회원은 하나의 Locker만 가지고, Locker는 하나의 회원에 의해서만 사용되는 경우이다. 일대일 연관관계는 @OneToOne 어노테이션을 사용하고, 이 관계는 다대일 단방향(@ManyToOne)과 거의 비슷하다.

 

② 양방향

 

양방향이므로 연관관계의 주인을 정해야한다. 여기서 주인은 외래 키가 있는 MEMBER테이블 이므로 Member.locker가 연관관계의 주인이 된다. 그러므로 Locker.member는 mappedBy를 사용해서 연관관계의 주인이 아니라고 설정해야 한다.

 

2) 대상 테이블에 외래 키

① 단방향

위처럼 일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 지원하지 않는다. 그리고 이런 모양으로 매핑할 수 있는 방법도 없다. 따라서 이 때는 단뱡향 관계를 Locker에서 Member 방향으로 수정하거나, 양방향 관계로 만들고 Locker를 양방향 관계의 주인으로 설정해야한다.

JPA 2.0부터 일대다 단방향 관계에서 대상 테이블에 외래 키가 있는 매핑을 허용했지만, 일대일 단방향을 이런 매핑을 허용하지 않는다.

 

② 양방향

일대일 매핑에서 대상테이블에 외래 키를 두고 싶으면 이처럼 양방향 관계로 매핑을 해야한다. 주 엔티티인 Member 엔티티 대신에 대상 엔티티인 Locker를 연관관계의 주인으로 만들어 LOCKER 테이블의 외래 키를 관리하도록 한다.

 

4. 다대다

관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 그래서 보통 다대다 관계를 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용한다. 다음 그림을 보자.

 

회원은 상품을 주문하고, 주상품은 회원들에 의해 주문되므로 둘은 다대다 관계이다. 따라서 회원 테이블과 상품 테이블만으로는 둘의 관계를 표현할 수 없다. 그래서 다음과 같이 연결 테이블을 추가한다.

이 연결 테이블을 사용해서 다대다 관계를 일대다, 다대일 관계로 풀어낼 수 있다.

 

그러나 객체는 테이블과 다르게 객체 3개로 다대다 관계를 만들 수 있다.

 

예를 들어 회원 객체는 컬렉션을 사용해서 상품들을 참조하면 되고, 상품들도 컬렉션을 이용해서 회원들을 참조하면 된다. @ManyToMany를 사용하면 이 처럼 편리하게 다대다 관계를 매핑할 수 있다.

 

1) 단방향

@Entity
public class Member {
    @Id @Column(name = "MEMBER_ID")
    private String id;
    
    private String username;
    
    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT",
               joinColumns = @JoinColumn(name = "MEMBER_ID"),
               inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
    private List<Product> products = new ArrayList<Product>();
}

@Entity
public class Product {
    @Id @Column(name = "PRODUCT_ID")
    private String productId;

    @Column
    private String name;
}

회원 엔티티와 상품 엔티티를 @ManyToMany 어노테이션으로 매핑했다. 여기서 중요한 점은 @ManyToMany와 @JoinTable을 사용해서 연결 테이블을 바로 매핑한 것이다. 따라서 회원과 상품을 연결하는 회원_상품(Member_Product) 엔티티 없이 매핑을 완료할 수 있다.

 

@JoinTable의 속성을 살펴보자.

속성 기능
name 연결 테이블을 지정한다.
joinColumns 현재 방향인 엔티티와 매핑할 조인 컬럼 정보를 지정한다.
inverseJoinColumns 반대 방향인 엔티티와 매핑할 조인 컬럼 정보를 지정한다.

 

2) 양방향

다대다 연관관계 단방향을 양방향으로 바꾸기 위해서는 역방향도 @ManyToMany 어노테이션을 추가하고 연관관계의 주인을 결정하는 mappedBy 속성을 추가해주면 된다. 여기서는 상품 클래스에 mappedBy속성을 추가해주면 된다.

@Entity
public class Product {
    @Id @Column(name = "PRODUCT_ID")
    private String productId;

    @Column
    private String name;
    
    @ManyToMany(mapperBy = "products")
    private List<Member> memebrs;
}

 

3) 매핑의 한계와 극복 : 연결 엔티티 사용

@ManyToMany 어노테이션을 사용한 다대다 매핑은 편리해 보이지만 실무에서 사용하기에는 무리가 있다. 다음 그림을 보자.

회원이 상품을 주문하면 연결테이블에는 단순히 회원 아이디와 상품 아이디만 담기는건 아니다. 주문 수량과 주문 날짜와 같은 추가적인 데이터들도 필요하다. 이렇게 칼럼을 추가하면 더이상 @ManyToMany 어노테이션을 사용할 수 없다. 주문 엔티티나 상품 엔티티에는 추가한 컬럼을 매핑할 수 없기 때문이다.

그래서 다음과 같이 연결 테이블을 추가하고 다대다 연결관계를 일대다, 다대일 연결관계로 바꿔줘야 한다.

 

복합 기본 키

JPA에서 복합 키를 사용하려면 별도의 식별자 클래스를 만들어야 한다. 그리고 엔티티에 @IdClass 어노테이션을 사용해서 식별자 클래스를 지정하면 된다. 
  • 복합키는 별도의 식별자 클래스로 만들어야 한다.
  • Serializable을 구현해야 한다.
  • equals와 hashCode 메소드를 구현해야 한다.
  • 기본 생성자가 있어야 한다.
  • 식별자 클래슨는 public이어야 한다.
  • @IdClass를 사용하는 방법 외에 @EmbeddeId를 사용하는 방법도 있다.