메인 페이지에 보여줄 상품 페이징 구현에 대해 정리해보자 한다.
Spring Data 의 Pageble 과 Page를 Querydsl과 함께 사용해보도록 하자.
요약하자면
- SpringData에서 Pageable은 페이지 요청에 대한 데이터를 담을때 사용하는 인터페이스이다.
- 응답할때는 Page를 사용한다.
- where 절 을 통해 동적쿼리를 만든다.
- 검색 키워드를 담을 ItemSearchCondition(DTO), 반환되는 데이터를 담을 MainItemDto(DTO)를 만들었다.
사용자 정의 리포지토리를 만든다.
- 사용자 정의 인터페이스 구현
- 스프링 데이터 리포지토리에 사용자 정의 인터페이스 상속
먼저 리파지토리에 JPAQueryFactory를 등록한다.
생성자에서 직접 생성하여 주입해준다.
public class ItemRepositoryCustomImpl implements ItemRepositoryCustom {
private final JPAQueryFactory queryFactory;
public ItemRepositoryCustomImpl(EntityManager em) {
this.queryFactory = new JPAQueryFactory(em);
}
....
MainItemDto : 반환되는 데이터를 담는다.
- @QueryProjection을 통해서 Dto 조회를 하려한다.
- Dto 생성후 gradle에서 compileQueryDsl로 Q타입 객체가 생성되 도록 해야한다.
@Data
public class MainItemDto {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
private String imgName;
private String itemType;
private String categoryType;
@QueryProjection
public MainItemDto(Long id, String itemName, Integer price, Integer quantity, String imgName, String itemType, String categoryType) {
this.id = id;
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
this.imgName = imgName;
this.itemType = itemType;
this.categoryType = categoryType;
}
}
ItemSearchCondition : 검색 키워드를 담는다.
- 검색 조건을 담는 Dto이다.
- 각 필드가 조건의 파라미터가 된다.
@Data
public class ItemSearchCondition {
private String itemName;
private String categoryType;
private User loginMember;
}
where 절 파라미터를 통한 동적쿼리를 만든다.
- where 절에 직접 만든 메서드를 통해 컨디션을 추가한다 .
- 메서드에서는 컨디션의 null 여부를 따져 검색조건을 반환한다.
BooleanExpression
- and 와 or 같은 메소드들을 이용해서 BooleanExpression 을 조합해서 새로운 BooleanExpression 을 만들 수 있다.
- 그러므로 재사용성이 높다.
- BooleanExpression 은 null 을 반환하게 되면 Where 절에서 조건이 무시되기 때문에 안전하다.
private BooleanExpression itemNameContains(String itemName) {
return StringUtils.hasText(itemName) ? item.itemName.contains(itemName) : null;
}
private BooleanExpression categoryTypeContains(String categoryType) {
return StringUtils.hasText(categoryType) ? item.categoryType.contains(categoryType) : null;
}
조회할땐 Entity 보다는 Dto 를 우선적으로 가져오기
Dto 조회할때 필요한 칼럼만 가져온다.
필요한 칼럼만 조회하도록 하고 Entity 를 조회하면 그에따른 OneToOne 관계에서는 N+1 문제가 발생할 수도 있으니까
조심하도록 한다.
PageableExecutionUtils를 사용하여 응답한다.
- 컨텐츠를 가져오는 쿼리와 카운트 쿼리를 분리한다.
- 컨텐츠를 가져오는 쿼리는 fetch()로 한다.
- 카운트 쿼리는 fetch()하지 않는다.
- PageableExecutionUtils.getPage()는 PageImpl과 같은 역할을 한다.
- 마지막 인자로 함수를 전달하는데 내부 작동에 의해서 토탈카운트가 페이지사이즈 보다 적거나 , 마지막페이지 일 경우 해당 함수를 실행하지 않아 쿼리를 조금더 줄일 수 있다.
- PageableExecutionUtils.getPage()을 사용하면 조금 더 성능 최적화가 된다.
- 쿼리하나가 아쉬울 때는 PageImpl 보다는 PageableExecutionUtils.getPage를 사용하도록 한다.
@Override
public Page<MainItemDto> searchPageSort(ItemSearchCondition condition, Pageable pageable) {
List<MainItemDto> content = queryFactory
.select(new QMainItemDto(
item.id.as("itemId"),
item.itemName.as("itemName"),
item.price,
item.stockQuantity,
itemImg.imgName,
item.itemType,
item.categoryType)
)
.from(itemImg)
.join(itemImg.item, item)
.where(itemImg.reImgYn.eq("Y"))
.where(itemNameContains(condition.getItemName()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
JPAQuery<MainItemDto> query = queryFactory
.select(new QMainItemDto(
item.id.as("itemId"),
item.itemName.as("itemName"),
item.price,
item.stockQuantity,
itemImg.imgName,
item.itemType,
item.categoryType)
)
.from(itemImg)
.join(itemImg.item, item);
return PageableExecutionUtils.getPage(content, pageable, ()->query.fetch().size());
}
느낀점/ 보완사항
- Querydsl 사용하는 부분에서 성능 최적화할 수 있는지 고민해본다.
- 파라미터 null값 예외처리를 추가해야 한다.
- 카테고리별로 상품 페이징하기, 회원별 등록상품 페이징하기도 위의 코드와 같은 방식이다.
- 카테고리별로 상품 페이징하는 부분에서 아쉬운점이 있다.
아쉬운 점
- rest api 접근 방식 규칙을 지키지 못한 점이 많다.
- ItemCategory, ItemType을 @Data로 등록하는 방식 말고 다른 방식으로 교체가능한지 고민이다.
- Item의 정보 중 ItemCategory(상품 카테고리), ItemType(상품 상태등급)을 컨트롤러에서 @ModelAttribute으로 등록해 사용하는 방법이 좋은건지 모르겠다.
//ItemCategory
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ItemCategory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "category_id")
private Long category_id;
private String code;
private String categoryName;
public ItemCategory(String code, String categoryName) {
this.code = code;
this.categoryName = categoryName;
}
}
//controller --->
@ModelAttribute("itemCategories")
public List<ItemCategory> itemCategoryList() {
List<ItemCategory> itemCategories = new ArrayList<>();
itemCategories.add(new ItemCategory("BOOK", "책"));
itemCategories.add(new ItemCategory("MUSIC", "음반"));
return itemCategories;
}
'프로젝트 > 개인 프로젝트 V1' 카테고리의 다른 글
(8) 장바구니, 상품 판매 (0) | 2022.08.02 |
---|---|
(7) 로그인 (0) | 2022.08.02 |
(5) domain, repository, service 계층별 설계 및 구현 (0) | 2022.08.01 |
(3) 상품 등록 계층 설계 (0) | 2022.08.01 |
(4) 상품 등록 구현 - 이미지 업로드 (0) | 2022.08.01 |
댓글