티스토리 뷰
이번엔 게시글 북마크 기능을 만들어 볼 것이다. 이전 포스팅했던 댓글 좋아요 기능과 거의 비슷하다고 보면 된다. (사실 거의 똑같음)
Entity 설계
Bookmark entity
package com.example.MyFreshmanCommunity.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
@AllArgsConstructor //모든 필드를 매개변수로 갖는 생성자 자동 생성
@NoArgsConstructor //매개변수가 아예 없는 기본 생성자 자동 생성
public class Bookmark {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
@OnDelete(action = OnDeleteAction.CASCADE)
private Member member;
@ManyToOne
@JoinColumn(name = "article_id")
@OnDelete(action = OnDeleteAction.CASCADE)
private Article article;
@Column(nullable = false)
private boolean status;
@Column
private LocalDateTime createDate;
public static Bookmark createBookmark(Member member, Article article) {
return new Bookmark(
null,
member,
article,
true,
LocalDateTime.now()
);
}
public void deleteBookmark(Article article) {
this.status = false;
}
}
댓글 좋아요 기능과 똑같이 member가 지워지면, 해당 Bookmark 레코드도 사라질 수 있도록 하기 위해 외래키 제약조건 CASCADE를 사용해주었다.
북마크 버튼을 눌렀으면 true, 북마크 버튼을 누르지 않았거나 북마크를 취소했다면 false 값을 주어 각 게시글에 대한 북마크 상태를 관리해주었다.
좋아요 기능과 마찬가지로 createBookmark, deleteBookmark 두개의 메소드를 만들어주었다.
좋아요 기능과 달라진 점이 있다면 createDate 필드를 추가해주었다는 점인데, 이는 나중에 북마크 목록을 조회할 때 생성날짜를 기준으로 내림차순으로 (최신순으로) 정렬하기 위해 createDate를 추가해주었다.
DTO
BookmarkDto
package com.example.MyFreshmanCommunity.dto;
import com.example.MyFreshmanCommunity.entity.Bookmark;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BookmarkDto {
private String message;
private boolean status;
public static BookmarkDto createBookmarkDto(String message, Bookmark bookmark) {
return new BookmarkDto(
message,
bookmark.isStatus()
);
}
}
서버에서 클라이언트로 데이터를 전송하는데 사용되는 BookmarkDto는 북마크 처리가 완료되었다는 (북마크, 북마크 취소 둘 다) message와 북마크 상태(true, false)를 반환한다.
Repository
BookmarkRepository
package com.example.MyFreshmanCommunity.repository;
import com.example.MyFreshmanCommunity.entity.Article;
import com.example.MyFreshmanCommunity.entity.Bookmark;
import com.example.MyFreshmanCommunity.entity.Member;
import jakarta.persistence.Tuple;
import org.springframework.data.jpa.repository.JpaRepository;
import java.awt.print.Book;
import java.util.Collection;
import java.util.List;
public interface BookmarkRepository extends JpaRepository<Bookmark, Long> {
Bookmark findByMemberAndArticle(Member member, Article article);
void deleteByMemberAndArticle(Member member, Article article);
}
특정 Member(사용자)와 Article(게시글) 조합으로 북마크 객체(Bookmark)를 찾아 반환하는 findByMemberAndArticle 메서드를 만들어주었다. 이 메서드는 특정 사용자가 특정 게시글에 대해 북마크를 했는지 확인하는 데 사용된다.
반면, deleteByMemberAndArticle은 특정 Member(사용자)와 Article(게시글) 조합에 해당하는 북마크를 데이터베이스에서 삭제하는 메서드이다. 사용자가 게시글에 대한 북마크를 취소할 때 이 메서드가 사용된다.
Service
BookmarkService
package com.example.MyFreshmanCommunity.service;
import com.example.MyFreshmanCommunity.dto.BookmarkDto;
import com.example.MyFreshmanCommunity.entity.Article;
import com.example.MyFreshmanCommunity.entity.Bookmark;
import com.example.MyFreshmanCommunity.entity.Member;
import com.example.MyFreshmanCommunity.exception.MemberNotFoundException;
import com.example.MyFreshmanCommunity.exception.NotFoundException;
import com.example.MyFreshmanCommunity.repository.ArticleRepository;
import com.example.MyFreshmanCommunity.repository.BookmarkRepository;
import jakarta.servlet.http.HttpSession;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class BookmarkService {
private final ArticleService articleService;
private final ArticleRepository articleRepository;
private final BookmarkRepository bookmarkRepository;
@Transactional
public BookmarkDto addBookmark(Long articleId, HttpSession session) {
Member member = (Member) session.getAttribute("member");
Article article = articleRepository.findById(articleId).
orElseThrow(() -> new NotFoundException("대상 게시글이 없습니다."));
if(member == null) throw new MemberNotFoundException("로그인하지 않은 상태에선 북마크를 할 수 없습니다.");
if(bookmarkRepository.findByMemberAndArticle(member, article) == null) {
article.setBookmarkCount(article.getBookmarkCount() + 1);
Bookmark bookmark = Bookmark.createBookmark(member, article);
bookmarkRepository.save(bookmark);
return BookmarkDto.createBookmarkDto("북마크 처리 완료", bookmark);
}
else {
article.setBookmarkCount(article.getBookmarkCount() - 1);
Bookmark bookmark = bookmarkRepository.findByMemberAndArticle(member, article);
bookmark.deleteBookmark(article); //false처리
bookmarkRepository.deleteByMemberAndArticle(member, article);
return BookmarkDto.createBookmarkDto("북마크 취소 완료", bookmark);
}
}
}
사용자가 게시글에 북마크를 추가하거나 취소할 때 사용되는 addBookmark 메서드는 사용자 세션에서 멤버 정보를 가져오고, 게시글 ID를 사용하여 해당 게시글을 찾는다.
북마크가 아직 없으면 해당 게시글의 북마크 수를 1만큼 증가시켜 주고 새로운 북마크를 추가하고,
이미 북마크를 눌렀다면 북마크를 취소하는 것이므로 해당 게시글의 북마크 수를 1만큼 감소시켜주고, 북마크를 취소해준다.
(Bookmark 테이블에서 삭제)
Controller
BookmarkApiController
package com.example.MyFreshmanCommunity.api;
import com.example.MyFreshmanCommunity.dto.BookmarkDto;
import com.example.MyFreshmanCommunity.service.BookmarkService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class BookmarkApiController {
private final BookmarkService bookmarkService;
//게시글 북마크
@PostMapping("/article/{articleId}/bookmark")
public ResponseEntity<BookmarkDto> addBookmark(@PathVariable Long articleId, HttpServletRequest request) {
HttpSession session = request.getSession();
BookmarkDto bookmark = bookmarkService.addBookmark(articleId, session);
return ResponseEntity.status(HttpStatus.OK).body(bookmark);
}
}
@PathVariable을 사용하여 URL 경로의 일부(articleId) 를 메서드의 파라미터로 바인딩해주었다.
Postman을 통한 데이터 확인
postman을 통해 데이터가 잘 넘어가는지 확인해주었다.
'Spring' 카테고리의 다른 글
스프링 커뮤니티 만들기 #9 - 회원탈퇴 시 오류해결 (외래키 제약조건) (2) | 2024.03.03 |
---|---|
스프링 커뮤니티 만들기 #8 북마크 목록 조회 (0) | 2024.03.03 |
스프링 커뮤니티 만들기 #6 - 댓글 좋아요 (2) | 2024.03.03 |
스프링부트 커뮤니티 만들기 #5 - 댓글 CRUD (0) | 2024.03.03 |
스프링부트 커뮤니티 만들기 # 회고록 (2) | 2024.02.29 |
- Total
- Today
- Yesterday
- 프론트엔드
- 파이썬
- 커뮤니티
- 스프링 북마크
- 스프링 커뮤니티
- 자바
- 백준 파이썬
- JPA
- SQLD
- elasticsearch
- SQL
- 로깅
- 준영속
- 웹 MVC
- 로그아웃
- 다이나믹 프로그래밍
- 회원탈퇴
- 비영속
- 북마크
- 웹MVC
- 영속
- 인텔리제이
- 스프링부트
- 지연로딩
- DP
- SQL 레벨업
- 자바 스프링
- 백준
- 스프링
- EnumType.ORDINAL
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
31 |