상품 관리 기능 구현

jihan kong·2023년 2월 24일
0
post-thumbnail
post-custom-banner

관리자 측면에서 등록된 상품 리스트를 조회할 수 있는 관리 기능

상품을 조회하는 조건을 설정하고 페이징 기능을 통해 일정 개수의 상품을 불러오고, 상품 상세 페이지로 이동할 수 있는 기능까지 갖춘 관리 기능을 만들었다.

조회 조건으로는

  • 상품 등록일
  • 상품 판매 상태
  • 상품명 또는 상품 등록자 아이디

와 같은 값을 세팅했다. 조회 조건이 여러가지이고 복잡하기 때문에 Querydsl 을 이용해서 쿼리를 동적으로 생성하기로 했다. Querydsl을 사용하면 비슷한 쿼리를 재사용할수 있다는 장점이 있기 때문에 여러모로 사용하는것이 편리한 것 같다. Querydsl을 사용하기 위해서 Maven의 컴파일 명령을 통해 QDomain 클래스를 생성하였다.

컴파일을 하고 나면 다음과 같이 target 폴더 아래 QDomain 클래스들이 생성된다.

상품을 조회하기 위해 조회 조건을 가진 Dto 클래스를 먼저 만들어야한다.

상품 조회 조건

ItemSearchDto.java

package com.shop.dto;

import com.shop.constant.ItemSellStatus;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class ItemSearchDto {

    private String searchDateType;

    private ItemSellStatus searchSellStatus;        

    private String searchBy;                       
                                                   
    private String searchQuery = "";
}
  • searchDateType : 현재 시간과 상품 등록일을 비교하여 상품 데이터를 조회.
  • searchSellStatus : 상품의 판매 상태를 기준으로 상품 데이터를 조회
  • searchBy : 상품을 조회할 때 어떤 유형으로 조회할지 선택
    • itemNm : 상품명
    • createdBy : 상품 등록자 아이디
  • searchQuery : 조회할 검색어를 저장하는 변수
    (SearchBy가 itemNm일 경우 상품명을 기준으로, createdBy일 경우 상품 등록자 아이디 기준)

Querydsl을 Spring Data Jpa와 함께 사용하기 위해서는 사용자 정의 리포지토리를 생성해야한다. 다음 3단계를 거친다.

  1. 사용자 정의 인터페이스 작성
  2. 사용자 정의 인터페이스 구현
  3. Spring Data Jpa Repository 에서 사용자 정의 인터페이스 상속

사용자 정의 인터페이스 작성

ItemRepositoryCustom.java

package com.shop.repository;

import com.shop.dto.ItemSearchDto;
import com.shop.entity.Item;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface ItemRepositoryCustom {

    Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable);
    
}
  • 상품 조회 조건을 담고 있는 itemSearchDto 객체와 페이징 정보를 담고 있는 Pageable 객체를 파라미터로 받는 getAdminItemPage 메소드를 정의.

사용자 정의 인터페이스 구현

ItemRepositoryCustomImpl.java

public class ItemRepositoryCustomImpl implements ItemRepositoryCustom {			
// 1. 

    private JPAQueryFactory queryFactory;
	// 2.
    
    public ItemRepositoryCustomImpl(EntityManager em) {
        this.queryFactory = new JPAQueryFactory(em);
    }
    // 3.

    private BooleanExpression searchSellStatusEq(ItemSellStatus searchSellStatus) {
    	// 4.
        return searchSellStatus == null ? null : QItem.item.itemSellStatus.eq(searchSellStatus);
        // 4.
    }

    private BooleanExpression regDtsAfter(String searchDateType) {
    	// 5.
        LocalDateTime dateTime = LocalDateTime.now();

        if(StringUtils.equals("all", searchDateType) || searchDateType == null) {
            return null;
        } else if (StringUtils.equals("1d", searchDateType)) {
            dateTime = dateTime.minusDays(1);
        } else if (StringUtils.equals("1w", searchDateType)) {
            dateTime = dateTime.minusWeeks(1);
        } else if (StringUtils.equals("1m", searchDateType)) {
            dateTime = dateTime.minusMonths(1);
        } else if (StringUtils.equals("6m", searchDateType)) {
            dateTime = dateTime.minusMonths(6);
        }

        return QItem.item.regTime.after(dateTime);
    }

    private BooleanExpression searchByLike(String searchBy, String searchQuery) {
	    // 6. 
        if(StringUtils.equals("itemNM", searchBy)) {
            return QItem.item.itemNm.like("%" + searchQuery + "%");
        } else if (StringUtils.equals("createdBy", searchBy)) {
            return QItem.item.createdBy.like("%" + searchQuery + "%");
        }

        return null;
    }

