판매자 정보 조회 및 상품 목록 API 구현

뚜우웅이·2025년 5월 29일

캡스톤 디자인

목록 보기
31/35

User

DTO

    @Builder
    @Schema(description = "판매자 상세 응답 정보")
    public record SellerDetailResponse(
            @Schema(description = "판매자 ID", example = "1")
            Long sellerId,

            @Schema(description = "이름", example = "홍길동")
            String name,

            @Schema(description = "가입 날짜", example = "2025-01-01T09:00:00")
            LocalDateTime joinDate,

            @Schema(description = "총 판매 중인 상품 수", example = "3")
            int totalActiveProductCount,

            @Schema(description = "총 판매 완료된 상품 수", example = "7")
            int totalSoldProductCount
    ) {
        public static SellerDetailResponse from(User seller, int totalActiveProductCount, int totalSoldProductCount) {
            return SellerDetailResponse.builder()
                    .sellerId(seller.getId())
                    .name(seller.getName())
                    .joinDate(seller.getCreatedDate())
                    .totalActiveProductCount(totalActiveProductCount)
                    .totalSoldProductCount(totalSoldProductCount)
                    .build();
        }
    }

상품 목록들은 따로 쿼리를 통해 가져온다.

UserController

  @Operation(summary = "판매자 기본 정보 조회", description = "판매자의 기본 정보와 통계를 조회합니다.")
    @GetMapping("/{sellerId}/seller-info")
    public ResponseEntity<ResponseDTO<UserDto.SellerDetailResponse>> getSellerInfo(
            @Parameter(description = "조회할 사용자 ID", required = true) @PathVariable Long sellerId) {

        UserDto.SellerDetailResponse response = userService.getSellerInfo(sellerId);
        return ResponseEntity.ok(ResponseDTO.success(response));
    }

    @Operation(
            summary = "판매자 상품 목록 조회",
            description = "판매자의 상품 목록을 상태별로 조회합니다. 상태를 지정하지 않으면 모든 상품을 조회합니다." +
                    "예시 -> GET /api/users/123/products?status=ACTIVE&sort=createdDate,desc"
    )
    @GetMapping("/{sellerId}/products")
    public ResponseEntity<ResponseDTO<Page<ProductDto.ProductDetailResponse>>> getSellerProducts(
            @Parameter(description = "판매자 ID", required = true)
            @PathVariable Long sellerId,

            @Parameter(description = "상품 상태 (ACTIVE: 판매중, SOLD_OUT: 판매완료, 미지정시 전체)")
            @RequestParam(required = false) ProductStatus status,

            @Parameter(description = "페이지 정보 (기본: 10개씩, 최신순)")
            @PageableDefault(size = 10, sort = "createdDate", direction = Sort.Direction.DESC)
            Pageable pageable,

            @Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails) {

        Long currentUserId = userDetails != null ? userDetails.getUserId() : null;
        Page<ProductDto.ProductDetailResponse> response = productQueryService.getProductsBySeller(
                sellerId, status, pageable, currentUserId);

        return ResponseEntity.ok(ResponseDTO.success(response));
    }

판매자의 기본 정보를 가져오는 api와 판매자의 상품 목록을 가져오는 api를 분리하여 사용한다.
판매자의 상품 목록을 가져올 때는 상품 상태에 따라 가져올 수 있다.

UserService

   // 판매자 기본 정보 조회
    public UserDto.SellerDetailResponse getSellerInfo(Long sellerId) {
        User seller = getUser(sellerId);

        int activeCount = (int) productRepository.countBySellerIdAndStatus(sellerId, ProductStatus.ACTIVE);
        int soldCount = (int) productRepository.countBySellerIdAndStatus(sellerId, ProductStatus.SOLD_OUT);

        return UserDto.SellerDetailResponse.from(seller, activeCount, soldCount);
    }

판매자의 프로필 정보를 조회하면서, 현재 판매 중인 상품 수와 판매 완료된 상품 수를 함께 반환한다.

Product

ProductRepository

    /**
     * 판매자의 특정 상태 상품을 이미지와 함께 조회 (Fetch Join 사용)
     * N+1 문제 해결을 위해 이미지를 함께 로드
     */
    @Query("select distinct p from Product p " +
            "left join fetch p.images pi " +
            "left join fetch p.seller " +
            "where p.seller.id = :sellerId " +
            "and p.status = :status")
    Page<Product> findBySellerIdAndStatusWithImages(@Param("sellerId") Long sellerId,
                                                    @Param("status") ProductStatus status,
                                                    Pageable pageable);

    // 판매자의 모든 상품을 이미지와 함께 조회 (상태 무관)
    @Query("select distinct p from Product p " +
            "left join fetch p.images pi " +
            "left join fetch p.seller " +
            "where p.seller.id = :sellerId")
    Page<Product> findBySellerIdWithImages(@Param("sellerId") Long sellerId, Pageable pageable);

    // 판매자의 전체 상품 수 조회 (상태 무관)
    @Query("select count(p) from Product p where p.seller.id = :sellerId")
    long countBySellerId(@Param("sellerId") Long sellerId);
  • findBySellerIdAndStatusWithImages

    • 판매자 ID + 상품 상태로 필터
    • Product와 연관된 images, sellerfetch join으로 한 번에 로드
    • distinct를 써서 중복 제거 (fetch join에서 생길 수 있음)
    • N+1 문제 해결:
      - 예전엔 상품 10개 조회 후 이미지 10번 쿼리
      - 이제는 한 번에 조인해서 가져옴
  • findBySellerIdWithImages

    • 위와 거의 동일하지만 status 조건 없음
    • 판매자의 모든 상품을 이미지와 함께 조회
    • 이 메서드도 Page<Product> 타입이라 페이징 처리 가능
  • countBySellerId

    • 해당 판매자가 등록한 전체 상품 개수를 반환
    • 이미지 조인 없이 숫자만 계산하기 때문에 쿼리 성능 좋음

ProductService

  // 판매자별 상품 조회
    public Page<ProductDto.ProductDetailResponse> getProductsBySeller(
            Long sellerId,
            ProductStatus status,
            Pageable pageable,
            Long currentUserId) {

        // 판매자 존재 확인
        validateSellerExists(sellerId);

        Page<Product> productPage;
        if (status != null) {
            productPage = productRepository.findBySellerIdAndStatusWithImages(sellerId, status, pageable);
        } else {
            productPage = productRepository.findBySellerIdWithImages(sellerId, pageable);
        }

        return productPage.map(product -> convertToDetailResponse(product, currentUserId));
    }

    // 판매자별 상품 수 조회
    public int getProductCountBySeller(Long sellerId, ProductStatus status) {
        validateSellerExists(sellerId);

        if (status != null) {
            return (int) productRepository.countBySellerIdAndStatus(sellerId, status);
        } else {
            return (int) productRepository.countBySellerId(sellerId);
        }
    }

    private void validateSellerExists(Long sellerId) {
        if (!userRepository.existsById(sellerId)) {
            throw new UserException.UserNotFoundException(sellerId);
        }
    }
  • getProductsBySeller
    • 해당 판매자가 존재하는지 확인
    • 판매자의 모든 상품을 조회
    • 조회한 상품 리스트를 DTO 형태로 변환
  • getProductCountBySeller
    • 해당 판매자가 존재하는지 확인
    • statusnull이면 전체 개수 조회
    • 아니면 해당 상태만 필터
  • validateSellerExists
    • 해당 sellerId가 DB에 없는 경우, 커스텀 예외를 던져서 처리
profile
공부하는 초보 개발자

0개의 댓글