Spring : feign.codec.DecodeException: Error while extracting response for type ~ and content type ~

상우·2025년 1월 8일

쇼핑몰 만들기를 진행하던중
pagenation을 해서 데이터를 가져와야할 일이 있었다
내가 가져올 데이터는 쿠폰정책의 목록이었다.

여러개의 쿠폰정책을 페이지네이션해서 가져와야했는데
내가 생각한대로 가져와지지 않았다.

상황

프론트

프론트의 컨트롤러에서는 page 번호를 파라미터로 보낸다

    @GetMapping("/coupon-policies/rate/category")
    public String getRatePoliciesForCategory
    (@RequestParam(required = false, defaultValue = "0", value = "page") int pageNo, Model model){
        Page<RatePolicyForCategoryResponse> policyList = couponPolicyService.getRatePoliciesForCategory(pageNo);

        model.addAttribute("policyList",policyList.getContent());
        model.addAttribute("totalPage",policyList.getTotalPages());

        return "coupon/policy-list/rate-policy-for-category-list";
    }
    

서비스를 거쳐

 public Page<RatePolicyForCategoryResponse> getRatePoliciesForCategory(int pageNo){
        Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE);
        return couponPolicyClient.getRatePoliciesForCategory(pageable).getBody();
    }

오픈페인으로
게이트웨이를 통해 백으로 전달된다

@GetMapping("task/policies/rate/category")
    ResponseEntity<Page<RatePolicyForCategoryResponse>> getRatePoliciesForCategory(Pageable pageable);

백의 컨트롤러에 프론트에서 날아온 요청이 들어오고

   @GetMapping("/policies/rate/category")
    public ResponseEntity<Page<RatePolicyForCategoryResponse>> getRatePoliciesForCategory
            (Pageable pageable)
    {
        Page<RatePolicyForCategoryResponse> responses = policyService.getRatePoliciesForCategory(pageable);
        return ResponseEntity.ok(responses);
    }

서비스에서 Pageable을 이용해
리포지토리에서 정책들을 가져와 Page객체에 담아준다

    public Page<RatePolicyForCategoryResponse> getRatePoliciesForCategory(Pageable pageable){

        Page<RatePolicyForCategory> page = ratePoliciesForCategoryRepository.findAll(pageable);
        List<RatePolicyForCategoryResponse> result = new ArrayList<>();
        for(RatePolicyForCategory ratePolicyForCategory : page){
            result.add(RatePolicyForCategoryResponse.changeEntityToDto(ratePolicyForCategory));
        }

        return new PageImpl<>(result);
    }

그런데 나는
엔티티를 바로 리턴하는것은 적절하지 못하다고 생각했다 (트랜잭션이 계속 열려있게 되는거니깐)

그래서 엔티티를 Dto로 반환해주려고 했다
changeEntityToDto는
RatePolicyForCategory entity를
RatePolicyForCategoryResponse dto로 바꿔주는 메서드이다

단일 RatePolicyForCategory를 반환받아올때는
changeEntityToDto를 사용해
RatePolictForCategoryResponse로 바로 바꿔준 다음에
리턴해주면 되는데

지금 같은 경우는 Page 객체 내의 RatePolicyForCategory 엔티티들을 전부 Response로 바꿔줘야 했다

for문을 이용해
1. Page 인스턴스 안의 RatePolicyForCategory 엔티티들을 모두RatePolicyForCategoryResponse로 만들어주었고
2. 이거를 list에 넣은뒤
3. 이 list를 PageImpl 객체의 생성자 파라미터로 넣어
DTO를 담은 Page 객체를 만들어주었다

문제

그렇게 해서 백의 서비스에서 나온 결과값이
백의 컨트롤러로 리턴되고
백의 컨트롤러로 간 결과값이
다시 프론트의 오픈페인으로 리턴되는 도중에 다음과 같은 에러가 터졌다.

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: feign.codec.DecodeException: Error while extracting response for type [org.springframework.data.domain.Page<com.onebook.frontapi.dto.coupon.response.couponPolicy.RatePolicyForCategoryResponse>] and content type [application/json]] with root cause

