# 간단한 주문 조회 V1: 엔티티를 직접 노출

**주문** + **배송정보** + **회원**을 조회하는 API를 만들어본다!\
한마디로 주문 API를 만든다.

핵심은 **지연 로딩 때문에 발생**하는 **성능 문제를 해결**하는 것이다!

**SQL로 해결하기 어려운 성능 문제를 JPA를 통해 손쉽게 튜닝해서 성능 최적화를 이룬다!⭐️⭐️⭐️**\
지금부터 설명하는 내용은 **정말 중요**합니다. **실무에서 JPA를 사용하려면 100% 이해**해야 합니다.

**엔티티 직접 노출한 주문조회 결과**

**첫번째 문제 : 무한루프 발생**\
**양방향 순환 참조로 인해 무한루프가 발생한다!**

아래 ordersV1()의 반환타입은 Order 컬렉션이다.

1. Order에는 Member가 있고 JSON으로 Member를 뿌려야 한다.
2. Member 에는 또 List\<Order>가 있다.

\=>반복. 즉, 무한 루프에 빠지게 된다! 마치 양방향 관계의 tostring()처럼

**해결 방법**

**양방향 관계**에서 **어느 한쪽 필드**에 JSON Jackson 라이브러리의 **@JsonIgnore 해주어야한다!⭐️⭐️⭐️**\
\&#xNAN;**=>Order에서 다른 엔티티로 갔다가 다시 Order로 돌아가는 모든 엔티티(클래스)의 필드에 @JsonIgnore!!!**

Order->Member : Membe의 Order 필드에 @JsonIgnore\
Order->List\<OrderItem> : OrderItem의 Order 필드에 @JsonIgnore\
Order->Delivery : Delivery의 Order 필드에 @JsonIgnore

```java
@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1() {
          List<Order> all = orderRepository.findAll();
          for (Order order : all) {
order.getMember().getName(); //Lazy 강제 초기화
order.getDelivery().getAddress(); //Lazy 강제 초기환 }
return all; }
```

**두번째 문제 :** \
**첫번째 문제 해결 후**&#x20;

Order 클래스 내의 Member (Order->Member)\
@ManyToOne(fetch = LAZY) : **지연 로딩** =>진짜 엔티티가 아니라 **프록시 객체**를 가져온다!\
Member 객체를 상속받는 프록시 객체를 담아둔다!(프록시 기술 쓸 때 bytebuddy라는 라이브러리 많이 사용)\
(Member 객체를 사용하려고 접근할 때(=**프록시 초기화) 진짜 엔티티를 가져온다!)**

**문제는 JSON Jackson 라이브러리가 이 Member 객체를 가져오려고 하는데 진짜 엔티티가 아니라 프록시 객체이다!(그 객체를 사용하지 않고 조회만 하기 때문에 진짜 엔티티가 아니라 프록시 객체인 것이다!)**

**해결 방법1 : Hibernate5Module 이용**\
**Hibernate에서 JSON으로 프록시 객체 같은 것들은 처리하지 않도록 설정할 수 있다!(모듈 설치 필요)**\
build.gradle에 추가 : 버전은 따로 추가하지 않아도 스프링에서 최적의 버전을 자동으로 가져온다!

> **com.fasterxml.jackson.datatype:jackson-datatype-hibernate5**

Order 조회 시 Member, Delivery : xToOne(fetchType =  LAZY)이기 때문에 Order만 조회.\
\=> 지연로딩은 아직 DB에서 조회한 게 아니기 때문에 **Hibernate는 Order 조회 시에 Member, Delivery는 JSON에서 null로 처리**한다!

**해결 방법1-1**\
**FORCE\_LAZYLOADING** : LAZY로 된 애들 데이터들 다 로딩(조회)한다! 사용❌\
"강제로 LAZY 로딩을 해버린다" = "**강제로 LAZY 로딩을 무시(초기화)**&#xD558;고, **EAGER처럼 로딩**해버린다"

**문제점 : 엔티티를 그대로 모두노출한다!!!**\
**엔티티를 그 자체를 모두 노출**하는 문제점, **성능 저하 문제점이 발생**한다‼️\
애초에 필요한 데이터인 Member, Delivery 뿐만 아니라 **불필요한 데이터들까지 조**회해버리기 때문에 결국 성능상 문제도 발생한다!!!

```java
@Bean
Hibernate5Module hibernate5Module() {
    Hibernate5Module hibernate5Module = new Hibernate5Module();
    hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING,true);
    return hibernate5Module;
}
```

**해결 방법 2**\
Order의 Member, Delivery 객체(프록시)에 아무 메서드를 호출해서 원하는 애들만 선별적으로 **프록시 객체 초기화!**\
order.getMember()로 프록시 객체를 가져오고,\
order.getMember().getName()으로 진짜 엔티티를 사용(접근)함으로써 진짜 엔티티를 가져올 수 있다.\
\&#xNAN;**=>LAZY 지연 로딩이 강제 초기화된다!!!!!**\
**=>프록시 객체를 강제로 초기화함으로써 LAZY 지연 로딩은 강제로 초기화된다!**

**문제점 : 조회 결과 MEMBER, DELIVERY 테이블의 모든 데이터들이 다 조회된다!**\
**보통 회원명과 배송상태 정도만 조회한다.**\
**하지만 이런식으로 테이블의 모든 데이터들이 API로 다 노출이 된다면 운영이 어렵고, 이미 어떤 클라이언트에서 이 데이터를 사용하고 있다면 수정, 변경도 어렵다!**\
**=>그래서 필요한 데이터만!!!!! DTO로 만들어서!!!! 사용해야 한다!!!!!⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️**&#x20;

```java
@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1(){//EAGER로 변경시 : findAllByString : JPQL은 그대로 SQL로 번역. =>Order만 가져오는 쿼리인데 EAGER로 되있으면 연관된 엔티티 모두 다 조회한다!!(N+1문제 발생), 다른 API에서 불필요한 데이터들도 끌고 온다!
    List<Order> all = orderRepository.findAllByString(new OrderSearch());//List<Order>
    return all;//
    for (Order order : all) {
        order.getMember().getName();//Lazy 강제 초기화
        order.getDelivery().getAddress();//Lazy 강제 초기화
    }
}
```

**해결방법 3** : LAZY fetchType을 EAGER로 바꾸기\
LAZY 지연로딩으로 된것을 EAGER로 바꾸면 위의 코드에서 for문을 없앨 수 있다.

**문제점**\
\=> **즉시 로딩 때문에 연관관계가 필요 없는 경우에도 데이터를 항상 조회해서 성능 문제가 발생**할 수 있다‼️\
\=> 즉시 **로딩으로 설정하면 성능 튜닝이 매우 어려워 진다.**

**정리**

지연로딩을 피하기 위해 즉시 로딩(EARGR)으로 설정❌\
**항상 지연 로딩을 기본**으로 하고, **성능 최적화가 필요한 경우**에는 **페치 조인(fetch join)을 사용**해라!(V3 에서 설명)
