현재 JPA 를 이용해서 페이징 기능을 사용하고 있으나, 불필요한 Response 생성 및 API 를 클라이언트에게 보내줄때 좀 더 편리하게 사용할 수 있도록 리팩토링을 하려고 합니다.
먼저 현재 코드를 보면 아래와 같습니다.
이전에 가장 중요한 것은 어떤 반환값이든 엔티티를 반환값으로 사용하지 않습니다.
엔티티가 변경되면 API 를 사용하고 있는 화면에 장애가 발생할 수 있기 때문에 Dto 클래스를 필수로 사용합니다.
List<GoodsPageResponse> 타입을 갖고 있는 것을 볼 수 있는데, GoodsPageResponse를 따로 만들어준 이유는 페이징에 필요한 정보만 축약해서 응답해주려고 만들었습니다.
// 상품 전체 조회
@GetMapping("/goods")
@ResponseStatus(HttpStatus.OK)
@ApiOperation(value = "상품 전체 조회")
public List<GoodsPageResponse> goodsFindAll(@PageableDefault(sort = "id", direction = Sort.Direction.DESC) Pageable pageable) {
return goodsService.goodsFindAll(pageable);
}
public class GoodsPageResponse {
private Long memberId;
private Long goodsId;
private String goodsName;
private String categoryName;
private int price;
private String description;
private List<ImageResponse> imageList;
private List<OptionResponse> options;
private int totalPage;
private int totalCount;
private int pageNumber;
private int currentPageSize;
필요한 응답 필드만 내려준다는 의미에서는 좋은 방법일 수 있지만, 이렇게 응답을 내리게 되면 문제가 발생할 수 있습니다.
먼저, Response 클래스를 하나 더 생성해야 합니다. 유지보수 및 관리 면에서 번거로워집니다.
GoodsResponse 클래스를 공통으로 사용하고 싶으나, GoodsPageResponse 를 따로 생성해서 상품 페이징이 필요한 곳에 따로 사용해야 합니다.
두번째, goodsRepository.findAll(Pageable); 을 이용하면 Page 타입으로 반환되는데, 이것을 다시 List 형태로 변환해야 하는 불필요한 과정을 거쳐야 합니다.
가장 큰 문제는 양날의 검처럼 발생됩니다.
필요한 응답코드만 내려줬으나, 페이징정보가 중복되는 것을 볼 수 있습니다.
만약 페이징 size가 10개라면 10개의 페이징 중복값이 나타납니다.
[
{
"memberId": 118,
"goodsId": 217,
"goodsName": "상품test",
"categoryName": "신발",
"price": 3000,
"description": "나이키정품!",
...
...
"totalPage": 25,
"totalCount": 121,
"pageNumber": 0,
"currentPageSize": 5
},
{
"memberId": 118,
"goodsId": 216,
"goodsName": "상품2000",
"categoryName": "가방",
"price": 3000,
"description": "나이키정품!",
...
...
"totalPage": 25,
"totalCount": 121,
"pageNumber": 0,
"currentPageSize": 5
}
}
이제 List<GoodsPageResponse> 에서 Page<GoodsReponse> 로 리팩토링해보겠습니다.
응답에 필요한 필드값을 적어주었습니다.
GoodsResponse(Goods goods) 를 생성한 이유는 Service에서 Goods -> GoodsResponse 로 변환을 할때 간단히 사용하기 위해서 입니다.
public class GoodsResponse implements Serializable {
private Long memberId;
private Long goodsId;
private String goodsName;
private String categoryName;
private int price;
private String description;
private List<ImageResponse> imageList;
private List<OptionResponse> options;
public GoodsResponse(Goods goods) {
this.memberId = goods.getMemberId();
this.goodsId = goods.getId();
this.goodsName = goods.getGoodsName();
this.categoryName = goods.getCategory().getCategory();
this.price = goods.getPrice();
this.imageList = ImageResponse.toResponse(goods);
this.options = OptionResponse.toResponse(goods);
}
}
컨트롤러 부분을 변경했습니다.
// 상품 전체 조회
@GetMapping("/goods")
@ResponseStatus(HttpStatus.OK)
@ApiOperation(value = "상품 전체 조회")
public Page<GoodsResponse> goodsFindAll(@PageableDefault(sort = "id", direction = Sort.Direction.DESC) Pageable pageable) {
return goodsService.goodsFindAll(pageable);
}
서비스 부분을 변경해보겠습니다.
goodsRepository.findAll(pageable); 은 Page 타입을 반환하는데, 이 과정을 거친 후 형변환을 해주어야 합니다.
goods.map 을 이용해서 형변환을 간단히 진행할 수 있습니다.
new GoodsResponse(good) 은 GoodsResponse 클래스에서 만든 생성자 타입으로 만들어주었습니다.
// 상품 전체 검색
@Override
@TimerAop
@Transactional(readOnly = true)
@Cacheable(cacheNames = "goods", key = "#pageable")
public Page<GoodsResponse> goodsFindAll(Pageable pageable) {
Page<Goods> goods = goodsRepository.findAll(pageable);
Page<GoodsResponse> responses = goods.map(good -> new GoodsResponse(good));
return responses;
}
테스트 코드의 리팩토링은 포스팅에서는 생략하겠습니다.
이제 포스트맨을 통해 테스트해보겠습니다.
이전과 다르게 응답 값 맨 아래에 공통적으로 1개만 응답해주는 것을 볼 수 있습니다.
보편적으로 페이징 기능은 이렇게 응답값을 내려주면 클라이언트에서 사용하는 것을 디폴트값으로 사용하고 있다고 합니다.
'Spring > JPA' 카테고리의 다른 글
Querydsl 동적 쿼리 - 기초 예제 (0) | 2023.02.04 |
---|---|
QueryDsl 설정 방법 - Spring boot 2.7.x (1) | 2023.02.03 |
JPA N+1 문제 알아보기 (0) | 2023.01.25 |
@Convert - T타입 + Map 사용하기 (0) | 2023.01.14 |
JpaRepository 와 CrudRepository 의 차이점 (0) | 2022.12.04 |
댓글