읽어보니 RatePolicyForCategoryResponse 를 디코딩 하던 도중 문제가 생겼다는 것이었다.
왜 문제가 생겼을까?

원인과 해결

원인을 잘 모르겠어서
다른 방법으로 해볼까 싶어
새로운 PageImpl 객체를 만들지 않고
백의 서비스단에서 스트림을 이용하여
Page 객체내의 엔티티들을 DTO로 바꿔보았다

    // 정률정책 for Category read (all)
    public Page<RatePolicyForCategoryResponse> getRatePoliciesForCategory(Pageable pageable){

        Page<RatePolicyForCategory> page = ratePoliciesForCategoryRepository.findAll(pageable);
        List<RatePolicyForCategoryResponse> result = new ArrayList<>();
        for(RatePolicyForCategory ratePolicyForCategory : page){
            result.add(RatePolicyForCategoryResponse.changeEntityToDto(ratePolicyForCategory));
        }

// 스트림 map을 사용하여 Entity를 DTO로 변경
        return page.map(RatePolicyForCategoryResponse::changeEntityToDto);

    }

그러니까 정상적으로 Page 객체를 받아올 수 있었고
에러도 터지지 않았다.

이 두개가 어떤게 다르길래 이럴까 고민하던 도중 원인을 찾았다.

바로 내가 만든 PageImpl객체엔 pageable에 대한 정보가 전혀 없었기 때문이었다.

    public Page<RatePolicyForCategoryResponse> getRatePoliciesForCategory(Pageable pageable){

		
        // 여기서 pageable이 파라미터로 들어간채 findall 하게되면 나오는 Page 객체에는 pageable 정보가 있음
        즉 해당 Page 객체는 , pageable 정보 + 내용물(엔티티)을 가짐
        Page<RatePolicyForCategory> page = ratePoliciesForCategoryRepository.findAll(pageable);
        
        
        List<RatePolicyForCategoryResponse> result = new ArrayList<>();
        for(RatePolicyForCategory ratePolicyForCategory : page){
            result.add(RatePolicyForCategoryResponse.changeEntityToDto(ratePolicyForCategory));
        }

// 여기서 내가 new 키워드로 만든 PageImpl 객체는 내용물(dto)만 가짐, pageable 정보가 없음
        return new PageImpl<>(result);

    }

그렇기에 스트림으로 내용물만 바꿔줄 경우 , Page 객체의 정보가 유지되고 내용물만 dto로 바뀐것이기에 잘 동작했지만

내가 new PageImpl로 만들어준 Page객체는
내용물만 존재하고 pageable정보가 없었기에 동작하지 않았던 것이었다

따라서 new PageImpl을 사용할때
Pageable 정보를 생성자 파라미터로 넣어주어야 했다

PageImpl 생성자를 확인해보니
내용물 + Pageable + 총 페이지 수
를 생성자 파라미터로 받고있어서

다음과 같이 생성자에 정보를 더 넣어 생성하니
에러가 터지지 않고 잘 동작하였다

    // 정률정책 for Category read (all)
    public Page<RatePolicyForCategoryResponse> getRatePoliciesForCategory(Pageable pageable){

        Page<RatePolicyForCategory> page = ratePoliciesForCategoryRepository.findAll(pageable);
        List<RatePolicyForCategoryResponse> result = new ArrayList<>();
        for(RatePolicyForCategory ratePolicyForCategory : page){
            result.add(RatePolicyForCategoryResponse.changeEntityToDto(ratePolicyForCategory));
        }

        // return new PageImpl<>(result);
        return new PageImpl<>(result, pageable, page.getTotalPages()); // 잘됨!
    }

근데 스트림 쓰는게 더 편해서 스트림으로 그냥 내용물만 바꿔줄것같다

profile
엉성해도 우직하게

0개의 댓글