일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- WebMvcTest
- Spring
- FNN
- python3
- SpringBoot
- 로지스틱회귀
- 정렬
- 쉬운딥러닝
- 백준
- Spring Data JPA
- 알고리즘
- BOJ
- 그리디
- 코딩
- web
- 스택
- C++
- withmockuser
- 책리뷰
- 신경망기초
- Backend
- 딥러닝
- testing
- DP
- 에라토스테네스의체
- REST API
- responsebody
- RequestBody
- PS
- Andrew Ng
Archives
- Today
- Total
꾸준히하자아자
카카오 쇼핑하기 클론프로젝트 #4 본문
728x90
728x90
이번엔 좋아요 기능을 추가해봤다!!
기존 er diagram에서 테이블을 하나 추가해줬다.
사용자가 , 어떤 상품마다 좋아요를 눌렀는지에 대한 데이터를 저장해줘야한다고 생각했기 때문에 Like 라는 테이블을 따로 만들어주었다.
package com.example.kakao.like;
//import 생략..
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name="like_tb", indexes = {
@Index(name = "like_user_id_idx", columnList = "user_id"),
@Index(name = "like_product_id_idx", columnList = "product_id")
})
public class Like {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
@OneToOne
private Product product;
@Builder
public Like(int id, User user, Product product){
this.id = id;
this.user = user;
this.product = product;
}
}
사용자는 여러 상품에 좋아요를 누를 수 있으니 ManyToOne으로 매핑해줬고 상품 하나에 좋아요를 두번 누를 순 없기때문에 (...좋아요 한번 누르면 취소하고싶어도 못함ㅋ ㅋ) OneToOne으로 매핑해줬다. 또 불필요한 쿼리가 생성되지 않게 LazyLoading 으로 설정했다.
내가 설계한 REST API
- 좋아요를 추가하는 기능에는 딱히 응답body가 필요없을 거라고 생각해서 정상적으로 추가됐으면 null을 출력하는 형식으로 작성했다.
- 좋아요 목록을 조회하는 기능에는 userId로 좋아요 목록을 조회할 수 있게 했으며 본인의 좋아요 목록만 볼 수 있게 시큐리티설정을 해뒀다.
요청, 응답 DTO
public class LikeRequest {
@Getter
@Setter
public static class LikeAddDTO{
@NotNull
private int userId;
@NotNull
private int productId;
}
}
public class LikeResponse {
@Getter
@Setter
public static class FindAllDTO{
private int id;
private String productName;
private String description;
private String image;
private int price;
public FindAllDTO(Like like) {
this.id = like.getProduct().getId();
this.productName = like.getProduct().getProductName();
this.description = like.getProduct().getDescription();
this.image = like.getProduct().getImage();
this.price = like.getProduct().getPrice();
}
}
}
컨트롤러
@RestController
@RequiredArgsConstructor
public class LikeRestController {
private final LikeService likeService;
@PostMapping("/like/add")
public ResponseEntity<?> likeAdd(@RequestBody @Valid LikeRequest.LikeAddDTO requestDTO, Errors errors, @AuthenticationPrincipal CustomUserDetails userDetails){
likeService.likeAdd(requestDTO, userDetails.getUser());
ApiUtils.ApiResult<?> apiResult = ApiUtils.success(null);
return ResponseEntity.ok(apiResult);
}
@GetMapping("/like/{id}")
public ResponseEntity<?> findAllLikes(@AuthenticationPrincipal CustomUserDetails userDetails, @PathVariable int id){
List<LikeResponse.FindAllDTO> responseDTO = likeService.findAllLikes(userDetails.getUser(),id);
ApiUtils.ApiResult<?> apiResult = ApiUtils.success(responseDTO);
return ResponseEntity.ok(apiResult);
}
}
서비스
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class LikeService {
private final LikeJPARepository likeJPARepository;
private final UserJPARepository userJPARepository;
private final ProductJPARepository productJPARepository;
@Transactional
public void likeAdd(LikeRequest.LikeAddDTO requestDTO, User sessionUser){
int userId = requestDTO.getUserId();
int productId = requestDTO.getProductId();
User userPS = userJPARepository.findById(userId).orElseThrow(
()-> new Exception400("존재하지 않는 사용자입니다.")
);
if(sessionUser.getId() != requestDTO.getUserId()){
throw new Exception401("인증되지 않은 사용자입니다.");
}
//이미 좋아요 누른 상품인데 또 눌렀을 경우
Optional<Like> likeOP = likeJPARepository.findByProductId(productId, userId);
if(likeOP.isPresent()){
throw new Exception400("이미 좋아요를 누른 상품입니다.");
}
//존재하지 않는 상품 id로 좋아요를 시도했을 때
Product productPS = productJPARepository.findById(productId).orElseThrow(
()-> new Exception404("존재하지 않는 상품입니다")
);
Like like = Like.builder()
.user(userPS)
.product(productPS)
.build();
likeJPARepository.save(like);
}
@Transactional
public List<LikeResponse.FindAllDTO> findAllLikes(User sessionUser, int id){
if(sessionUser.getId()!= id){
throw new Exception401("권한이 없습니다.");
}
//userId 가 2인 user의 likeList를 가져온다.
List<Like> likeList = likeJPARepository.findProductsByUserId(id);
List<LikeResponse.FindAllDTO> responseDTOs = likeList.stream()
.map(like -> new LikeResponse.FindAllDTO(like))
.collect(Collectors.toList());
return responseDTOs;
}
}
발생할 수 있는 모든 예외들을 처리해줬다.
좋아요 추가 기능
- userId와 productId를 가져온 후 DB에서 userId가 있는지 확인하고 없으면 예외처리
- 요청DTO에서 보낸 userId 데이터와 본인 userId 데이터가 다르면 예외처리
- 이미 좋아요를 누른 상품일 경우 예외처리
- 존재하지 않는 상품 id로 좋아요를 누르려는 접근을 시도했을 때 예외처리
좋아요 목록 조회 기능
- 본인의 userId가 아닌 경우 예외처리
- 좋아요 목록을 가져오고 stream으로 응답DTO 데이터 가공해서 리턴
postman으로 응답 확인
userId가 1인 사용자가 2, 4, 10번 상품들을 좋아요 추가 후 테이블 상태
like/1 GET 요청 후 응답
다른 예외들 눈으로 확인했고 일일히 첨부하기 힘들기 때문에 생략 ,,
실행된 쿼리 확인
장바구니 추가
//유저 조회
Hibernate:
select
user0_.id as id1_7_0_,
user0_.email as email2_7_0_,
user0_.password as password3_7_0_,
user0_.roles as roles4_7_0_,
user0_.username as username5_7_0_
from
user_tb user0_
where
user0_.id=?
//유저가 동일 상품에 좋아요 누르는걸 예외처리하기 위해 한번 조회
Hibernate:
select
like0_.id as id1_3_,
like0_.product_id as product_2_3_,
like0_.user_id as user_id3_3_
from
like_tb like0_
where
like0_.product_id=?
and like0_.user_id=?
//상품 정보를 불러오기 위해 조회 (가격이나 상품이름...등)
Hibernate:
select
product0_.id as id1_6_0_,
product0_.description as descript2_6_0_,
product0_.image as image3_6_0_,
product0_.price as price4_6_0_,
product0_.product_name as product_5_6_0_
from
product_tb product0_
where
product0_.id=?
//like 테이블에 삽입
Hibernate:
insert
into
like_tb
(id, product_id, user_id)
values
(default, ?, ?)
내가 만든 최선의 쿼리다...
나중에 코드를 다 뜯어고쳐서라도 개선할 방법이 있는지 생각해봐야겠다.
장바구니 목록 조회
Hibernate:
select
like0_.id as id1_3_0_,
product1_.id as id1_6_1_,
like0_.product_id as product_2_3_0_,
like0_.user_id as user_id3_3_0_,
product1_.description as descript2_6_1_,
product1_.image as image3_6_1_,
product1_.price as price4_6_1_,
product1_.product_name as product_5_6_1_
from
like_tb like0_
inner join
product_tb product1_
on like0_.product_id=product1_.id
where
like0_.user_id=?
product테이블을 join fetch 하여 n+1문제를 해결했다.
후기
여러번의 익셉션 처리로 인해 로직 작성이 약간 헷갈렸지만 그래도 잘 해결한 것 같아서 뿌듯하다 ^~^
728x90
728x90
'프로젝트 > 카카오 쇼핑하기 web' 카테고리의 다른 글
카카오 쇼핑하기 클론프로젝트 #3 (0) | 2023.08.09 |
---|---|
카카오 쇼핑하기 클론프로젝트 #2 (0) | 2023.07.07 |
카카오 쇼핑하기 클론프로젝트 #1 (0) | 2023.07.02 |