인프런 커뮤니티 질문&답변

박진영님의 프로필 이미지
박진영

작성한 질문수

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

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

6분21초 질문 있습니다.

해결된 질문

작성

·

514

19

6분21초 화면에서 

List<Order> all = orderRepository.findAllByString( ...)

return all ; 했을때 무한루프에 빠지는걸 보았습니다.

그런데 Hibernate5Module 을 등록안했기 때문에 Order랑 연관관계인 Member,Delivery 는 프록시객체라서  무한루프가 아닌  jackson 라이브러리가 프록시객체를 인식못하는 예외로 먼저 빠져야 된다고 생각했는데 무한루프 에러를 먼저 처리하네요.

현재 생각으로는 무한루프는 return all 하기전에 orderRepository.findAllByString( ...) 에서 오류가 나는거고

@jasonignore 로 처리하고Hibernate5Module 등록안하면 return all; 할때  jackson 라이브러리가 프록시객체라서 에러를 내서 그렇구나 하고 넘어갔었는데 제가 생각한게 맞는건가요 ??

답변 4

24

안녕하세요. 저도 강의를 듣다가 해당 이슈가 너무 흥미로워서 약간의 추적을 해보았습니다. 답변이라고 하기엔 부족함이 있지만 간단하게나마 남겨봅니다..

com.fasterxml.jackson.databind.ser.std.BeanSerializerBase 클래스의 #serializeFields : void 메소드를 주목하시면 됩니다.

또한, 기억해야 할 것은 직렬화 대상 객체는 jpabook.jpashop.domain.Member$HibernateProxy 즉, "프록시 객체"라는 것 입니다.

이 객체는 jpabook.jpashop.domain.Member 객체의 필드에 추가하여 또 하나 가진 필드가 있습니다. "hibernateLazyInitializer" 입니다.

#serializeFields 메소드에서 props 변수가 직렬화할 필드목록 배열을 나타냅니다. 살펴보면 다음과 같습니다.

props[0] : property 'id'

props[1] : property 'name'

props[2] : property 'address'

props[3] : property 'orders'

props[4] : property 'hibernateLazyInitializer'

* orders에 @JsonIgnore가 붙어있으면 props는 orders를 제외한 배열. 다음과 같습니다.

props[0] : property 'id'

props[1] : property 'name'

props[2] : property 'address'

props[3] : property 'hibernateLazyInitializer'

다음으로 주목해야 할 것은 #serializeFields 메소드에서의 예외처리 부분입니다. 다음과 같습니다.

try {

    ........

    for (final int len = props.length; i < len; ++i) {

       ........

       if (prop != null) {

           prop.serializeAsField(bean, gen, provider);

       }

    }

    ........

} catch (Exception e) {

           ...생략...

} catch (StackOverflowError e) {

           ...생략...

}

따라서 @JsonIgnore가 없을 때는 props[3]값인 'orders'를 작업할 때 양방향관계에 의한 순환참조로 인해 무한재귀가 동작하면서 Error가 발생하여

catch (StackOverflowError e) 이쪽으로 빠지게 됩니다. 강의에서 첫번째 이슈 케이스에 해당합니다.

만일 @JsonIgnore가 있을 때는 props[3]값인 'hibernateLazyInitializer'를 작업할 때 No serializer found Exception이 발생하여

catch (Exception e) 이쪽으로 빠지게 됩니다. 강의에서 두번째 이슈 케이스에 해당합니다.

* 첫번째 이슈 케이스에서 응답이 된 것은 StackOverflowError 가 발생하면서 response에 담아놨던 것을 강제 flush하고 뻗어버려서 그런 듯 합니다.

* @JsonIgnore가 없을 때 디버그 모드로 props[3]값인 'orders'를 작업할 시점 때 prop = null로 바꿔치기 할당하여 props[4]인 'hibernateLazyInitializer'를 작업하게 해보았더니 No serializer found Exception 똑같이 발생하였습니다.

유익한 강의 항상 감사드립니다. JPA전문가로 거듭나기 위해 정말 열심히 하겠습니다!!

7

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 박진영님 좋은 질문입니다.

우선 결론부터 말씀드리면, 둘다 return all을 호출하고 나서 실제 API 응답시에 발생하는 오류입니다.

저도 질문을 듣고 생각을 해보니 첫번째 예제에서 JSON이 프록시 객체를 인식하지 못하는 것이 분명 문제가 맞네요.  이건 아마도 JSON이 마샬링 할 때 실제 인스턴스를 기준으로 타입을 알아내는데, 무한루프가 계속 돌아서 타입을 정확하게 찾는 것에 실패해서 그런 조건을 체크조차 못한 것이라 생각합니다. 그리고 두번째 예제는 무한루프가 발생하지 않고, 정상 동작하기 때문에 프록시인지 아닌지 타입을 발견할 수 있습니다. 물론 이것은 정확하지는 않고 제 추정입니다^^

혹시 정확한 답을 알고 계신분은 답변 부탁드립니다^^!

6

김영한님의 프로필 이미지
김영한
지식공유자

크! 승석님 저도 잘 이해가 되네요! 정말 고맙습니다^^!

5

박진영님의 프로필 이미지
박진영
질문자

답변 너무 감사합니다.  열심히 하겠습니다.

박진영님의 프로필 이미지
박진영

작성한 질문수

질문하기