자바 ORM 표준 JPA 프로그래밍 - 기본편 챕터 6 정리

정종일·2023년 5월 22일
0

Spring

목록 보기
11/18

다양한 연관관계 매핑

엔티티의 연관관계를 매핑할 때는 다중성, 관계방향, 주인 을 고려해야 한다

  1. 다중성
    • ManyToOne (N:1)
    • OneToMany (1:N)
    • OneToOne (1:1)
    • ManyToMany (N:N)
  2. 단방향 양방향
    • 테이블은 외래 키 하나로 Join을 사용해서 양방향으로 쿼리가 가능하여 방향이라는 개념이 존재하지 않는다
    • 객체관계는 참조용 필드를 가지고 있는 객체만 연관된 객체를 조회할 수 있다
  3. 연관관계의 주인
    • 객체 연관관계 중 하나를 정해서 데이터베이스 외래 키를 관리하는것
    • 외래키를 가진 테이블과 매핑한 엔티티가 외래키를 관리하는게 효율적
    • 주인이 아닌 방향은 외래키를 변경할 수 없고 읽기만 가능

1. 다대일


데이터베이스 테이블의 N:1 관계에서 외래키는 항상 'N' 쪽이다

다대일 단방향 [N:1]

N에는 참조하는 필드가 존재하고 1에는 참조하는 필드가 없는경우

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

  1. 양방향은 외래키가 있는 쪽이 연관관계의 주인
    • 일대다와 다대일 연관관계는 항상 다(N)에 외래 키가 존재
    • 주인은 외래 키를 관리하며 주인이 아닌쪽은 조회를 위한 JPQL이나 객체 그래프를 탐색할 때 사용
  2. 양방향 연관관계는 항상 서로를 참조해야 한다
    • 항상 서로 참조하려면 연관관계 편의 메소드를 작성하는 것이 좋다
    • 편의 메서드는 양쪽에 다 작성하면 무한루프에 빠지므로 주의해야한다

2. 일대다


일대다 관계는 엔티티를 하나이상 참조할 수 있으므로 자바 컬렉션인 Collection, List, Set, Map 중 하나를 사용해야 한다

일대다 단방향 [1:N]

일대다 단방향 관계는 약간 특이하다. 보통 자신이 매핑한 테이블의 외래키를 관리하는데, 이 매핑은 반대쪽 테이블에 있는 외래 키를 관리한다.

  1. 일대다 단방향 매핑의 단점
    • 위 처럼 관리하는 외래 키가 다른 테이블에 있다는 것
    • 다른 테이블에 외래키가 있으면 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 함
  2. 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자
    • 다른 테이블의 외래키를 관리해야하는 것은 성능의 문제도 있지만 관리도 부담스러움
    • 문제 해결 방법은 다대일 양방향 매핑을 사용하는 것
    • 다대일 양방향 매핑은 관리해야하는 외래키가 본인 테이블에 존재

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

일대다 양방향 매핑은 존재하지 않는다. 하지만 완전히 불가능한 것은 아니다.

매핑 반대편에 같은 외래 키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 하나 추가하면 된다

insertable = false, updatable = false 로 설정하면 읽기만 가능하다.

but ! 이는 일대다 양방형처럼 보이게 할 뿐 일대다 단방향 매핑이 가지는 단점을 그대로 가진다 !!

그러니 다대일 양방향 매핑을 사용하도록 하자 !

3. 일대일


일대일 관계는 아래와 같은 특징이 있다

  • 일대일 관계는 그 반대도 일대일 관계이다
  • 테이블 관계에서 일대다, 다대일은 항상 N쪽이 외래키를 가진다. 반면에 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래키를 가질 수 있다 !

일대일 관계는 대상테이블 중 누가 외래키를 가질지 선택해야 한다.

  1. 주 테이블에 외래 키
    • 외래키를 객체 참조와 비슷하게 사용할 수 있어서 객체지향 개발자들이 선호
    • 주 테이블이 외래키를 가지고 있으므로 주 테이블만 확인해도 연관관계를 알 수 있다는 장점이 존재
  2. 대상 테이블에 외래 키
    • 전통적인 데이터베이스 개발자들의 선호방식
    • 테이블 관계를 일대일에서 일대다로 변경할 때 테이블 구조를 그대로 유지할 수 있다는 장점이 존재

주 테이블에 외래 키

JPA는 주 테이블에 외래키가 있으면 좀 더 편리하게 매핑할 수 있다. 이 관계는 다대일 단방향(ManyToOne) 과 비슷하다.

  1. 단방향
    • 다대일 단방향과 거의 비슷
  2. 양방향
    • 연관관계의 주인을 정하고 주인이 아닌 테이블은 mappedBy를 선언한다

대상 테이블에 외래 키

  1. 단방향
    • 일대일 관계 중 대상 테이블에 외래 키가 있는 단방향 관계는 JPA에서 미지원
    • 단방향 관계를 수정하거나 양방향 관계로 만들어 연관관계의 주인을 설정해야 함
  2. 양방향
    • 대상 테이블에 외래 키를 두고 싶으면 양방향 매핑
