Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
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 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

지연로딩과 조회성능 최적화(엔티티 노출) 본문

인프런/[인프런] Springboot와 JPA활용 2

지연로딩과 조회성능 최적화(엔티티 노출)

Seung__ 2021. 8. 22. 22:35
  • 주문, 배송정보, 회원을 조회하는 api를 만들자
  • 지연로딩 때문에 발생하는 "성능"문제를 단계적으로 해결!!!!!



1. 주문조회 : 엔티티를 직접 노출, X To One 성능 최적화


 

/**
 * 연관관계(x to One)
 * Order-> Member (N:1)
 * Order -> Delivery (1: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;
    }
}
  • 아앗....멈추지 않는다...무한루프에 빠졌다...

  • 먼저 Order와 Member는 양방향 관계이다.
  • Order를 불러오면 Member를 불러와야 하는데 Member안에 orderList가 있고 또 order는 불러오고 순환참조...

  • 따라서 json을 만드는 jackson 라이브러리는 계속 순환참조를 할 수 밖에 없음..
  • 따라서 양방향 연관관계에서 Jacson이 헷갈리지 않게 한쪽은 @JasonIgnore을 적용해주자.

 

  • localhost:8080/api/v1/simple-orders 을 실행해보자.
  • 앗...그래도 에러가 발생한다..bytebuddy가 뭐지..?
Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]
  • 왜 발생할까?

  • Order에서 Member를 가지고 올 때 지연로딩이 적용된다
  • 즉 Db에서 가지고 올 때 order 만 가지고 오고,
  • member는 프록시 라이브러리를 사용하여 프록시 객체를 가져온다.
  • 이 때 위의 bytebuddy가 프록시 라이브러리를 의미함.
    • 최근 프록시 객체를 만들 때 bytebuddy 라이브러리를 많이 사용함.
  • 즉 Member 는 사실상 byteBuddyIntercaptor가 들어가있는 것.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "MEMBER_ID")
private Member member = new ByteBuddyInterceptor();
  • member의 값을 알기위해 프록시 객체에 접근하면 그 때 db에 쿼리를 날려서 진짜 member를 가져옴.
    • = 프록시 초기화
  • 그런데 여기의 문제는 jacson 라이브러리로 json으로 만들어야 하는데, member가 member가 아니고 프록시네..?
  • json으로 못만들어서 발생한 문제.
  • 그러면 jackson 라이브러리에 지연로딩이 적용되는 객체는 json으로 뿌리지 말라하면됨.
  • 위에서 언급한 것곽 타이ㅣ jackson라이브러리에 지연로딩이 적영되는 객체는
  • json으로 뿌리지 않도록 지정하기 위해 Hibernate5Module사용이 필요하다.

 

  • dependency 등록
 // https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-hibernate5
    implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate5'
  • Hibernate5Module을 빈으로 등록
@SpringBootApplication
public class SpringJpa2Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringJpa2Application.class, args);
    }

    @Bean
    Hibernate5Module hibernate5Module(){
        return new Hibernate5Module();
    }

}
  • Hibernate5Module을 빈으로 등록하면 지연로딩 객체는 json으로 변환하지 않고 리턴하게된다.
    • 지연로딩 객체는 아직 db에서 가져오지 않았기 때문에 null로 반환된다.

  • 지연로딩이라도 값을 불러올 수 있긴 하다.
    • FORCE_LAZY_LOADING : LAZY로딩 강제로 실행.
@SpringBootApplication
public class SpringJpa2Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringJpa2Application.class, args);
    }

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

}

 

  • 근데 이처럼 엔티티를 직접 사용하면 안됨 ㅋㅋㅋㅋ
  • 불필요한 정보도 노출되고 불필요한 쿼리로 인해 성능문제도 발생함.
  • 그렇담 FORCE_LAZY_LOADING을 쓰지 않고 원하는 정보만 출력해 보자.
    • 프록시를 강제로 초기화하자.
    • get으로  member 정보에 접근하면 쿼리가 날라가서 member정보를 가져온다.
 @GetMapping("/api/v1/simple-orders")
    public List<Order>ordersV1(){
        List<Order> all = orderRepository.findAllByString(new OrderSearch());

        for (Order order : all) {
            order.getMember().getName(); //프록시 강제 초기화.
            order.getDelivery();
        }
        return all;
    }
  • 강제로 초기화한 member와, delivery만 초기화가 되었다.

 

하지만 이래도 불필요한 정보가 너무 많이 노출된다...

 

진짜로 엔티티를 API응답으로 외부로 노출하지 말자..

 

DTO로 변환해서 반환하자!!!!!

 

 

GitHub : 210822


https://github.com/bsh6463/SpringBootJPA1

 

GitHub - bsh6463/SpringBootJPA1

Contribute to bsh6463/SpringBootJPA1 development by creating an account on GitHub.

github.com

 

Comments