본문 바로가기
일기/항해99

[항해99 6기] 실전프로젝트 인기제품 보여주기 구현하기

by 고구밍 2022. 5. 17.

 

떠오르는 추천 교환을 구현하기위해서 

신청한 거래내역이 많은 Barte Top5를 보여주는 것을 구현하고자 하였습니다.

 

따라서 Barter의 Status값이 1( 거래신청 )인 내역중에서 SellerItem을 보여주면됩니다.

하지만 Seller의 ItemId는 barter라는 컬럼에서 파싱되어있어서,

Jpa문으로 조회하기가 어렵다는 문제가 발생하였습니다.

 

셀러아이템Id 9가 4번 신청받았다.

 

따라서 처음으로 접근한 방법으로는

"Barter에 SellerId (판매자)의 언급된 횟수 순으로 받아서 간접적으로 신청을 많이한 아이템을 알 수 있지 않을까?"

 

 

-> sql문의 문법적인 문제가 발생하여 조회가 되지 않앗습니다.

<오류내용>

더보기

2022-05-17 17:03:39.901 ERROR 14588 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : Expression #1 of ORDER BY contains aggregate function and applies to the result of a non-aggregated query
2022-05-17 17:03:39.937 ERROR 14588 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: could not extract ResultSet; nested exception is org.hibernate.exception.GenericJDBCException: could not extract ResultSet] with root cause

java.sql.SQLException: Expression #1 of ORDER BY contains aggregate function and applies to the result of a non-aggregated query
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.28.jar:8.0.28]

 

따라서 아래와 같이 단순히 barter의 status(거래상태)가 1(거래신청)인 리스트를

최신에 가입한 SellerId를 내림차순으로 조회하는 JPQL의 @Query를 만들었습니다. 

 

package com.sparta.mulmul.service;

import com.sparta.mulmul.dto.item.ItemStarDto;
import com.sparta.mulmul.model.Barter;
import com.sparta.mulmul.model.Item;
import com.sparta.mulmul.repository.BarterRepository;
import com.sparta.mulmul.repository.ItemRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
@RequiredArgsConstructor
public class ItemStarService {

    private final ItemRepository itemRepository;
    private final BarterRepository barterRepository;


    public List<ItemStarDto> hotItem() {
        int status = 1;
        List<Barter> barterList = barterRepository.findAllByBarter(status);
        List<ItemStarDto> itemStarDtoList = new ArrayList<>();

        Map<String, Integer> map = new HashMap<>();
        int cnt = 0;
        for (Barter eachBarter : barterList) {
            String sellerItem = eachBarter.getBarter().split(";")[1];
            Integer count = map.get(sellerItem);
            if (count == null) {
                map.put(sellerItem, 1);
            } else {
                map.put(sellerItem, count + 1);
            }
            cnt++;
            // 1000번째에 정지 - 페이징처리 개선시 없엘 예정
            if (cnt == 1000) {
                break;
            }
        }

        List<String> listKeySet = new ArrayList<>(map.keySet());
        // 내림차순
        Collections.sort(listKeySet, (value1, value2) -> (map.get(value2).compareTo(map.get(value1))));

        cnt = 0;
        for (String key : listKeySet) {
            System.out.println("key : " + key + " , " + "value : " + map.get(key));
            Long sellerItemId = Long.parseLong(key);
            Item sellerItem = itemRepository.findById(sellerItemId).orElseThrow(
                    () -> new IllegalArgumentException("아이템 정보가 없습니다."));

            ItemStarDto itemStar = new ItemStarDto(
                    sellerItem.getId(),
                    sellerItem.getItemImg().split(",")[0],
                    sellerItem.getTitle(),
                    sellerItem.getContents()
            );
            itemStarDtoList.add(itemStar);
            cnt++;
            if (cnt == 5) {
                break;
            }
        }
        return itemStarDtoList;
    }
}

 

그렇게 조회한 List<Barter>는 for each문을 통해서 인기있는 SellerItemId를 탐색합니다.

이때 사용한 HashMap을 통해서 Key값을 sellerId로, Value값을 신청받은 횟수를 카운팅합니다.

 

아쉽게도, JPQL의 @Query에서는 페이징 처리가 되지않아서, cnt라는 값을 통해서

전체리스트에서 최대 1000회 작업하는 것으로 하였습니다.

-> 차후 QueryDSL을 도입하게 된다면, 페이징과 조회 조건문을 추가하여 개선할 예정입니다.

 


 

처음에는 List형식으로 받고 싶었지만

 

{1,2,2,2,2,1,1,2,2,1,1,2,1,1,2} 이런 형식에서 리스트 내에서

1. 불특정 Id에대해서 갯수를 count하고

2. count수를 내림차순하여 -> 해당 Id값을 출력

 

으로 도출할 수 있는 코드가 떠오르지가 않았습니다.

 

