떠오르는 추천 교환을 구현하기위해서
신청한 거래내역이 많은 Barte Top5를 보여주는 것을 구현하고자 하였습니다.
따라서 Barter의 Status값이 1( 거래신청 )인 내역중에서 SellerItem을 보여주면됩니다.
하지만 Seller의 ItemId는 barter라는 컬럼에서 파싱되어있어서,
Jpa문으로 조회하기가 어렵다는 문제가 발생하였습니다.
따라서 처음으로 접근한 방법으로는
"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
2. STACK
따라서 평소에 사용하지 않았지만,
상대적으로 구현하기 간편해 보이는 HashMap을 선택하였습니다.
Key값과 Value값이 1:1구조로 가지고있어서,
Key : SellerItemId
Value : 거래신청 횟수
https://crazykim2.tistory.com/587
활용한 코드 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
활용한 코드 2 - Value값 내림차순 ( 람다식 이용 )
자바에서 HashMap에 저장한 데이터를
Collections.sort메서드를 이용하여 값(Value)으로 정렬하는 방법
코드설명
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
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
2. 람다식을 이용하여 내림차순으로 정렬합니다.
https://math-coding.tistory.com/185
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": "맛있겠다,우와앙"
}
]
'일기 > 항해99' 카테고리의 다른 글
[항해99 6기] 실전 프로젝트 5 주차 - 느낀점 (0) | 2022.05.30 |
---|---|
[항해99 6기] 실전프로젝트 4주차 - 느낀점 (0) | 2022.05.23 |
[항해99 6기] 실전프로젝트 3주차 - 느낀점 (0) | 2022.05.15 |
[항해99 6기] 실전프로젝트 2주차 - 느낀점 (0) | 2022.05.08 |
[항해99 6기] 실전프로젝트 2주차 - 상대방 평가 (0) | 2022.05.07 |