검색하기
- 쿼리 짜기
- DTO짜기 찾을 때 필요한 데이터 - 책 id - 제목 - 저자 - 출판사 - 책 표지 - 받을 키워드
- 서비스짜기 - 키워드에 아무것도 적지 않았을 때(그냥 전부 보여주기) - 키워드가 아무것도 없을 때 (검색 결과가 없습니다) - 키워드 있을 때 (DTO에 담아서 보내기)
- 컨트롤러 짜기
- 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);
- 검색 시 받을 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;
}
}
- 서비스 짜기
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;
}
- 컨트롤러 짜기
@GetMapping("/search")
public ResponseEntity<?> search(@RequestParam(name = "keyword") String keyword) {
List<BookResponse.BookSearchDTO> searchDTOS = bookService.검색기록보기(keyword);
return ResponseEntity.ok(Resp.ok(searchDTOS));
}
메인화면 뿌리기, 카테고리 선택 시 해당 책 뿌리기
상세보기(리뷰)
- 더미 데이터 삽입 시 varchar 길이 초과 나 문제가 생겼다 나왔다

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

- 이제 댓글적은 유저의 닉네임만 알면 된다!
근데 이대로 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번하는 문제

그래서 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);
뭐가 다르지?
- 전부 join한 것



차이가 있다 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);
한방에 똭

서비스, 컨트롤러 만들기
서비스
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));
}
확인

emergency
문제가 있었다!
원래는 inner join으로 해서 댓글을 삭제하니까 책을 찾을 수 없다 나왔다
inner join하면 공통된 것만 모아서 보여줘서 그런가
left outer join하니까 해결됨
emergency2
로그인을 안 하면 상세 페이지에 들어갈 수 없다
뭐가 문제인가
상세 페이지에 DTO가 문제 인가 확인하고 쿼리에서 찾을 때 문제가 생기는가 확인하고 서비스쪽으로 가니까 토큰이 여기서 확인하는데 한 줄 한 줄 확인함
책 상세보기 메서드에 토큰을 받는게 있다
문제는 로그인을 하지 않으면 토큰이 없는데

없는 토큰으로 userId를 찾고 이것을 DetailDTO에 담아서 문제가 생긴 것 같다

그래서
토큰이 없을 때 if문으로 주고 currentUserId는 null로 던졌다

그리고 받는 DetailDTO안에 CommentDTO에서 만약 currentUserId가 ≠null일 때 지금 Id와 리뷰의 id를 비교하는 걸로 변경함

책 상세보기, 댓글 따로 따로 보내기
책 상세보기
책 댓글 조회
SELECT * FROM COMMENT_TB c where c.book_isbn13 = '9791190669313'
리뷰 삭제
- 서비스 구현
- 컨트롤러 구현
1. 서비스 구현
서비스 구현
- 리뷰 있는지 확인
- 리뷰 삭제 권한 확인
- 리뷰 삭제
1. 리뷰 있는지 확인
댓글 삭제 메서드를 만들고 파라미터로 Long id를 받는다.
받은 id로 댓글이 있는지 확인하고

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

- 토큰 가지고 왔으면 토큰 안에 있는 userId를 뽑아내기


- 찾은 userId와 리뷰의 userId를 비교 후 확인

3. 삭제하기
commentRepository에서 댓글 pk로 삭제한다!

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

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

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

2. 리뷰는 삭제가 잘 되는건가?
h2에 가서 댓글 있는지 삭제 여부 확인
4번 없어짐

그러면 무슨 문제인가?
3. 책도 있고, 댓글도 잘 삭제 됐는데 상세보기 페이지에서 문제가 생겼으니 여기가 문제다
만들어둔 DetailDTO에서 문제가 있나 싶어 확인
필요한 것들 다 있고
이상 없어 보임 아직은 모름
생각해보니 bookRepository에서 쿼리문이 문제가 있나 생각함
왜냐하면 지금 계속 오류가 해당 책이 없습니다 라고 계속 뜨기에

@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)
결과
보인다

Share article