티스토리 뷰
주문 Entity
주문 Entity가 가지고 있는 필드는 다음과 같습니다. 연관관계에 있는 Entity를 집중해 봅시다.
주문 Entity는 회원(Member), 주문 상픔(OrderItem), 배송 정보(Delivery) Entity와 연관관계에 있습니다.
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;
private LocalDateTime orderDate; //주문시간
@Enumerated(EnumType.STRING)
private OrderStatus status; //주문상태 [ORDER, CANCEL]
}
Entity의 모든 필드 조회
준영속 상태에 있는 주문 Entity를 조회해 봅시다. order_id가 4인 Entity를 조회해 보겠습니다.
@SpringBootTest
@Transactional
public class ProxyObjectTest {
@Autowired
EntityManager em;
@Test
void test() {
Order order = em.find(Order.class, 4L);
}
}
order_id가 4인 Entity를 조회하니 아래와 같이 쿼리문이 작성되었습니다. Order Entity와 연관관계에 있는 Entity를 모두 조회하게 됩니다.
모든 필드를 조회한 결과를 반환하기 때문에 Order Entity를 사용하여 모든 필드를 사용할 수 있습니다.
그런데 만약에 Order 엔티티가 가지고 있는 정보 중에서 회원 정보만 필요하다고 가정합시다. 그렇다면 주문 상품(orderItem)과 배송 정보(delivery)에 대한 정보는 필요 없게 됩니다. 이 상황에서는 주문 Entity를 조회할 때 회원 정보만 같이 조회하면 됩니다. 이러한 상황에서 지연로딩이라는 개념이 필요합니다.
지연로딩이란 Entity를 조회할 때 필요하지 않은 정보는 조회를 미루고, 실제로 필요로 한 상황이 되었을 때 DB를 조회하게 됩니다. 이렇게 되면 네트워크를 덜 사용할 수 있는 이점을 갖게 됩니다.
지연로딩을 사용하여 Entity 조회
주문 Entity에서 회원 정보만 필요로 합니다. 따라서 당장 필요하지 않은 배송 정보와 주문 상품 정보 조회를 미뤄봅시다.
주문 Entity를 다음과 같이 변경합시다. @xToOne 애너테이션의 속성으로 fetch = LAZY를 추가합니다.
@Entity
@Table(name = "orders")
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
// @OneToOne(cascade = CascadeType.ALL)
@OneToOne(fetch = LAZY, cascade = CascadeType.ALL) // 지연로딩
@JoinColumn(name = "delivery_id")
private Delivery delivery;
private LocalDateTime orderDate; //주문시간
@Enumerated(EnumType.STRING)
private OrderStatus status; //주문상태 [ORDER, CANCEL]
}
@XtoOne 속성으로 fetch = LAZY를 추가하면 해당 Entity의 조회를 미루게 됩니다. 즉 주문 Entity를 조회할 때 LAZY로 등록한 Entity는 조회하지 않게 됩니다.
위 쿼리문을 보면 필요로 하는 Entity만 조회함을 알 수 있습니다.
지연로딩과 Proxy
지연로딩을 통해 필요하지 않은 정보의 조회를 미뤘습니다. 그렇다면 조회를 미룬 Entity는 주문 Entity에서 무슨 값으로 저장되어 있을까요? 먼저 출력을 해봅시다. 우리는 배송 정보(delivery)에 대한 조회를 미뤘습니다.
@SpringBootTest
@Transactional
public class ProxyObjectTest {
@Autowired
EntityManager em;
@Test
void test() {
Order order = em.find(Order.class, 4L);
System.out.println("order.getMember().getClass() = " + order.getMember().getClass());
System.out.println("order.getDelivery().getClass() = " + order.getDelivery().getClass());
}
}
조회를 미룬 필드에 $HibernateProxy 형태의 객체가 저장되어 있습니다. 이것을 Proxy 객체라고 하는데, 해당 객체에는 DB에서 조회한 Entity를 참조하고 있는 것이 아닌 가짜 객체를 참조하게 됩니다.
시간이 흘러 배송 정보(delivery) 정보가 필요해졌습니다. 다음과 같이 배송 정보에 접근해 보겠습니다.
@SpringBootTest
@Transactional
public class ProxyObjectTest {
@Autowired
EntityManager em;
@Test
void test() {
Order order = em.find(Order.class, 4L);
System.out.println("order.getMember().getClass() = " + order.getMember().getClass());
System.out.println("order.getDelivery().getClass() = " + order.getDelivery().getClass());
System.out.println("-------------------------------------------");
// 배송 정보에 접근
System.out.println("order.getDelivery().getOrder() = " + order.getDelivery().getOrder());
}
}
배송 정보에 접근하니 아래와 같이 쿼리문이 추가로 작성되었습니다. 지연로딩이 DB에서 조회를 미루는 개념이었기 때문에, 실제로 필요로 한 경우에는 DB에 접근하여 Entity를 조회하게 됩니다.
그렇다면 배송 정보 필드에 어떠한 객체가 저장되어 있을까요?
@SpringBootTest
@Transactional
public class ProxyObjectTest {
@Autowired
EntityManager em;
@Test
void test() {
Order order = em.find(Order.class, 4L);
System.out.println("order.getMember().getClass() = " + order.getMember().getClass());
System.out.println("order.getDelivery().getClass() = " + order.getDelivery().getClass());
System.out.println("-------------------------------------------");
System.out.println("order.getDelivery().getOrder() = " + order.getDelivery().getOrder());
// 배송 정보 class 조회
System.out.println("order.getDelivery().getClass() = " + order.getDelivery().getClass());
}
}
아래를 보면 여전히 Proxy 객체가 저장되어 있음을 알 수 있습니다.
Proxy 특징은 다음과 같습니다.
- Proxy 객체는 처음 사용할 때 한 번만 초기화한다.
- Proxy 객체를 초기화할 때, Proxy 객체가 실제 Entity로 바뀌지 않는다. Proxy 객체가 초기화되면 Proxy 객체를 통해 실제 Entity에 접근한다.
- Proxy 객체를 초기화할 때, 실제 Entity가 영속성 컨텍스트에 존재하면 실제 Entity를 반환한다. 즉 $HibernateProxy 형태의 객체가 아닌 실제 Entity를 반환한다.
위 설명을 그림으로 표현하면 다음과 같습니다.
2. Proxy 객체를 초기화할 때 영속성 컨텍스트에 실제 Entity가 존재하면 해당 객체를 참조하고, 존재하지 않는다면 DB로부터 조회하여 영속성 컨텍스트에 저장합니다. 영속성 컨텍스트에 이미 존재하는 Entity 사용 시 실제 객체를 그대로 참조하는 것이고, DB로부터 조회한 Entity를 참조하면 Proxy 객체를 통해 실제 Entity를 참조하게 됩니다.
결론
지연로딩이란 DB 조회를 미루는 것이다. 조회를 미룬 필드에는 Proxy 객체가 저장된다.
조회를 미룬 필드를 조회하게 되면 DB에서 조회하는 쿼리문이 추가적으로 작성된다.