본문 바로가기
개인 공부/JPA (자바 ORM 표준 JPA 프로그래밍)

[자바 ORM 표준 JPA 프로그래밍] 다양한 연관관계 매핑 (6장)

by 희조당 2022. 12. 2.
728x90


📌 다대일

다대일 관계에서 외래 키는 항상 다쪽에 존재한다. 즉, 연관관계의 주인은 항상 다쪽이다.

1️⃣ 다대일 단방향 [N:1]

회원은 Member.team으로 팀 엔티티를 참조할 수 있지만 반대로는 참조할 수 없다.

2️⃣ 다대일 양방향 [N:1, 1:N]

다대일 양방향에서 핵심은 다음과 같다.

  • 양방향은 외래 키가 있는 쪽이 연관관계의 주인이다.
  • 양방향 관계는 항상 서로를 참조해야 한다.

📌 일대다

다대일 관계의 반대 방향이다. 보통 엔티티를 하나 이상 참조할 수 있으므로 자바 컬렉션을 사용한다.

1️⃣ 일대다 단방향 [1:N]

보통 자신이 매핑한 테이블의 외래 키를 관리하는데 Team.members로 회원 테이블의 TEAM_ID 외래 키를 관리한다.

이 경우 mappedBy 속성을 사용하지 않고 @JoinColumn을 명시해야 한다.

 

외래 키가 다른 테이블에 있어 연관관계 처리 시 UPDATE SQL을 추가로 실행해야 한다.

따라서, 일대다 단방향 매핑보다 다대일 양방향 매핑을 권장한다.

2️⃣ 일대다 양방향 [1:N, N:1]

사실 다대일 양방향, 일대다 양방향 매핑은 같은 말이다. 따라서 없는 표현이다.


📌 일대일 [1:1]

일대일 관계에서는 어느 곳에서도 외래 키를 가질 수 있다. 따라서 누가 외래 키를 가질지 선택해야 한다.

1️⃣ 주 테이블에 외래 키

객체 참조와 비슷하게 사용할 수 있다는 점에서 선호되는 방식이다.

 

🧷 단방향

@Entity
public class Member {
  ...
  @OneToOne
  @JoinColumn(name = "LOCKER_ID")
  private Locker lcoker;
  ...
}

@Entity
public class Locker {
  ...
  private String name;
}

🧷 양방향

@Entity
public class Member {
  ...
  @OneToOne
  @JoinColumn(name = "LOCKER_ID")
  private Locker locker;
  ...
}

@Entity
public class Locker {
  ...
  @OneToOne(mappedBy = "locker")
  private Member member;
  ...
}

양방향이므로 Locker 엔티티에 mappedBy 속성을 사용해서 연관관계의 주인이 아니라고 설정했다.

2️⃣ 대상 테이블에 외래 키

전통적인 데이터베이스 개발자가 선호하는 방식이다. 일대다로 넘어가기 편리하다는 장점이 있다.

 

🧷 단방향 : 허용 ❌

 

🧷 양방향

@Entity
public class Member {
  ...
  @OneToOne(mappedBy = "member")
  private Locker locker;
}

@Entity
public class Locker {
  ...
  @OneToOne
  @JoinColumn(name = "MEMBER_ID")
  private Member member;
}

📌 다대다 [N:N]

관계형 데이터베이스에서는 다대다 관계를 표현할 수 없다.

그래서 연결 테이블을 사용해서 일대다, 다대일 관계로 풀어서 표현한다.

반면에 객체는 다대다 관계를 만들 수 있다.

1️⃣ 다대다 : 단방향

@Entity
public class Member {
  ...
  @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 id;
}

회원 엔티티와 상품 엔티티를 @ManyToMany와 @JoinTable로 바로 매핑했다.

@JoinTable의 속성은 다음과 같다.

  • name : 연결 테이블 지정
  • joinColumns : 조인 칼럼 정보를 지정
  • inverseColumns :  반대편 조인 칼럼 정보를 지정

2️⃣ 다대다 : 양방향

@Entity
public class Product {
  ...
  @ManyToMany(mappedBy = "products") // 역방향 추가
  private List<Member> members;
}

다대다 매핑으로 역방향도 @ManyToMany를 사용한다. 연관관계의 주인이 아니므로 mappedBy 속성을 사용한다.

3️⃣ 다대다 : 매핑의 한계와 극복, 연결 엔티티 사용

@ManyToMany를 사용하면 편리하지만 실무에서 사용하기에는 한계가 존재한다.

한계를 보완하기 위해서 연결 엔티티를 추가해서 관리할 수 있다.

아래 코드를 보면 @Id와 @JoinColumn을 통해서 기본 키와 외래 키를 한 번에 매핑했다.

또한 @IdClass를 이용해서 복합 기본 키를 매핑했다.

@Entity
public class Member {
  // 역방향
  @OneToMany(mappedBy = "member")
  private List<MemberProduct> memberProducts;
  ...
}

@Entity
public class Product {
  ...
}

@Entity
@IdClass(MemberProductId.class)
public class MemberProduct {
  @Id
  @ManyToOne
  @JoinColumn(name = "MEMBER_ID")
  private Member member; // MemberProductId.member와 연결
  
  @Id
  @ManyToOne
  @JoinColumn(name = "PRODUCT_ID")
  private Product product; // MemberProductI.product와 연결
  
  private int orderAmount;
}

 

복합 키를 사용하기 위해서는 별도의 식별자 클래스를 만들어야 한다.

회원상품 식별자 클래스와 특징은 다음과 같다.

public class MemberProductId implements Serializable {
  private String member; // MemberProduct.member와 연결
  private String product; // MemberProduct.product와 연결
  
  // hashCode and equals
  @Override
  public boolean equals(Object o) {...}
  
  @Override
  public int hashCode() {...}
}
  • 복합 키는 별도의 식별자 클래스로 만들어야 하고 Serializable로 구현해야 한다.
  • equals와 hashCode 메서드를 구현해야 한다.
  • 기본 생성자가 있어야 한다.
  • 식별자 클래스는 public이다.

회원상품은 회원에서 기본 키를 받아서 자신의 기본 키로 사용함과 동시에 회원과의 관계를 위한 외래 키로 사용한다.

4️⃣ 다대다 : 새로운 기본 키 사용

데이터베이스에서 자동으로 생성해주는 대리 키를 사용하면 두 가지 장점이 있다.

  • 간편하고 비지니스에 의존하지 않는다.
  • ORM 매핑 시 복합 키를 만들 필요가 없다.

다음과 같이 대리 키를 사용하면 복합 키를 사용하는 것보다 매핑이 훨씬 단순하고 이해하기 쉬워진다.

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

 

5️⃣ 다대다 연관관계 정리

다대다 관계를 일대다 다대일 관계로 풀어내기 위해 식별자를 어떻게 구성할지 선택해야 한다.

단순하고 편리하다는 점에서 비식별 관계를 사용하자.

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

 

댓글