다른 방법으로는 알고리즘 공부하면서 배운 새로운 기술들을 적용 해 볼까 했지만,

1. HEAP

https://coding-factory.tistory.com/603

 

[Java] PriorityQueue(우선순위 큐) 클래스 사용법 & 예제 총정리

우선순위 큐(Priority Queue)란? 일반적으로 큐는 데이터를 일시적으로 쌓아두기 위한 자료구조로 스택과는 다르게 FIFO(First In First Out)의 구조 즉 먼저 들어온 데이터가 먼저 나가는 구조를 가집니다

coding-factory.tistory.com

2. STACK

https://min-zero.tistory.com/entry/python-%ED%81%AC%EB%A0%88%EC%9D%B8-%EC%9D%B8%ED%98%95%EB%BD%91%EA%B8%B0-%EA%B2%8C%EC%9E%84-2019-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EA%B2%A8%EC%9A%B8-%EC%9D%B8%ED%84%B4%EC%8B%AD

 

[python] 크레인 인형뽑기 게임 (2019 카카오 개발자 겨울 인턴십)

문제 설명 게임개발자인 죠르디는 크레인 인형뽑기 기계를 모바일 게임으로 만들려고 합니다. 죠르디는 게임의 재미를 높이기 위해 화면 구성과 규칙을 다음과 같이 게임 로직에 반영하려고

min-zero.tistory.com

 

따라서 평소에 사용하지 않았지만,

상대적으로 구현하기 간편해 보이는 HashMap을 선택하였습니다.

 

 

Key값과 Value값이 1:1구조로 가지고있어서,

Key : SellerItemId

Value :  거래신청 횟수

 

 

https://crazykim2.tistory.com/587

 

[JAVA] HashMap의 개념 및 사용법 정리

안녕하세요 이번 포스팅에서는 HashMap에 대해서 알아보겠습니다 목차 HashMap이란? HashMap 선언하기 HashMap 값 추가하기 HashMap 값 삭제하기 HashMap 크기 구하기 HashMap 값 출력하기 HashMap이란? HashMap은..

crazykim2.tistory.com

 


 

 

 

 

활용한 코드 1 - 카운팅

반복문을 이용하여

HashMap 객체에 key(원소), value(중복 횟수) 형식으로 저장하고 출력하는 방법입니다.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class ArrayListDupCheck {
public static void main(String[] args) {
// ArrayList 준비
ArrayList<String> list = new ArrayList<>(Arrays.asList("A", "B", "C", "A", "B", "A"));
System.out.println("원본 : " + list); // [A, B, C, A, B, A]
// ArrayList 원소 빈도수를 Map에 저장
Map<String, Integer> map = new HashMap<String, Integer>();
for (String str : list) {
Integer count = map.get(str);
if (count == null) {
map.put(str, 1);
} else {
map.put(str, count + 1);
}
}
// // Map 출력
for (String key : map.keySet()) {
System.out.println(key + " : " + map.get(key));
}
}
}

출처: https://hianna.tistory.com/572 [어제 오늘 내일]

 

https://hianna.tistory.com/572

 

[Java] ArrayList 중복 값 개수 세기

Collections.frequency() 이용하기 Map 이용하기 1. Collections.frequency() 이용하기 public static int frequency(Collection<?> c, Object o) 위 메소드는 첫번째 파라미터로 전달된, Collection 객체에..

hianna.tistory.com

 

 

 

활용한 코드 2 - Value값 내림차순 ( 람다식 이용 )

자바에서 HashMap에 저장한 데이터를

Collections.sort메서드를 이용하여 값(Value)으로 정렬하는 방법

https://junghn.tistory.com/entry/JAVA-Map%EC%97%90%EC%84%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EA%B0%92Value%EA%B8%B0%EC%A4%80%EC%9C%BC%EB%A1%9C-%EC%A0%95%EB%A0%AC%EB%B0%A9%EB%B2%95-%EC%98%A4%EB%A6%84%EC%B0%A8%EC%88%9C-%EB%82%B4%EB%A6%BC%EC%B0%A8%EC%88%9C

 

[JAVA] Map에서 데이터를 값(Value)기준으로 정렬방법 (오름차순, 내림차순)

자바에서 HashMap에 저장한 데이터를 Collections.sort메서드를 이용하여 값(Value)으로 정렬하는 방법을 알아보겠습니다. import java.util.ArrayList; import java.util.Collections; import java.util.HashMap;..

junghn.tistory.com

 

 

코드설명


1. 거래신청 횟수 구하기

 Map<String, Integer> map = new HashMap<>();
        int cnt = 0;
        for (Barter eachBarter : barterList) {
            String sellerItem = eachBarter.getBarter().split(";")[1];
            Integer count = map.get(sellerItem);
            if (count == null) {
                map.put(sellerItem, 1);
            } else {
                map.put(sellerItem, count + 1);
            }
            cnt++;
            // 1000번째에 정지 - 페이징처리 개선시 없엘 예정
            if (cnt == 1000) {
                break;
            }
        }

