느낀점
https://moolmooldoctor.firebaseapp.com/
" 설문조사 "
설문 자세히보기
아쉬운 점
자유 코멘트
4주간 만들었던 사이트를 사람들에게 처음으로 배포하는 한 주였습니다.
그동안 팀원들과 열심히 만들었던 프로젝트를 마무리하고 정비를 하면서,
제대로 된 작품을 만들어서 뿌듯하였습니다.
비록 백엔드파트에서 제가 구현한 코드는 20%정도로 비중이 많지는 않았지만,
저희 프로젝트에 있어서 거래에대한 플로우를 가장 잘 이해할 수 있었던
거래내역의 상태변화 (거래완료, 거래취소, 상대방에게 평가하기)을 무사히 구현을 하였다는 부분에서,
이번 4주간의 저의 업무는 성공이였다고 자신있게 이야기 할 수 있습니다.
또한 어플리케이션에서 DB에서 자료를 조회할 때
1. JPA문
2. JPQL @Query (SQL문법)
3. QueryDSL
등 거래내역을 조회하기위해서 동일한 결과를 얻기위해서
1, 2, 3 모두를 사용하고 JMETER를 이용해서 6000회에서부터 2,6000회까지 조회테스트를 통해서,
내가 짠 코드가 과연 좋은 코드인지 테스트하는 과정이 가장 재미있었던 부분이였습니다.
마지막으로 카페인캐시를 스스로 적용해보면서,
비록 캐시에 대해서 모든 것을 이해한 것은 아니지만 잘 돌아갈 수 있도록
캐시의 이름, 저장시간, 갯수를 정하고 각각의 메서드에서
사용하는 파라미터를 사용하여 key값을 설정하는 과정을 스스로 찾는 과정도 재미가 있었고,
눈으로 성능개선이 잘 보였기 때문에 많이 뿌듯했던 작업 이었습니다.
내가 한 일들
월요일 : QueryDSL적용 - 거래내역보기 (Dto로 조회하기) / 찜한목록 조회 수정(status값 추가)
-> 1개의 거래내역에 2개이상의 Item 엔티티를 조회해야되고, 컬럼의 수가 많기 때문에
-> 쿼리의 길이를 줄이기 위해서는 Dto형식으로 필요한 정보만 담아서,
-> 어플리케이션에서 return값을 조리해줘야했습니다.
아래는 거래내역에 사용했던 queryDSL문입니다.
거래내역
@Override
public BarterItemListDto findByBarterItems(Long itemId) {
return queryFactory
.select(new QBarterItemListDto(
item.id,
item.title,
item.itemImg,
item.contents
))
.from(item)
.where(item.id.eq(itemId))
.fetchOne();
}
마이페이지
// 성훈 - 찜하기를 한 아이템을 찾는다
@Override
public List<ItemUserResponseDto> findByMyScrabItems(Long userId) {
return queryFactory
.select(new QItemUserResponseDto(
item.id,
item.itemImg,
item.status
))
.from(item)
.join(scrab1).on(scrab1.itemId.eq(item.id))
.fetchJoin()
.distinct()
.where(scrab1.userId.eq(userId), scrab1.scrab.eq(true))
.orderBy(scrab1.modifiedAt.desc())
.limit(3)
.fetch();
}
화요일 :
1. 거래완료시 다른 사람이 신청한 거래내역을 삭제 & 신청한 거래내역을 2 -> 0으로 초기화 업데이트
2. DB에서 IMG url 리스트가 String형식으로 ","으로 되어있는 것을 파싱하여 조회할 수 잇는 방법을 구현하기
https://goguming2.tistory.com/102?category=932343
1. 신청받은 사람(SellerId)의 거래내역을 전체 조회
2. for문으로 각 거래를 파싱하여
3. if문으로 현재 거래완료한 셀러의 아이템Id와 동일한 다른사람의 거래내역이면
4. 각 다른 요청자 (bueyer)의 아이템상태를 0으로 초기화하고, 거래내역을 삭제한다.
-> JPA문으로 barterRepository.deleteById(barterId)로 삭제하는 것을 구현하고자 하였지만,
잘되지않아서 barter를 넣어서 삭제하는 방식입니다.
List<Barter> sellerBarterList = barterRepository.findAllBySellerId(sellerId);
for (Barter eachBarter : sellerBarterList) {
Long eachBarterId = eachBarter.getId();
String[] eachBarterIdList = eachBarter.getBarter().split(";");
String[] eachBuyerItemId = eachBarterIdList[0].split(",");
String eachSellerItemId = eachBarterIdList[1];
// 해당 거래내역이 아닌, 동일한 셀러아이템일 경우
if (!eachBarterId.equals(barterId) && eachSellerItemId.equals(sellerItemId)) {
for (String eachBuyerItem : eachBuyerItemId) {
Long buyerItemIds = Long.parseLong(eachBuyerItem);
Item eachBuyerItems = itemRepository.findById(buyerItemIds).orElseThrow(() -> new CustomException(NOT_FOUND_BUYER_ITEM));
// 다른 바이어 아이템들을 0으로 초기화해준다.
eachBuyerItems.statusUpdate(buyerItemIds, 0);
// 거래내역을 삭제한다.
barterRepository.delete(eachBarter);
}
}
}
수요일 : JPQL문을 이용하여 DB에서 IMG를 파싱하여 조회 / JMETER테스트 비교
-> 처리속도가 크게 개선되지 않았지만, 오류발생율이 3.85 %-> 6.12% 으로 늘어서 사용하지 않기로 함
https://goguming2.tistory.com/103?category=932188
목요일 : 거래내역 수정하기 추가 / 캐시 카페인 공부 / 배포
https://goguming2.tistory.com/104?category=932188
거래내역 수정하기 추가
@Transactional
@Caching(evict = {
@CacheEvict(cacheNames = "barterMyInfo", key = "#userDetails.userId", allEntries = true),
@CacheEvict(cacheNames = "userProfile", key = "#userDetails.userId", allEntries = true),
@CacheEvict(cacheNames = "itemInfo", allEntries = true),
@CacheEvict(cacheNames = "itemDetailInfo", key = "#userDetails.userId + '::' + #editRequestDto.itemId", allEntries = true),
@CacheEvict(cacheNames = "itemTradeCheckInfo", key = "#userDetails.userId+ '::' + #editRequestDto.itemId", allEntries = true)})
public void editBarter(EditRequestDto editRequestDto, UserDetailsImpl userDetails) {
// 수정할 거래내역 찾기
Barter barter = barterRepository.findById(editRequestDto.getBarterId()).orElseThrow(() -> new CustomException(NOT_FOUND_BARTER));
// 유저
User user = userRepository.findById(userDetails.getUserId()).orElseThrow(() -> new CustomException(NOT_FOUND_USER));
String barterItems = barter.getBarter();
String[] buyerItemList = barterItems.split(";")[0].split(",");
String sellerItem = barterItems.split(";")[1];
// 수정하기 전 아이템의 buyer의 아이템 상태를 0으로 초기화 해준다.
for (String eachItem : buyerItemList) {
Long eachItemId = Long.parseLong(eachItem);
Item eachItems = itemRepository.findById(eachItemId).orElseThrow(() -> new CustomException(NOT_FOUND_ITEM));
eachItems.statusUpdate(eachItemId, 0);
}
StringBuilder editItemIds = null;
// 수정할 아이템의 아이템 상태를 거래중 (2)로 만들어준다.
for (Long eachEditItemId : editRequestDto.getItemId()) {
Item editItems = itemRepository.findById(eachEditItemId).orElseThrow(() -> new CustomException(NOT_FOUND_ITEM));
editItems.statusUpdate(eachEditItemId, 2);
// 아이템의 아이디를 String형태로 변환하여 edit
if (editItemIds != null) {
editItemIds.append(",").append(eachEditItemId);
} else {
editItemIds = new StringBuilder(String.valueOf(eachEditItemId));
}
}
// 수정된 거래사항을 업데이트합니다.
String editBarter = editItemIds + ";" + sellerItem;
barter.editBarter(editBarter);
}
+ 포스트맨으로 @RequestParam형태로 List를 보낼 때 다음과 같이 보내 주면 된다는 점을 처음 알았습니다.
캐시공부
https://goguming2.tistory.com/105?category=932343
https://goguming2.tistory.com/106?category=932343
금요일 : 카페인 캐시 적용하기
https://goguming2.tistory.com/107?category=932188
카페인 캐시를 적용하고 JMETER테스트를 통해서 동일한 게시판을 여러번 요청을 할 경우,
어플리케이션에서 복잡한 계산을 하지 않고, 캐시에 저장해 두었다고 보내 줄 수 있어서
성능향상이 크게 되었습니다.
마이페이지 JMETER 테스트
1번째 해석
전송속도로 보정해서 비교를 한 이유?
단순히 비슷하게 숫자를 맞춰서 평균 시간을 계산해 본거에요
거래내역 JMETER 테스트
< 1만회 >
100 - (5000*7 / 58696<- DSL평균속도 * 100<- 백분율) 해서 DSL에비해서 40% 빨라 졌네요.
거래내역 JMETER 테스트
< 2.6만회 >
오류는 58.5% -> 38.6% 으로 34% 감소하였습니다.
장난삼아서 계산해보았는데,
거래내역에서 2.6만번 조회했을 때 단순 비교로 평균속도가
JPA 93080.5 기준으로 (93초 : 1분 33초)
DSL (DTO로 조회했을 경우)
61426.3 -> 32.96% (1분 2초)
캐시 (DSL + 캐시)
7686.4 -> 96.78% (7.6초)
으로 단축한거네요
토요일 : api요청 주소 수정, 낙관적 락 적용 / 거래완료시 알람 중복생성 및 거래취소시 알람 삭제 구현
거래완료 알람을 상대 userId으로 저장하기
1. 내 포지션이 buyer와 seller중에 하나일 경우에 상대userId로 알람이 저장이 되어있기 때문에,
2. 내가 buyer일경우 sellerId와 현재 거래내역Id로 모든알람을 가져옵니다.
3. 이미 알람이 존재한다면 stomp으로만 메시지를 보내주고, notificationRepository에 추가로 저장하지 않습니다.
4. else 만약 그렇지 않는다면 알람정보를 상대바의 userId로 내 닉네임 정보를 담아서 저장합니다.
List<Notification> notificationCeck;
if (myPosition.equals("buyer")) {
notificationCeck = notificationRepository.findAllByUserIdAndChangeId(myBarter.getSellerId(), barterId);
} else {
notificationCeck = notificationRepository.findAllByUserIdAndChangeId(myBarter.getBuyerId(), barterId);
}
// 이미 저장된 알람이면 스톰프 메시지만 보낸다.
if (notificationCeck.size() >= 1) {
for (Notification notification : notificationCeck) {
sendTradeMessage(myBarter, myPosition, notification);
}
} else {
// 알림 내역 저장
Notification notification = notificationRepository.save(Notification.createOfBarter(myBarter, user.getNickname(), myPosition, "Barter"));
// 상대방의 sup주소로 전송
sendTradeMessage(myBarter, myPosition, notification);
}
// 내게 거래완료 메시지 보내기
sendMyMessage(barterId, myBarter, myPosition);
return new BarterStatusDto(opponentTrade, false, myBarter.getStatus());
}
거래취소시 상대방과 거래내역에 대한 알람을 일괄적으로 삭제
String myPosition;
if (myBarter.getBuyerId().equals(userId)) {
myPosition = "buyer";
// 유저가 셀러라면 셀러거래완료를 true로 변경한다.
} else {
myPosition = "seller";
}
// 거래취소시 저장된 알림도 삭제된다.
List<Notification> notificationCeck;
if (myPosition.equals("buyer")) {
notificationCeck = notificationRepository.findAllByUserIdAndChangeId(myBarter.getSellerId(), barterId);
notificationRepository.deleteAll(notificationCeck);
} else {
notificationCeck = notificationRepository.findAllByUserIdAndChangeId(myBarter.getBuyerId(), barterId);
notificationRepository.deleteAll(notificationCeck);
}
어려웠던 점
사실 간단한 건데, 알람에 저장되어 있는
ENUM 타입이 BARTER(거래중)가 아닌 FINISH(거래완료)인데,
이 부분을 잠시 잊어버리고 있어서, WHERE절에 조건을 잘못 조회해서 3시간 째 삽질하고 있었습니다...ㅠㅠㅠ
@Override
public List<Notification> findAllByUserIdAndChangeId(Long userId, Long changeId) {
return queryFactory
.selectFrom(notification)
.where(notification.userId.eq(userId),
notification.changeId.eq(changeId),
notification.type.eq(FINISH))
.fetch();
}
https://reiphiel.tistory.com/entry/understanding-jpa-lock
https://www.notion.so/b308c025e9954cadb9fdc19d86bd411f?v=b6b1eb52cfe24793b97163d7b4825b07
api주소를 api/item 이였던 부분은 item/ 혹은 user/barter 이런방향으로 수정하였습니다.
내가 해야하는 일
1. 카페인 캐시 정교화하기
2. 부족한 부분 찾아서 채우기
3. 버그수정
4. 발표준비 돕기
5. 개인 면접자료 만들기
'일기 > 항해99' 카테고리의 다른 글
[항해 99 6기] 이력서를 작성 시작 (0) | 2022.06.05 |
---|---|
[항해 99 6기] 최종 발표회를 마치며 (0) | 2022.06.03 |
[항해99 6기] 실전프로젝트 4주차 - 느낀점 (0) | 2022.05.23 |
[항해99 6기] 실전프로젝트 인기제품 보여주기 구현하기 (0) | 2022.05.17 |
[항해99 6기] 실전프로젝트 3주차 - 느낀점 (0) | 2022.05.15 |