JPA #4 연관관계 매핑

함형주·2022년 10월 23일
0

JPA

목록 보기
4/7

질문, 피드백 등 모든 댓글 환영합니다.

JPA에서 중요한 엔티티 연관관계를 매핑하는 방법을 알아보겠습니다.

객체가 연관관계를 맺는 방법(참조)과 테이블이 연관관계를 맺는 방법(외래 키)이 다르기 때문에 이를 연결하기 위한 수많은 코드가 필요합니다.

JPA에선 어노테이션을 사용하여 간편하게 엔티티의 연관관계를 매핑할 수 있습니다.

양방향 연관관계

테이블은 외래 키 하나로 쉽게 두 테이블을 관리할 수 있습니다. 외래키로 양방향 매핑이 가능합니다.
(select * from User U join Board B on U.User_id = B.User_id,
select * from Board B join User U on B.User_id - U.User_id)

하지만 객체는 참조를 사용하여 연관관계를 맺습니다. 그렇기 때문에 사실은 객체에선 양방향으로 연관관계를 맺는 것이 아니라 단방향 연관관계를 두 개 가지는 것입니다.
(user.getBoard(), board.getUser() - 객체마다 참조값이 각자 필요함)

JPA에서 이를 어떻게 설계하는지 알아보겠습니다.

1:N 관계의 User와 Item을 예를 들겠습니다. (사용된 어노테이션에 관한 설명은 밑에서 하겠습니다.)

테이블

객체

@Entity
public class User {
	@Id @GeneratedValue @Colunm(name = "user_id")
    private Long id;
    
    @OneToMany(mappedBy = "user")
    private List<Item> items = new ArrayList<>();
}
@Entity
public class Item {
	@Id @GeneratedValue @Colunm(name = "item_id")
    private Long id;
    
    @ManyToOne @JoinColumn(name = "user_id")
    private User user;
}

User와 Item는 1:N 관계이기 때문에 User가 List로 Item를 가지고 Item는 User 단일 객체를 가지도록 설계했습니다.
@OneToMany, @ManyToOne 을 이용해서 1:N 관계의 테이블을 객체로 연결할 수 있습니다. (@OneToOne, @ManyToMany도 존재합니다.)

객체는 단방향 두 개로 연관관계를 가지므로 어떤 객체를 테이블의 외래 키를 관리하는 연관관계의 주인으로 설정할지 결정해야합니다.

연관관계의 주인 정하기

객체에서 양방향 매핑을 사용할 때는 외래 키를 관리할 연관관계의 주인을 지정해야 합니다.

만약 새로운 Item을 생성하면 연관관계를 어떻게 연결해야 할까요?

  1. Item item = new Item(); user.getItems.add(item); em.persist(item);

    • DB 결과 : item_id = 1, user_id = null
  2. Item item = new Item(); item.setUser(user); em.persist(item);

    • DB 결과 : item_id = 2, user_id = 1

두 결과의 차이는 연관관계의 주인에 값을 설정했는지 아닌지 여부에 따라 생깁니다.

JPA에서 연관관계의 주인은 외래 키를 관리하여 등록, 수정 등의 작업을 실행하는 역할을 담당합니다. 반대로 주인이 아닌 쪽은 조회만 가능합니다.

즉 외래 키가 있는 곳을 연관관계의 주인으로 설정해야 합니다. (만약 반대를 연관관계의 주인으로 설정하게 된다면 외래 키가 반대 테이블에 존재하므로 연관관계 관리를 위해 추가로 SQL이 작성됩니다.)

@OneToOne, @OneToMany, @ManyToMany의 속성인 mappedBy를 사용하여 연관관계의 주인을 지정할 수 있습니다.
핵심은 연관관계의 주인이 아닌 필드에 mappedBy를 사용하여 주인을 지정한다는 점 입니다. (mappedBy = 연관관계의 주인 <- X)

위의 예제에서 N쪽인 Item.user_id 를 외래 키로 사용하므로 Item.user를 연관관계의 주인으로 설정해야합니다.

때문에 위 예제에서 User.items(연관관계의 주인이 아닌 쪽)에 @OneToMany(mappedBy = "user")를 적용했습니다.

연관관계 매핑

@JoinColunm

@JoinColunm을 이용하여 외래 키를 매핑합니다. 이 어노테이션은 생략 가능합니다.

  • name : 매핑할 외래 키 이름 (기본 값 필드명 + "_" + 참조하는 테이블 PK 컬럼명 사용. 어노테이션 자체를 생략할 경우 이 규칙에 따라 외래 키 매핑)

  • referencedColumnName : 외래 키가 참조하는 테이블 컬럼명 (기본 값 참조하는 테이블 PK 컬럼명 사용)

  • foreignKey(DDL) : 외래 키 제약조건 지정

  • unique : @Colunm과 동일
    nullable insertable
    updatable
    columnDefinition
    table

@OneToMany

1:N 연관관계 매핑 어노테이션

  • mappedBy : 연관관계의 주인 필드 지정

  • fetch : fetch 전략 설정 (기본 값 FetchType.LAZY)

  • cascade : 영속성 전이 범위 지정

  • targetEntity : 연관된 엔티티의 타입 정보를 설정

  • orphanRemoval() 고아 객체관리 (기본 값 FALSE)

@ManyToOne

N:1 연관관계 매핑 어노테이션

  • optional : 연관된 엔티티 필수 여부 (기본 값 TRUE)

  • fetch : fetch 전략 설정 (기본 값 FetchType.EAGER)

  • cascade : 영속성 전이 범위 지정

  • targetEntity : 연관된 엔티티의 타입 정보를 설정

@OneToOne

1:1 연관관계 매핑 방법은 1:N 매핑과 형식과 비슷합니다. 외래 키에 맞춰 연관관계의 주인을 설정하는 것은 같지만 1:N 경우 N쪽에서 외래 키를 관리하지만 1:1 관계에서는 외래 키를 주 테이블이건 대상 테이블이건 어느 쪽에서 관리해도 상관 없습니다.

  • mappedBy : 연관관계의 주인 필드 지정

  • optional : 연관된 엔티티 필수 여부 (기본 값 TRUE)

  • fetch : fetch 전략 설정 (기본 값 FetchType.LAZY)

  • cascade : 영속성 전이 범위 지정

  • targetEntity : 연관된 엔티티의 타입 정보를 설정

  • orphanRemoval() default false;

@ManyToMany

N:N 연관관계 매핑 어노테이션
N:N 연관관계 자체가 안티 패턴이므로 사용하지 않는 것을 추천합니다. N:N을 1:N, N:1 관계의 연결테이블을 사용하여 매핑하고 연결테이블을 엔티티로 관리하는 방식으로 풀어 사용합니다.

  • mappedBy : 연관관계의 주인 필드 지정

  • fetch : fetch 전략 설정 (기본 값 FetchType.LAZY)

  • cascade : 영속성 전이 범위 지정

  • targetEntity : 연관된 엔티티의 타입 정보를 설정

profile
평범한 대학생의 공부 일기?

0개의 댓글