JPA

[JPA 활용] 조회 성능 최적화 - 엔티티 직접 노출🐱

woochii 2023. 11. 4. 15:05
728x90
반응형

지연 로딩과 조회 성능 최적화

  • 지연 로딩 때문에 발생하는 성능 문제를 단계적으로 해결하기
  • 컬렉션타입을 반환하는 XXXToMany 관계는 다음으로 미루고
    XXXToOne (OneToOne, ManyToOne) 관계를 최적화 해보자

 

V1. 엔티티를 직접 노출

문제점 1

@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderRepository orderRepository;

    @GetMapping("/api/v1/simple-orders")
    public List<Order> ordersV1() {
        List<Order> all = orderRepository.findAllByString(new OrderSearch());
        return all;
    }
}

실행한 후 포스트맨으로 가서 Send 버튼을 누르면

에러가 나온다.

StackOverflowError라고 나온다.

 

원인

  • Order(주문) 엔티티는 member를 갖고있다.
  • Member(회원) 엔티티는 orders를 갖고있다.
  • 서로가 서로를 계속 호출하면서 무한반복에 걸린다.

해결 방법

  • 양방향 연관관계에는 이러한 일이 상당히 많이 발생한다.
  • 양방향에선 둘 중에 하나는 @JsonIgnore로 끊어줘야 한다.
@Entity
@Getter @Setter
public class Member {

    @JsonIgnore
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}

 

문제점 2

다시 포스트맨에서 Send를 눌러보면

다른 서버에러가 뜨는 것을 볼 수 있다.

 

원인

  • 기본적으로 모든 연관관계의 FetchType을 LAZY로 설정해 두었다.
  • 그러므로 Order에서 member를 불러올 때 Member 객체가 아닌 프록시 객체를 가져오게 된다.

해결 방법

  1. 스프링 부트 버전 3.0미만이라면 Hibernate5Module을 3.0이상이면 Hibernate5JakartaModule을 등록하자.
  2. 프록시가 아닌 진짜 Member 객체를 가져오기 위해 Member의 필요한 속성을 호출한다.

 

build.gradle

라이브러리 추가 (3.0 이상)
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5-jakarta'

 

JpashopApplication

@Bean
    Hibernate5JakartaModule hibernate5JakartaModule() {
        Hibernate5JakartaModule hibernate5JakartaModule = new Hibernate5JakartaModule();
//        강제 지연 로딩 설정
//        hibernate5JakartaModule.configure(Hibernate5JakartaModule.Feature.FORCE_LAZY_LOADING, true);
        return hibernate5JakartaModule;
    }

위와 같이 코드를 추가한다.

 

  • 주석을 풀면 강제로 지연 로딩을 설정할 수 있다.
  • 그러나 불필요한 속성들까지 호출되기 때문에 쓸 데 없는 쿼리가 많이 나간다. (성능 저하)
  • 단, 지연로딩을 피하기 위해 LAZY를 EAGER로 변경하면 절대 안된다.
  • 다음과 같은 방법으로 최소한의 성능으로 필요한 속성만 조회하자
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderRepository orderRepository;

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

포스트맨에서 실행해보면

 

 

주의

  • 이러한 방법은 좋지 않은 방법이다. (사용하지 말자)
  • 엔티티를 외부로 노출하지 말자.
  • 이러한 방법 대신 DTO로 변환해서 반환하자!

출처 : 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

728x90
반응형