서로의 카드를 교환하는 로직을 만드는 중에 내가 생각할 수 있는 예외를 방어하기 위해
생각 또 생각~ Spinnin' 'round and 'round
@Test
@DisplayName("카드 교환 전 내가 가지고 있는 카드 처리 - 카드 적은 경우")
void beforeCardTradeSimple() throws Exception {
//given
Long accountId = 1L;
Long[] cardIds = {2L, 24L, 24L, 24L, 67L};
for (Long cardId : cardIds) {
AccountCard findCard = query
.selectFrom(accountCard)
.where(
accountCard.account().id.eq(accountId),
accountCard.card().id.eq(cardId)
).fetchOne();
if (findCard == null || findCard.getCount() < 1) {
throw new IllegalArgumentException("보유하지 않았거나 수량이 부족한 카드입니다. cardId=" + cardId);
}
if (findCard.decreaseCount()) {
accountCardRepository.delete(findCard);
}
}
}
이 코드는 교환 로직 중 첫번째!
내가 가진 카드를 교환 게시판(?)에 등록하는 코드다.
DataJpaTest 이기 때문에 쿼리만 작성.
다만 아직 추가로 개발해야 할 부분은 역시.. 교환 게시판 엔티티 이다. 여기서는 단순하게 내가 가진 카드를 제대로 등록했는지 판단하고 해당 카드를 갯수에 맞게 마이너스 시키고 0이 되면 삭제하는 일을 한다.
@Test
@DisplayName("카드 교환 전 내가 가지고 있는 카드 처리 - 카드 많은 경우")
void beforeCardTrade() throws Exception {
//given
Long accountId = 1L;
Long[] cardIds = {2L, 24L, 24L, 24L, 67L};
// 1. 요청 수량을 세서 Map<Long cardId, Long count> 생성 ex) {2=1, 24=3, 67=1}
Map<Long, Long> requestedCounts = Arrays.stream(cardIds)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// 2. 필요한 카드들에 대해 AccountCard를 한 번에 조회
List<AccountCard> accountCards = query
.selectFrom(accountCard)
.where(
accountCard.account().id.eq(accountId),
accountCard.card().id.in(requestedCounts.keySet())
)
.fetch();
// 3. 조회된 AccountCard들을 Map<cardId, AccountCard> 형태로 변환
Map<Long, AccountCard> ownedCardMap = accountCards.stream()
.collect(Collectors.toMap(
ac -> ac.getCard().getId(),
Function.identity()
));
for (Map.Entry<Long, Long> entry : requestedCounts.entrySet()) {
Long cardId = entry.getKey();
Long requiredCount = entry.getValue();
AccountCard owned = ownedCardMap.get(cardId);
//4 .요청한 카드 ID에 대해 보유 수량 검증
if (owned == null || owned.getCount() < requiredCount) {
throw new IllegalArgumentException("보유하지 않았거나 수량이 부족한 카드입니다. cardId=" + cardId);
}
// 5. 수량 차감 및 삭제 처리
for (int i = 0; i < requiredCount; i++) {
if (owned.decreaseCount()) {
accountCardRepository.delete(owned);
break;
}
}
}
}
와웅... 이 코드를 처음 봤을 때 눈이 휘둥그레 졌다
이렇게 많은 작업이 일어난다고...??? 저건 또 무슨 코드야... ㅠㅠ
stream().collect()를 사용해서 map을 순식간에 만드는 코드는 처음 접하기 때문에 꼬치꼬치 캐물었다.
// 1. 요청 수량을 세서 Map<Long cardId, Long count> 생성 ex) {2=1, 24=3, 67=1}
Map<Long, Long> requestedCounts = Arrays.stream(cardIds)
.collect(Collectors.groupingBy(
Function.identity(), Collectors.counting()
));
우선 이 코드
Function.identity()는 해당 값을 그대로 사용하겠다는 말.
즉, cardIds에 들어있던{2L, 24L, 24L, 24L, 67L}이 값들을 그대로key로 사용하겠다는 말이고,
여기서 뒤에values는Collectors.counting()으로 각 값이 몇개씩 있는지 확인해서 쓰겠다는 말이다.
최종적으로 코드의 주석에도 달아놨지만 {2=1, 24=3, 67=1} 이런 형태의 Map이 생기게 되는 것!
그렇다면?? 그 밑에 보이는 이 코드도 아주 비슷하게 작동한다. 이번엔 Collectors.toMap()으로 즉시 map으로 변환
// 3. 조회된 AccountCard들을 Map<cardId, AccountCard> 형태로 변환
Map<Long, AccountCard> ownedCardMap = accountCards.stream()
.collect(Collectors.toMap(
ac -> ac.getCard().getId(),
Function.identity()
));
ac.getCard().getId()를 map의key로 사용하고
Function.identity()->ac(accountCard)객체를 그대로value로 사용하겠다는 말
그 이후의 코드는 Map을 순회하면서 AccountCard의 count 값을 조절하고 0이 된다면 해당 엔티티를 삭제하는 로직을 수행한다.
내가 작성한 코드

GPT가 제안한 코드


실제로 내가 작성한 코드는 cardIds 배열만큼 순회하기 때문에 select 쿼리가 매번 발생하고 decreaseCount() 메서드에서 update 쿼리, accountCardRepository.delete(findCard) 에서 delete 쿼리가 발생할 것이기 때문에 쿼리가 상당히 많이 발생한다.
그리고 만약에 교환하고자 하는 카드가 엄청나게 많다면..??
물론 사용자가 많이 가지고 있는 카드를 사용해서 아직 가지지 못한 카드를 원하겠지만 막 100장씩 교환하거나 그럴까...??
라는 생각이 들기도 하지만 사용자는 내가 원하는대로 움직여주지 않는다.. ㅎㅎ 그리고 100장씩 교환하고 싶은 사람이 있을수도 있지!!
ChatGPT가 제안한 코드를 사용하기로 했다. 내 입맛에 맞게 조금 가다듬고 코드를 완벽하게 내것으로 만들기 위해 코드를 계속 읽어보고 구글링도 하면서 해당 코드가 어떻게 동작하는지 다른 사람들은 어떻게 사용했는지 파악했다.
일단 가장 첫번째 로직인 교환할 카드 등록하기 중에 내 데이터를 컨트롤하는 로직을 완성했다.
이제 다음으로 교환 상대와 카드 맞바꿔서 등록하기 로직을 만들고...
그 다음으로 진짜 교환 게시판 엔티티를 만들어서 데이터를 저장하고 해당 데이터로 교환을 진행하는 로직을 만들면 될 것 같다!
화면을 만들지 않고 머릿속으로 상상한 흐름으로 코드를 만드려니 좀 쉽지 않긴 하다...
피그마나 이런쪽도 좀 공부해서 화면부터 좀 만들긴 해야겠다 ㅠㅠ
아 추가로 Collectors.toMap() 같은 경우에는 상대방과 카드 교환하는 로직에도 자주 써먹을 수 있을 것 같다.
객체에 접근해서 getId() 하기에는 List<AccountCard> 나 List<Card>, List<Account> 를 돌면서 값을 찾아와야 할 것 같아서 Map에서 get(key) 를 사용하면 아주 편하게 값을 찾을 수 있을 것으로 보인다.
GPT와 함께 내 로직도 좀 튼튼? 하게 만들었고 새로운 접근방법도 익힐 수 있어서 좋았다.