1. barterList를 eachBarter에 1개씩 대입

2. String 형식으로 sellerItemId를 파싱되어 있는 내용에서 split(";")[1]을 하여서 추출한다.

ex) "1,2;4" -> "4"

3. Integer count를 정의해서 map에서 추출한 Id의 값을 가져옵니다.

4. if 만약 값이 null이면 map안에 값얼 넣어줍니다.

5. else 이미 값이 들어가있다면, 값 + 1으로 카운팅을 높여줍니다.

-> 이것을 cnt값이 1000까지 반복합니다.

 

https://wakestand.tistory.com/112

 

자바 Map 사용법부터 출력까지

일단 Map의 특징을 먼저 알아보자면 Map은 선언 시 로 값을 넣는다 Key와 Value는 한 쌍으로 Key로 식별하고 Value에 사용할 값을 넣는 식이다 여기서 Key는 중복이 불가능하고 동일한 Key 값으로 값을 넣

wakestand.tistory.com

 

2. 내림차순으로 정렬 후 5개를 추출하기

List<String> listKeySet = new ArrayList<>(map.keySet());
// 내림차순
Collections.sort(listKeySet, (value1, value2) -> (map.get(value2).compareTo(map.get(value1))));

cnt = 0;
for (String key : listKeySet) {
    System.out.println("key : " + key + " , " + "value : " + map.get(key));
    Long sellerItemId = Long.parseLong(key);
    Item sellerItem = itemRepository.findById(sellerItemId).orElseThrow(
            () -> new IllegalArgumentException("아이템 정보가 없습니다."));

    ItemStarDto itemStar = new ItemStarDto(
            sellerItem.getId(),
            sellerItem.getItemImg().split(",")[0],
            sellerItem.getTitle(),
            sellerItem.getContents()
    );
    itemStarDtoList.add(itemStar);
    cnt++;
    if (cnt == 5) {
        break;
    }
}

 

1. 위의 map을 listKeySet에 전체 출력을 합니다.

https://tychejin.tistory.com/31

 

[Java] Map 전체 출력(entrySet, keySet, Iterator)

Map에 값을 전체 출력하기 위해서는 entrySet(), keySet() 메소드를 사용하면 되는데 entrySet() 메서드는 key와 value의 값이 모두 필요한 경우 사용하고, keySet() 메서드는 key의 값만 ..

tychejin.tistory.com

 

2. 람다식을 이용하여 내림차순으로 정렬합니다.

https://math-coding.tistory.com/185

 

[Java] 람다식이란?

이 글은 "자바 온라인 스터디"에서 공부한 내용을 정리하였습니다. 람다식이란? 람다식이란 함수명을 선언하고 사용하는 것이 아닌 식별자 없이 실행가능한 함수입니다. 절차형 프로그래밍, 객

math-coding.tistory.com

 

3. forEach문을 통하여 key값을 Long.parseLong을 통하여 Long타입의 ItemId으로 변환합니다.

4. 변환된 ItemId를 itemRepository에 조회합니다.

5. ItemStarDto에 조회한 Item의 정보를 넣어줍니다.

6. for문을 돌면서 List<ItemStarDto> itemStarDtoList안에 넣어줍니다.

7. 이 작업으로 5번 반복하면 끝납니다.

 


이렇게 하여서 아래와 같은 결과를 얻었습니다.

 

포스트맨 실행 결과

[
    {
        "itemId": 9,
        "image": "https://gotgam.s3.ap-northeast-2.amazonaws.com/8d273e31-14ef-40f2-a4b9-fad96bebc658.jpeg",
        "title": "우아아아,맛있",
        "contensts": "맛있겠다,우와앙"
    },
    {
        "itemId": 8,
        "image": "https://gotgam.s3.ap-northeast-2.amazonaws.com/cdec4c4a-a049-4a59-b8d7-7cf6326e8201.jpeg",
        "title": "우아아아,맛있",
        "contensts": "맛있겠다,우와앙"
    },
    {
        "itemId": 25,
        "image": "https://gotgam.s3.ap-northeast-2.amazonaws.com/7d6dcfa0-085c-4466-9d59-39e6782c56a5.png",
        "title": "우히히히,맛있",
        "contensts": "맛있겠다,우와앙"
    },
    {
        "itemId": 26,
        "image": "https://gotgam.s3.ap-northeast-2.amazonaws.com/5c3884ba-96f2-4b36-9060-74e0d98f6a30.png",
        "title": "우히히히,맛있",
        "contensts": "맛있겠다,우와앙"
    },
    {
        "itemId": 27,
        "image": "https://gotgam.s3.ap-northeast-2.amazonaws.com/c592961e-eefd-4170-994e-c6507bc2b752.png",
        "title": "우히히히,맛있",
        "contensts": "맛있겠다,우와앙"
    }
]