    @Override
    public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable) {

        List<Item> content = queryFactory		// 7.
                .selectFrom(QItem.item)
                .where(regDtsAfter(itemSearchDto.getSearchDateType()),
                        searchSellStatusEq(itemSearchDto.getSearchSellStatus()),
                        searchByLike(itemSearchDto.getSearchBy(),
                                itemSearchDto.getSearchQuery()))
                .orderBy(QItem.item.id.desc())
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetch();

        long total = queryFactory.select(Wildcard.count).from(QItem.item)
                .where(regDtsAfter(itemSearchDto.getSearchDateType()),
                        searchSellStatusEq(itemSearchDto.getSearchSellStatus()),
                        searchByLike(itemSearchDto.getSearchBy(), itemSearchDto.getSearchQuery()))
                .fetchOne()
                ;

        return new PageImpl<>(content, pageable, total);	
        // 8.
    }
  1. ItemRepositoryCustom 상속

  2. 동적 쿼리 생성을 위해 JPAQueryFactory 클래스 사용

  3. JPAQueryFactory 생성자로 EntityManager 객체를 넣어줌

  4. 상품 판매 상태 조건이 null 일 경우, null 리턴. 결과값이 null이면 where 절에서 해당 조건은 무시됨. (판매중 or 품절 상태일때만 조회)

  5. SearchDateType 의 값에 따라서 dateTime의 값을 이전 시간의 값으로 세팅 후, 해당 시간 이후로 등록된 상품만 조회.
    (ex. searchDateType이 "1m"인 경우, dateTime의 시간을 한 달 전으로 세팅 후 최근 한 달 동안 등록된 상품만 조회)

  6. searchBy의 값에 따라서 상품명에 검색어를 포함하고 있는 상품 또는 상품 생성자의 아이디에 검색어를 포함하고 있는 상품을 조회하도록 조건 값을 반환

  7. queryFactory 를 이용해서 쿼리 생성.

    • selectFrom(QItem.item) : 상품 데이터를 조회하기 위해 QItem의 item을 지정
    • where 조건절 : BooleanExpresion 반환하는 조건문을 넣어줌.
    • offset : 데이터를 가지고 올 시작 인덱스를 지정
    • limit : 한 번에 가지고 올 최대 개수를 지정
    • fetchResult() : 조회한 리스트 및 전체 개수를 포함하는 QueryResults 를 반환. 상품 데이터 리스트 조회 및 상품 데이터 전체 개수를 조회하는 2번의 쿼리문이 실행됨
  8. 조회한 데이터를 Page 클래스의 구현체인 PageImpl 객체로 반환


사용자 정의 인터페이스 상속

이제, ItemRepository 인터페이스에서 ItemRepositoryCustom 인터페이스를 상속한다. 이렇게 하면 ItemRepository 에서 Querydsl로 구현한 상품 관리 페이지 목록을 불러오는 getAdminItemPage() 메소드를 사용할 수 있다.

ItemRepository.java

package com.shop.repository;

// ..import 생략

public interface ItemRepository extends JpaRepository<Item, Long>, QuerydslPredicateExecutor<Item>, ItemRepositoryCustom {
	// 코드 생략
}

ItemService 클래스에 상품 조회 조건과 페이지 정보를 파라미터로 받아서 상품 데이터를 조회하는 getAdminItemPage() 메소드를 추가한다. 데이터의 수정이 일어나지는 않으므로 Transactional(readOnly = true) 어노테이션을 설정했다.


ItemService.java

package com.shop.service;

// import 생략 

@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {

	// ..코드 생략
    
	@Transactional(readOnly = true)
    public Page<Item> getAdminItemPage(ItemSearchDto itemSearchDto, Pageable pageable){
        return itemRepository.getAdminItemPage(itemSearchDto, pageable);
    }
}

마지막으로 ItemController 클래스에 상품 관리 화면 이동 및 조회한 상품 데이터를 화면에 전달하는 로직을 구현했다.

ItemController.java

package com.shop.controller;

// ..import 생략

@Controller
@RequiredArgsConstructor
public class ItemController {

	// ..코드 생략
    
    @GetMapping(value = {"/admin/items", "/admin/items/{page}"})            
    // 1.
    public String itemManage(ItemSearchDto itemSearchDto,
                             @PathVariable("page") Optional<Integer> page, Model model) {
        Pageable pageable = PageRequest.of(page.isPresent() ? page.get() : 0, 3);
        // 2.
        Page<Item> items = itemService.getAdminItemPage(itemSearchDto, pageable);   
        // 3.
        model.addAttribute("items", items);                     // 4.
        model.addAttribute("itemSearchDto", itemSearchDto);     // 5.
        model.addAttribute("maxPage", 5);                       // 6.
         return "item/itemMng";
    }
  1. value에 상품 관리 화면 진입 시 URL에 페이지 번호가 없는 경우와 있는 경우 2가지를 매핑
  2. 페이징을 위해 PageRequest.of 메소드를 통해 Pageable 객체 생성. URL 경로에 페이지 번호가 있으면 해당 페이지를 조회하도록 세팅.
    페이지 번호가 없으면 0번 페이지를 조회

📌 PageRequest.of 메소드의 파라미터 의미
첫 번째 파라미터 : 조회할 때 페이지 번호
두 번째 파라미터 : 한 번에 가지고 올 데이터 수

  1. 조회 조건과 페이징 정보를 파라미터로 넘겨서 Page 객체를 반환 받음
  2. 조회한 상품 데이터 및 페이징 정보를 뷰에 전달
  3. 페이지 전환 시 기존 검색 조건을 유지한 채 이동할 수 있도록 뷰에 다시 전달
  4. 상품 관리 메뉴 하단에 보여줄 페이지 번호의 최대 개수
profile
학습하며 도전하는 것을 즐기는 개발자
post-custom-banner

0개의 댓글