티스토리 뷰

JPA 양방향관계, 즉시로딩과 지연로딩

DB를 사용하다보면 필연적으로 여러 테이블을 설계하게 된다.
그리고 여러 테이블의 연관 관계를 이용하여 원하는 데이터를 DB로부터 가져오게 된다.

예를들어, 배달앱이라고 가정했을 때 유저의 정보를 저장하는 User 테이블과 주문 정보를 저장하는 Order 테이블이 존재 할 것이다.

그리고 유저는 여러개의 주문을 가질 수 있으며, 주문은 하나의 유저에게 속하게 된다.
이러한 관계를 유저 입장에서는 주문과 1:N 관계이며 주문 입장에서는 유저와 N:1 관계를 가지게 된다.

그리고 이를 클래스로 표현하면 다음과 같다.

@Entity
@Getter
@Setter
public class User {

  @Id
  @GeneratedValue
  @Column(name = "member_id")
  private Long id;

  @Column
  private String name;

  @OneToMany(mappedBy = "member")
  private List<Order> orders = new ArrayList<>();
}
@Entity
@Getter
@Setter
public class Order {

  @Id @GeneratedValue
  @Column(name = "order_id")
  private Long id;

  @ManyToOne
  @JoinColumn(name = "member_id")
  private Member member;
}

여기서 잘 봐야할 부분은 User 클래스에 있는 orders 필드와 Order 클래스에 있는 member 필드이다.

UserOrder 와 1:N관계이므로 @OneToMany 어노테이션을 사용했으며 Order는 그와 반대로 @ManyToOne 어노테이션을 사용했다.

일반적으로 외래키를 가지고 있는쪽이 연간관계의 주인이므로 외래키를 가지고 있는 Order 쪽에서 @JoinColumn 어노테이션을 통해 User의 PK와 매칭해준다.

User 쪽에서는 Ordermember 필드에 매칭되었음을 알리기 위해 @OneToMany(mappedBy = "member") 로 설정하자.

만약 @JoinColumn 을 사용하지않으면 연관관계를 위한 테이블이 새롭게 생성되니 불필요한 테이블 생성을 막기위해 @JoinColumn 을 통해 User의 PK에 매칭하자.

양방향 관계 설정 이유

위 클래스를 잘보면 User 에는 Order 정보가 있고, Order 에는 User 정보가 있다.

하지만 실제로 DB 테이블의 구조를 보면 User 쪽에서는 Order 의 정보를 가지고 있을 필요가 없다. Order 에 존재하는 User 의 PK를 통해 참조할 수 있기 때문이다.

이처럼 양쪽에 관계를 설정하는 것을 양방향 관계 라고 하며 한쪽에만 관계를 설정하는 것은 단방향 관계 라고 한다.

그렇다면 왜 양방향 관계 로 설정해야 할까?

단방향 관계에서 UserOrder 를 정보를 저장한다고 해보자.

Order 를 저장하는 시점에 User 의 정보를 모르므로 일단 Order 를 저장하고 이후에 User 의 PK를 Update해야 한다.

따라서, 데이터를 insert한 이후에 FK값을 설정해주는 추가적인 update 쿼리가 나가게 된다.

하지만 양방향 관계를 설정했을 경우 Order 를 저장하는 시점에 유저의 id를 알 수 있으므로 추가적인 update 쿼리가 나가지 않는다.

즉시로딩과 지연로딩

현재 UserOrder 는 연관관계를 맺고 있다.

만약 Order 의 정보가 필요하다고하자. 이때, Order 의 정보를 가져올 때 User 의 정보도 함께 가져와야 할까?

물론 상황에 따라 다를 수 있다. 어떤 때에는 주문 정보와 유저의 정보가 함께 필요한 경우가 있을 수 있다.

하지만 반대로, 단순히 Order 의 정보만 필요한 경우가 있을 수 있다.

만약 즉시로딩 관계로 설정되어 있다면 Order 가 조회되는 시점에 연관관계로 맺어진 Member 도 DB로 부터 조회하게 된다. 하나의 쿼리로 Order의 정보와 User의 정보까지 가져오므로 효율적이라고 느껴질 수 있다.

하지만

"select o from Order o" 이런 쿼리를 사용하면 어떻게 될까

먼저 Order의 데이터를 모두 가져올 것이다. 그런데 Order의 데이터를 가져와 놓고 보니 연관관계 설정된 필드가 있고, 그 설정이 즉시로딩 으로 설정되어 있다.

그렇다면 이때, 연관관계 설정된 모든 User 의 데이터까지 가져오게 된다.

즉 쿼리는 단지 1개를 날렸을 뿐인데, 그와 연관된 데이터를 가져오기 위해 N개의 추가 쿼리가 나가야 하는 상황이다. 이를 JPQL N+1 문제라고 한다.

이러한 문제를 해결하기 위해서는 즉시로딩 이 아닌 지연로딩 을 사용하자.
그럼 연관관계가 설정되어 있더라도 그 데이터를 즉시 가져오지 않는다.
데이터를 흉내내는 프록시 객체 를 가져온다음, 실제로 그 데이터가 필요한 시점에 쿼리가 나가므로 더 효율적이라고 할 수 있다.

그렇다면 지연 로딩 으로 설정은 해두었지만, 특정 경우에 하나의 쿼리로 연관관계를 조회하고 싶을 때는 어떻게 하면 될까?

이때는 fetch join 이나 엔티티 그래프 기능을 사용하면 된다.

JPA에서 ManyToOne 이나 OneToOne 은 기본적으로 즉시로딩 으로 설정되어 있다. 따라서 지연로딩 으로 설정하기 위해서는 @ManyToOne(fetch = FetchType.LAZY) 와 같이 설정하자.

'JPA' 카테고리의 다른 글

[JPA] 변경감지와 병합(merge)을 통한 엔티티 수정  (0) 2022.04.15
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함