2차 팀 프로젝트 : BookBox (Back-End)

도서 대여/예약 앱 플랫폼 : 카테고리/필터링, 메인화면, 리뷰
SHIN's avatar
Oct 22, 2024
2차 팀 프로젝트 : BookBox (Back-End)
 
검색하기
  1. 쿼리 짜기
  1. DTO짜기 찾을 때 필요한 데이터 - 책 id - 제목 - 저자 - 출판사 - 책 표지 - 받을 키워드
  1. 서비스짜기 - 키워드에 아무것도 적지 않았을 때(그냥 전부 보여주기) - 키워드가 아무것도 없을 때 (검색 결과가 없습니다) - 키워드 있을 때 (DTO에 담아서 보내기)
  1. 컨트롤러 짜기
 
 
  1. title, author, publisher 전부 통합 검색이기에
@Query("SELECT b FROM Book b WHERE b.title LIKE %:title% OR b.author LIKE %:author% OR b.publisher LIKE %:publisher% order by b.isbn13 desc") List<Book> mFindAll(@Param("title") String title, @Param("author") String author, @Param("publisher") String publisher);
title, author, publiser을 각 각 넣을 예정이었지만
 
생각해보면 하나의 검색창에 같은 입력값을 받는데 굳이 3개로 나눠야 할 까 싶어
하나의 입력값으로 전부 찾는 쿼리를 생각했습니다.
@Query("SELECT b FROM Book b WHERE b.title LIKE %:searchTerm% OR b.author LIKE %:searchTerm% OR b.publisher LIKE %:searchTerm%") List<Book> mFindAll(@Param("searchTerm") String searchTerm);
 
  1. 검색 시 받을 DTO
@Data public static class BookSearchDTO { private String isbn13; private String title; private String author; private String publisher; private String cover; private String keyword; public BookSearchDTO(Book book, String bookTitle) { this.isbn13 = book.getIsbn13(); this.title = book.getTitle(); this.author = book.getAuthor(); this.publisher = book.getPublisher(); this.cover = book.getCover(); this.keyword = bookTitle; } }
 
  1. 서비스 짜기
