스프링 커뮤니티 만들기 #8 북마크 목록 조회
게시글 북마크 기능은 다 만들었고, 이제 유저(멤버)의 북마크 페이지나 마이 페이지에서 북마크한 목록을 조회하는 기능을 구현할 것이다.
Dto
MyBookamrkDto
package com.example.MyFreshmanCommunity.dto;
import com.example.MyFreshmanCommunity.entity.Article;
import com.example.MyFreshmanCommunity.entity.Major;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class MyBookmarkDto {
private Long id;
private String title;
private String content;
private LocalDateTime createDate;
private LoginResponseDto member;
private Major major;
public static MyBookmarkDto createMyBookmarkDto(Article article){
return new MyBookmarkDto(
article.getId(),
article.getTitle(),
article.getContent(),
article.getCreateDate(),
LoginResponseDto.createLoginDto(article.getMember()),
article.getMajor()
);
}
}
사실 ArticleResponseDto랑 다른 게 없지만 구분하는 게 더 이해하기 편할 것 같아 구분하여 클래스를 만들어주었다.
유저가 북마크한 게시글의 목록들이 createBookmarkDto메서드를 통해 MyBookmarkDto에 담겨 반환될 것이다.
BookmarkRepository
List<Bookmark> findAllByMember(Member member);
Service
MemberService
❗️❗️❗️틀린코드❗️❗️❗️
public List<MyBookmarkDto> myBookmark(HttpSession session) {
Member member = (Member) session.getAttribute("member");
List<Article> bookmarks = articleRepository.findAllByIdIn(bookmarkRepository.findAllArticleIdByMember(member));
List<MyBookmarkDto> dtos = new ArrayList<MyBookmarkDto>();
for(Article a: bookmarks) {
MyBookmarkDto dto = MyBookmarkDto.createBookmarkDto(a);
dtos.add(dto);
}
return dtos;
}
처음에는 되게 복잡하게 생각했다.
findAllArticleIdByMember 메서드를 호출하여 회원의 북마크된 게시물의 ID 목록을 가져온 다음, 이 ID 목록을 사용하여 findAllByIdIn 메서드를 호출하여 해당 ID를 가진 게시물을 가져왔다. 그런 다음 가져온 각 게시물을 MyBookmarkDto로 변환하여 리스트에 추가해주었다. 그러나 이렇게 하면 두 번의 데이터베이스 쿼리를 실행하므로 데이터베이스 부하가 발생할 뿐만 아니라, postman으로 돌려보았을 때 "Specified result type [java.lang.Long] did not match Query selection type [com.example.MyFreshmanCommunity.entity.Bookmark] - multiple selections: use Tuple or array" 라는 오류 메시지가 나타났다. 찾아보니 이 오류는 여러 선택 항목이 있는 쿼리에서 단일 타입을 반환하려고 할 때 발생하는데, 따라서 여러 개의 엔티티나 다른 객체를 선택한 경우에는 해당 결과를 Tuple이나 배열로 반환해야 한다.
❗️❗️❗️두번째 틀린코드❗️❗️❗️
public List<MyBookmarkDto> myBookmark(HttpSession session) {
Member member = (Member) session.getAttribute("member");
// Member로부터 Article ID 목록 가져오기
List<Tuple> articleTuples = bookmarkRepository.findAllArticleIdByMember(member);
List<Long> articleIds = new ArrayList<>();
for (Tuple tuple : articleTuples) {
// Tuple에서 Article ID를 가져와서 List<Long>에 추가
Long articleId = (Long) tuple.get(0);
articleIds.add(articleId);
}
// Article ID 목록을 사용하여 Article 목록 조회
List<Article> bookmarks = articleRepository.findAllByIdIn(articleIds);
// Article 목록을 MyBookmarkDto로 변환
List<MyBookmarkDto> dtos = new ArrayList<>();
for (Article a: bookmarks) {
MyBookmarkDto dto = MyBookmarkDto.createBookmarkDto(a);
dtos.add(dto);
}
return dtos;
}
따라서 투플을 이용한 코드로 바꿔보았지만,,, 이번엔 Invoked method is not a property accessor라는 오류가 떴다. 이 오류는 스프링 데이터 프로젝션에서 사용되는 메서드가 올바른 방식으로 호출되지 않았을 때 발생한다.
이를통해 내가 생각했던 방식은 아예 잘못되었음을 깨닫고, 다시 처음부터 생각해보았다. ㅎㅎ
🌟🌟최종코드🌟🌟
public List<MyBookmarkDto> myBookmark(HttpSession session) {
Member member = (Member) session.getAttribute("member");
return bookmarkRepository.findAllByMember(member).stream()
.map(bookmark -> MyBookmarkDto.createMyBookmarkDto(bookmark.getArticle()))
.collect(Collectors.toList());
}
두 번의 데이터베이스 쿼리를 호출할 필요 없이, findAllByMember 메서드를 사용하여 회원의 북마크된 항목을 한 번에 가져오고, 그런 다음 각 북마크 항목을 MyBookmarkDto로 매핑하여 최종 결과를 얻으면 된다.
이 방법은 스트림 및 람다 표현식을 사용하여 코드를 간결하게 유지할 수 있을 뿐만 아니라, 하나의 쿼리만 사용하므로 데이터베이스 부하가 줄어들게 된다.
Controller
MemberApiController
//나의 북마크
@GetMapping("/user/bookmark")
public ResponseEntity<List<MyBookmarkDto>> myBookmark(HttpServletRequest request) {
HttpSession session = request.getSession();
List<MyBookmarkDto> bookmarks = memberService.myBookmark(session);
return ResponseEntity.status(HttpStatus.OK).body(bookmarks);
}
유저가 여러개의 게시글을 북마크할 수 있으므로 반환타입을 <List<MyBookmarkDto>>로 해주었다. 또한 유저마다의 북마크 목록을 가져오기 위해 현재 로그인되어있는 멤버의 세션을 가져와주었다.
postman을 통한 데이터 확인