⚠️ 프록시를 사용할 때 외래 키를 직접 관리하지 않는 일대일 관계는 지연로딩으로 설정해도 즉시로딩된다. 프록시의 한계때문에 발생하는 문제이며 프록시 대신 `bytecode instrumentation`을 사용하면 해결가능하다.

4. 다대다


관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없어 릴레이션 테이블을 이용한다. 하지만 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있다. ManyToMany를 사용하면 다대다 관계를 편리하게 매핑할 수 있다.

다대다: 단방향

@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 id;
		
		private String name;
}

@JoinTable

name: 연결 테이블을 지정
joinColumns: 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정
inverseJoinColumns: 반대 방향인 상품과 매핑할 조인 컬럼 정보를 지정

public void save() {
		Product product = new Product();
		product.setId("product");
		product.setName("상품");
		em.persist(product);

		Member member = new Member();
		member.setId("member");
		member.setUsername("회원");
		member.getProducts().add(product);
		em.persist(member);
}

회원과 상품 연관관계를 설정했으므로 회원을 저장할 때 연결 테이블에도 값이 저장됨

해당 코드를 실행하면 아래와 같은 SQL이 실행된다

INSERT INTO PRODUCT...
INSERT INTO MEMBER ...
INSERT INTO MEMBER_PRODUCT ...
public void find() {
		Member member = em.find(Member.class, "member");
		List<Product> products = member.getProducts();
		for (Product product : products) {
				System.out.println("product.name = " + product.getName());
		}
}

getProducts() 를 호출하면 아래와 같은 SQL이 실행된다

SELECT * FROM MEMBER_PRODUCT MP
INNER JOIN PRODUCT P ON MP.PRODUCT_ID = P.PRODUCT_ID
WHERE MP.MEMBER_ID = ?

ManyToMany 덕분에 복잡한 다대다 관계를 어플리케이션에서는 아주 단순하게 사용할 수 있다.

다대다: 양방향

양쪽 중 원하는 곳에 mappedBy로 연관관계의 주인을 지정한다.
(mappedBy가 없는 부분이 주인이다 !)

다대다 양방향 연관관계는 편의 메소드를 추가해서 관리하는 것이 편리하다.

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

@ManyToMany를 사용하면 연결 테이블을 자동으로 처리해주므로 도메인 모델이 단순해지고 편리해진다. 하지만 연관관계 정보 외에 다른 추가적인 정보들을 담을 수 없다.

릴레이션 테이블이 필요하며 해당 엔티티에 @IdClass를 사용하면 복합 기본키를 매핑할 수 있다.

@Entity
@IdClass(MemberProductId.class)
public class MemberProduct {
		@Id
		@ManyToOne
		@JoinColumn(name = "MEMBER_ID")
		private Member member;

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

		...
}
  1. 복합 기본 키

    • JPA에서 복합키를 사용하려면 별도의 식별자 클래스를 만들어야 한다.
    • @IdClass를 사용해서 식별자 클래스를 지정하면 된다.
    • 복합 키를 위한 식별자 클래스는 특징들이 있다
      • 복합 키는 별도의 식별자 클래스로 만들어야 한다
      • Serializable 을 구현해야 한다
      • equalshashCode 메소드를 구현해야 한다
      • 기본 생성자가 있어야 한다
      • 식별자 클래스는 public 이어야 한다
      • @IdClass 를 사용하는 방법 외에 @EmbeddedId 를 사용하는 방법도 있다
    🔊 자바 IDE 에는 대부분 equals, hashCode 메소드를 자동으로 생성해주는 기능이 존재
  2. 식별 관계

    • 부모테이블의 기본 키를 받아서 자신의 기본 키 + 외래 키로 사용하는 것을 데이터베이스 용어로 식별관계라 한다

복합키를 사용하는 방법은 복잡...

컬럼 하나만 기본키로 사용하는 것과 비교해서 복합 키를 사용하면 ORM 매핑에서 처리할 일이 상당히 많아지며 식별자 클래스도 만들어야하고 그 안에 equals, hashCode도 구현해야하며 @IdClass 또는 @EmbeddedId도 사용해야한당..

다대다: 새로운 기본 키 사용

추천하는 기본 키 생성 전략

  • 데이터베이스에서 자동으로 생성해주는 대리 키를 Long 값으로 사용하는 것 → 편하고 반 영구적으로 쓸 수 있으며 비지니스에 의존하지 않는다 → ORM 매핑 시에 복합키를 만들지 않아도 된다

다대다 연관관계 정리

다대다 관계를 일대다 다대일 관계로 풀어내기 위해 연결 테이블을 만들 때 식별자 구성 방법

  • 식별 관계 : 받아온 식별자를 기본 키 + 외래 키로 사용
  • 비식별 관계 : 받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가
profile
제어할 수 없는 것에 의지하지 말자

0개의 댓글