public List<BookResponse.BookSearchDTO> 검색기록보기(String keyword){ System.out.println("검색어:" + keyword); //아무것도 적지 않았을 때 if(keyword == null){ List<Book> bookPG = bookRepository.findAll(); List<BookResponse.BookSearchDTO> dtos = new ArrayList<>(); for(Book book : bookPG){ BookResponse.BookSearchDTO dto = new BookResponse.BookSearchDTO(book, ""); dtos.add(dto); } return dtos; } List<Book> searchBookList = bookRepository.mFindAll(keyword); //만약 검색결과가 없을 때 if(searchBookList.isEmpty()){ throw new ExceptionApi404("검색 결과가 없습니다."); } //검색 결과가 있을 때 List<BookResponse.BookSearchDTO> dtos = new ArrayList<>(); for(Book book : searchBookList){ BookResponse.BookSearchDTO dto = new BookResponse.BookSearchDTO(book, keyword); dtos.add(dto); } return dtos; }
 
  1. 컨트롤러 짜기
@GetMapping("/search") public ResponseEntity<?> search(@RequestParam(name = "keyword") String keyword) { List<BookResponse.BookSearchDTO> searchDTOS = bookService.검색기록보기(keyword); return ResponseEntity.ok(Resp.ok(searchDTOS)); }
메인화면 뿌리기, 카테고리 선택 시 해당 책 뿌리기
 
상세보기(리뷰)
  • 더미 데이터 삽입 시 varchar 길이 초과 나 문제가 생겼다 나왔다
notion image
insert into comment_tb(content, created_at, isbn13, user_id) values('너무 감명깊에 읽었지만 평가에 귀여운 여우에 대한 감정이 들어간게 아닌가 싶어 보류 드리겠습니다.',now(),'9791190669313', 1);
 
→ 해결
isbn13이라는 책 pk를 가지고 와야하는데 더미를 넣을 때 책 더미보다 위쪽에 넣어서 오류났음
→ book더미 밑에 리뷰더미를 넣어 book더미 먼저 만들어지고 리뷰더미 만들어지게 변경함
 
 
상세보기 댓글 쿼리
  • 9788937462788 책의 내용 및 댓글쿼리
SELECT * FROM book_tb b inner join comment_tb ct on b.isbn13 = ct.book_isbn13 where b.isbn13= 9788937462788
notion image
 
  • 이제 댓글적은 유저의 닉네임만 알면 된다!
근데 이대로 test하니까 유저 닉네임이 나오기는 하는데 2번 select한다 문제임
test코드
//책 상세보기 @Test public void mFindByIdWithComment_test(){ String isbn13 = "9788937462788"; Book detailBook = bookRepository.mFindByIdWithComment(isbn13).get(); //2번 들락날락은 별로라고 했는데 그럼 join할까? System.out.println(detailBook.getComments().get(0).getContent()); System.out.println(detailBook.getComments().get(0).getUser().getNick()); }
select 2번하는 문제
 
notion image
그래서 2번 select할 바에 join한번 더 하자 해서 함
@Query("SELECT b FROM Book b JOIN b.comments ct join ct.user u WHERE b.isbn13 = :isbn13") Optional<Book> mFindByIdWithComment(@Param("isbn13") String isbn13);
 
 
뭐가 다르지?
  1. 전부 join한 것
notion image
notion image
notion image
 
차이가 있다 fetch 안 해서 그런듯
fetch 붙이니까
@Query("SELECT b FROM Book b JOIN fetch b.comments ct join fetch ct.user u WHERE b.isbn13 = :isbn13") Optional<Book> mFindByIdWithComment(@Param("isbn13") String isbn13);
한방에 똭
notion image
 
 
서비스, 컨트롤러 만들기
 
서비스
public BookResponse.DetailDTO 책상세보기(String isbn13){ Book bookPS = bookRepository.mFindByIdWithComment(isbn13) .orElseThrow(()-> new ExceptionApi404("해당 책이 없습니다")); return new BookResponse.DetailDTO(bookPS); }
 
컨트롤러
@GetMapping("/api/books/detail") public ResponseEntity<?> detail(@RequestParam(name = "isbn13") String isbn13){ BookResponse.DetailDTO bookDetail = bookService.책상세보기(isbn13); return ResponseEntity.ok(Resp.ok(bookDetail)); }
 
확인
notion image

emergency

문제가 있었다!
원래는 inner join으로 해서 댓글을 삭제하니까 책을 찾을 수 없다 나왔다
inner join하면 공통된 것만 모아서 보여줘서 그런가
left outer join하니까 해결됨
 

emergency2

로그인을 안 하면 상세 페이지에 들어갈 수 없다
뭐가 문제인가
상세 페이지에 DTO가 문제 인가 확인하고 쿼리에서 찾을 때 문제가 생기는가 확인하고 서비스쪽으로 가니까 토큰이 여기서 확인하는데 한 줄 한 줄 확인함
 
책 상세보기 메서드에 토큰을 받는게 있다
문제는 로그인을 하지 않으면 토큰이 없는데
notion image
없는 토큰으로 userId를 찾고 이것을 DetailDTO에 담아서 문제가 생긴 것 같다
notion image
그래서
토큰이 없을 때 if문으로 주고 currentUserId는 null로 던졌다
notion image
 
그리고 받는 DetailDTO안에 CommentDTO에서 만약 currentUserId가 ≠null일 때 지금 Id와 리뷰의 id를 비교하는 걸로 변경함
notion image
 

책 상세보기, 댓글 따로 따로 보내기

책 상세보기
 
책 댓글 조회
SELECT * FROM COMMENT_TB c where c.book_isbn13 = '9791190669313'
리뷰 삭제
  1. 서비스 구현
  1. 컨트롤러 구현

1. 서비스 구현

서비스 구현
  1. 리뷰 있는지 확인
  1. 리뷰 삭제 권한 확인
  1. 리뷰 삭제

1. 리뷰 있는지 확인

댓글 삭제 메서드를 만들고 파라미터로 Long id를 받는다.
받은 id로 댓글이 있는지 확인하고
notion image
 

2. 리뷰 삭제 권한 확인

  • 생각 로그인 할 때 JWT토큰을 받고 토큰 안에 userId가 있으니까 그것과 리뷰 있는지 없는지 확인 할 때 리뷰가 가지고 있는 userId와 비교하면 될 것 같음
  1. 먼저는 토큰을 가지고 와야 함 JwtUtil로 이동 HttpServletRequest Header에 토큰이 있을 거니까 Authorization이름의 해더를 뽑아오고, Bearer 를 지운 나머지 부분을 가지고 온다
notion image
 
  1. 토큰 가지고 왔으면 토큰 안에 있는 userId를 뽑아내기
notion image
notion image
 
  1. 찾은 userId와 리뷰의 userId를 비교 후 확인
notion image
 

3. 삭제하기

commentRepository에서 댓글 pk로 삭제한다!
notion image
 

2. 컨트롤러 구현

컨트롤러 구현
서비스에서 리뷰삭제 메서드를 호출하고 Json데이터 보내기
notion image
 

문제가 있었음

문제
  • 리뷰 삭제 메서드를 실행 후 책 상세보기를 하면 책이 존재하지 않는다는 throw가 나옴
 
  1. 리뷰 삭제를 하면 책 자체가 없어지나?
  1. 리뷰는 삭제가 잘 되는건가?
  1. 둘 다 아니라면 뭐가 문제인가?
 
일단 리뷰를 삭제해보자
포스트맨에서 확인해보니 200 성공이 나왔고
notion image

1. 리뷰 삭제를 하면 책 자체가 없어지나?

책 있음
notion image

2. 리뷰는 삭제가 잘 되는건가?

h2에 가서 댓글 있는지 삭제 여부 확인
4번 없어짐
notion image
 
그러면 무슨 문제인가?

3. 책도 있고, 댓글도 잘 삭제 됐는데 상세보기 페이지에서 문제가 생겼으니 여기가 문제다

만들어둔 DetailDTO에서 문제가 있나 싶어 확인
필요한 것들 다 있고
이상 없어 보임 아직은 모름
 
생각해보니 bookRepository에서 쿼리문이 문제가 있나 생각함
왜냐하면 지금 계속 오류가 해당 책이 없습니다 라고 계속 뜨기에
notion image
 
@Query("SELECT b FROM Book b JOIN fetch b.comments ct JOIN fetch ct.user u WHERE b.isbn13 = :isbn13") Optional<Book> mFindByIdWithComment(@Param("isbn13") String isbn13);
inner join을 사용했었는데 이러면 매칭되는 데이터가 없으면 그 레코드를 포함하지 않아서 책이 결과에서 제외될 수 있다! 그래서
left join으로 만드니까
@Query("SELECT b FROM Book b left JOIN fetch b.comments ct left JOIN fetch ct.user u WHERE b.isbn13 = :isbn13") Optional<Book> mFindByIdWithComment(@Param("isbn13") String isbn13)
 
결과
보인다
notion image
 
 
 
Share article

SHIN