Repository: somecreater/PetStayConnect
Pull request #49: feat: Create bookmark(관심목록) module (entity, dto, repository, service, controller)
Author: login: Suu-a
Created: 2025-05-08T05:55:46Z | Merged: 2025-05-13T06:46:53Z

## 설명
feat: Create bookmark(관심목록) module (entity, dto, repository, service, controller)

## 작성 타입
- [ ] 버그 수정
- [x] 새로운 기능 추가
- [ ] 설명 수정
- [ ] 코드 리팩토링(경로 변경, 성능 최적화)

## 체크사항
- [ ] 내가 작성한 코드를 직접 테스트를 하였습니다.
- [ ] 내가 작성한 코드에 대해서 주변에 미치는 영향을 최소화 하였습니다.
- [ ] 깔끔히 코드를 작성 하였으며, 프로젝트 내 테스트 경로 외의 테스트 코드를 모두 지운 것을 확인하였습니다.
- [ ] 코드가 이해하기 쉽게 작성되었는지 확인하였습니다.
- [ ] 코드 내 민감한 정보가 없는 것을 확인하였습니다.
- [ ] 하드코딩을 모두 지운 것을 확인하였습니다.

## 특이 사항
qna나 호텔 삭제 시 북마크 자동 정리하는건 qna와 호텔부분 코드 수정필요해서 충돌 우려있어 아직 하지 않았음



Diff:
diff --git a/src/main/java/com/petservice/main/bookmark/controller/BookmarkController.java b/src/main/java/com/petservice/main/bookmark/controller/BookmarkController.java
new file mode 100644
index 0000000..824facf
--- /dev/null
+++ b/src/main/java/com/petservice/main/bookmark/controller/BookmarkController.java
@@ -0,0 +1,99 @@
+package com.petservice.main.bookmark.controller;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.BookmarkType;
+import com.petservice.main.bookmark.service.BookmarkServiceInterface;
+import com.petservice.main.user.database.dto.CustomUserDetails;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/bookmarks")
+@RequiredArgsConstructor
+public class BookmarkController {
+
+    private final BookmarkServiceInterface bookmarkService;
+
+    @PostMapping
+    public ResponseEntity<?> addBookmark(
+            @AuthenticationPrincipal CustomUserDetails principal,
+            @RequestParam BookmarkType bookmarkType,
+            @RequestParam Long targetId
+    ) {
+        Map<String, Object> result = new HashMap<>();
+        if (principal == null) {
+            result.put("result", false);
+            result.put("message", "로그인이 필요합니다");
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
+        }
+        try {
+            bookmarkService.addBookmark(principal.getUsername(), bookmarkType, targetId);
+            result.put("result", true);
+            result.put("message", "북마크가 추가되었습니다");
+            return ResponseEntity.ok(result);
+        } catch (Exception e) {
+            result.put("result", false);
+            result.put("message", e.getMessage());
+            return ResponseEntity.badRequest().body(result);
+        }
+    }
+
+    @DeleteMapping
+    public ResponseEntity<?> removeBookmark(
+            @AuthenticationPrincipal CustomUserDetails principal,
+            @RequestParam BookmarkType bookmarkType,
+            @RequestParam Long targetId
+    ) {
+        Map<String, Object> result = new HashMap<>();
+        if (principal == null) {
+            result.put("result", false);
+            result.put("message", "로그인이 필요합니다");
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
+        }
+        bookmarkService.removeBookmark(principal.getUsername(), bookmarkType, targetId);
+        result.put("result", true);
+        result.put("message", "북마크가 삭제되었습니다");
+        return ResponseEntity.ok(result);
+    }
+
+    @GetMapping
+    public ResponseEntity<?> getBookmarks(
+            @AuthenticationPrincipal CustomUserDetails principal
+    ) {
+        Map<String, Object> result = new HashMap<>();
+        if (principal == null) {
+            result.put("result", false);
+            result.put("message", "로그인이 필요합니다");
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
+        }
+        List<BookmarkDTO> bookmarks = bookmarkService.getBookmarksByUser(principal.getUsername());
+        result.put("result", true);
+        result.put("bookmarks", bookmarks);
+        return ResponseEntity.ok(result);
+    }
+
+    @GetMapping("/check")
+    public ResponseEntity<?> checkBookmark(
+            @AuthenticationPrincipal CustomUserDetails principal,
+            @RequestParam BookmarkType bookmarkType,
+            @RequestParam Long targetId
+    ) {
+        Map<String, Object> result = new HashMap<>();
+        if (principal == null) {
+            result.put("result", false);
+            result.put("message", "로그인이 필요합니다");
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(result);
+        }
+        boolean isBookmarked = bookmarkService.isBookmarked(principal.getUsername(), bookmarkType, targetId);
+        result.put("result", true);
+        result.put("isBookmarked", isBookmarked);
+        return ResponseEntity.ok(result);
+    }
+}
diff --git a/src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java b/src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java
new file mode 100644
index 0000000..106c6b0
--- /dev/null
+++ b/src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java
@@ -0,0 +1,71 @@
+package com.petservice.main.bookmark.service;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.Bookmark;
+import com.petservice.main.user.database.entity.BookmarkType;
+import com.petservice.main.user.database.entity.User;
+import com.petservice.main.user.database.mapper.BookmarkMapper;
+import com.petservice.main.user.database.repository.BookmarkRepository;
+import com.petservice.main.user.database.repository.UserRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class BookmarkServiceImpl implements BookmarkServiceInterface {
+
+    private final BookmarkRepository bookmarkRepository;
+    private final UserRepository userRepository;
+    private final BookmarkMapper bookmarkMapper;
+
+    @Override
+    @Transactional
+    public void addBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        // 이미 북마크했는지 확인 (커스텀 쿼리로 처리)
+        boolean exists = bookmarkRepository.existsByUser_UserLoginIdAndBookmarkTypeAndTargetId(userLoginId, bookmarkType, targetId);
+        if (exists) return;
+
+        // 사용자 조회 (북마크 생성에 user 엔티티 필요)
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        Bookmark bookmark = new Bookmark();
+        bookmark.setUser(user);
+        bookmark.setBookmarkType(bookmarkType);
+        bookmark.setTargetId(targetId);
+        bookmarkRepository.save(bookmark);
+    }
+
+    @Override
+    @Transactional
+    public void removeBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        // 검증 없이 바로 삭제 (user 조회 없이)
+        bookmarkRepository.deleteByUser_UserLoginIdAndBookmarkTypeAndTargetId(userLoginId, bookmarkType, targetId);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public List<BookmarkDTO> getBookmarksByUser(String userLoginId) {
+        // 커스텀 쿼리로 북마크 목록 조회
+        return bookmarkRepository.findByUser_UserLoginId(userLoginId).stream()
+                .map(bookmarkMapper::toDTO)
+                .toList();
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public boolean isBookmarked(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        // 커스텀 쿼리로 북마크 존재 여부 확인
+        return bookmarkRepository.existsByUser_UserLoginIdAndBookmarkTypeAndTargetId(userLoginId, bookmarkType, targetId);
+    }
+
+    @Override
+    @Transactional
+    public void cleanupDeletedItem(BookmarkType bookmarkType, Long targetId) {
+        // 커스텀 쿼리로 북마크 일괄 삭제
+        bookmarkRepository.deleteByBookmarkTypeAndTargetId(bookmarkType, targetId);
+    }
+}
diff --git a/src/main/java/com/petservice/main/bookmark/service/BookmarkServiceInterface.java b/src/main/java/com/petservice/main/bookmark/service/BookmarkServiceInterface.java
new file mode 100644
index 0000000..57ba83f
--- /dev/null
+++ b/src/main/java/com/petservice/main/bookmark/service/BookmarkServiceInterface.java
@@ -0,0 +1,13 @@
+package com.petservice.main.bookmark.service;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.BookmarkType;
+import java.util.List;
+
+public interface BookmarkServiceInterface {
+    void addBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId);
+    void removeBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId);
+    List<BookmarkDTO> getBookmarksByUser(String userLoginId);
+    boolean isBookmarked(String userLoginId, BookmarkType bookmarkType, Long targetId);
+    void cleanupDeletedItem(BookmarkType bookmarkType, Long targetId);
+}
diff --git a/src/main/java/com/petservice/main/user/database/repository/BookmarkRepository.java b/src/main/java/com/petservice/main/user/database/repository/BookmarkRepository.java
index 254d9bc..a9638e7 100644
--- a/src/main/java/com/petservice/main/user/database/repository/BookmarkRepository.java
+++ b/src/main/java/com/petservice/main/user/database/repository/BookmarkRepository.java
@@ -1,10 +1,25 @@
 package com.petservice.main.user.database.repository;
 
 import com.petservice.main.user.database.entity.Bookmark;
+import com.petservice.main.user.database.entity.BookmarkType;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
+import java.util.List;
+
 @Repository
 public interface BookmarkRepository extends JpaRepository<Bookmark,Long> {
 
+    // 북마크 존재 여부 확인 (userLoginId, type, targetId)
+    boolean existsByUser_UserLoginIdAndBookmarkTypeAndTargetId(String userLoginId, BookmarkType bookmarkType, Long targetId);
+
+    // 북마크 삭제 (userLoginId, type, targetId)
+    void deleteByUser_UserLoginIdAndBookmarkTypeAndTargetId(String userLoginId, BookmarkType bookmarkType, Long targetId);
+
+    // 사용자별 북마크 목록 조회 (userLoginId)
+    List<Bookmark> findByUser_UserLoginId(String userLoginId);
+
+    // 특정 타입/타겟ID로 북마크 일괄 삭제 (예: QnA 게시글 삭제 시)
+    void deleteByBookmarkTypeAndTargetId(BookmarkType bookmarkType, Long targetId);
+
 }


Reviews:
user: login: somecreater
state: COMMENTED
author_association: OWNER
submitted_at: 2025-05-09T02:59:03Z
user: login: Suu-a
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-09T03:42:10Z

Review comments:
diff_hunk: @@ -0,0 +1,99 @@
+package com.petservice.main.bookmark.service;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.Bookmark;
+import com.petservice.main.user.database.entity.BookmarkType;
+import com.petservice.main.user.database.entity.User;
+import com.petservice.main.user.database.mapper.BookmarkMapper;
+import com.petservice.main.user.database.repository.BookmarkRepository;
+import com.petservice.main.user.database.repository.UserRepository;
+import com.petservice.main.qna.database.repository.QnaPostRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class BookmarkServiceImpl implements BookmarkServiceInterface {
+
+    private final BookmarkRepository bookmarkRepository;
+    private final UserRepository userRepository;
+    private final QnaPostRepository qnaPostRepository;
+    private final BookmarkMapper bookmarkMapper;
+
+    @Override
+    @Transactional
+    public void addBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // QNA 게시글 존재 여부 확인
+        if (bookmarkType == BookmarkType.QNA && !qnaPostRepository.existsById(targetId)) {
+            throw new EntityNotFoundException("QnA 게시글이 존재하지 않습니다.");
+        }
+
+        // 이미 북마크했는지 확인 (메모리상에서 체크)
+        boolean exists = bookmarkRepository.findAll().stream()
path: src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java
user: login: somecreater
body: 이렇게 쓰지 마세요. 다 가져오기 보다는 해당 데이터만 가져와서 확인하세요.
created_at: 2025-05-09T02:53:52Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,99 @@
+package com.petservice.main.bookmark.service;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.Bookmark;
+import com.petservice.main.user.database.entity.BookmarkType;
+import com.petservice.main.user.database.entity.User;
+import com.petservice.main.user.database.mapper.BookmarkMapper;
+import com.petservice.main.user.database.repository.BookmarkRepository;
+import com.petservice.main.user.database.repository.UserRepository;
+import com.petservice.main.qna.database.repository.QnaPostRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class BookmarkServiceImpl implements BookmarkServiceInterface {
+
+    private final BookmarkRepository bookmarkRepository;
+    private final UserRepository userRepository;
+    private final QnaPostRepository qnaPostRepository;
+    private final BookmarkMapper bookmarkMapper;
+
+    @Override
+    @Transactional
+    public void addBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // QNA 게시글 존재 여부 확인
+        if (bookmarkType == BookmarkType.QNA && !qnaPostRepository.existsById(targetId)) {
+            throw new EntityNotFoundException("QnA 게시글이 존재하지 않습니다.");
+        }
+
+        // 이미 북마크했는지 확인 (메모리상에서 체크)
+        boolean exists = bookmarkRepository.findAll().stream()
+                .anyMatch(b -> b.getUser().getId().equals(user.getId())
+                        && b.getBookmarkType() == bookmarkType
+                        && b.getTargetId().equals(targetId));
+        if (exists) return;
+
+        Bookmark bookmark = new Bookmark();
+        bookmark.setUser(user);
+        bookmark.setBookmarkType(bookmarkType);
+        bookmark.setTargetId(targetId);
+        bookmarkRepository.save(bookmark);
+    }
+
+    @Override
+    @Transactional
+    public void removeBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
path: src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java
user: login: somecreater
body: 굳이 user도, 모든 북마크 정보를 다 가져올 필요가 없어요. 그냥 그 데이터만 삭제하세요(일단 검증 빼고 삭제만 하세요, 제가 추후 수정할게요.)
created_at: 2025-05-09T02:56:53Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,99 @@
+package com.petservice.main.bookmark.service;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.Bookmark;
+import com.petservice.main.user.database.entity.BookmarkType;
+import com.petservice.main.user.database.entity.User;
+import com.petservice.main.user.database.mapper.BookmarkMapper;
+import com.petservice.main.user.database.repository.BookmarkRepository;
+import com.petservice.main.user.database.repository.UserRepository;
+import com.petservice.main.qna.database.repository.QnaPostRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class BookmarkServiceImpl implements BookmarkServiceInterface {
+
+    private final BookmarkRepository bookmarkRepository;
+    private final UserRepository userRepository;
+    private final QnaPostRepository qnaPostRepository;
+    private final BookmarkMapper bookmarkMapper;
+
+    @Override
+    @Transactional
+    public void addBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // QNA 게시글 존재 여부 확인
+        if (bookmarkType == BookmarkType.QNA && !qnaPostRepository.existsById(targetId)) {
+            throw new EntityNotFoundException("QnA 게시글이 존재하지 않습니다.");
+        }
+
+        // 이미 북마크했는지 확인 (메모리상에서 체크)
+        boolean exists = bookmarkRepository.findAll().stream()
+                .anyMatch(b -> b.getUser().getId().equals(user.getId())
+                        && b.getBookmarkType() == bookmarkType
+                        && b.getTargetId().equals(targetId));
+        if (exists) return;
+
+        Bookmark bookmark = new Bookmark();
+        bookmark.setUser(user);
+        bookmark.setBookmarkType(bookmarkType);
+        bookmark.setTargetId(targetId);
+        bookmarkRepository.save(bookmark);
+    }
+
+    @Override
+    @Transactional
+    public void removeBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // 메모리상에서 찾아서 삭제
+        bookmarkRepository.findAll().stream()
+                .filter(b -> b.getUser().getId().equals(user.getId())
+                        && b.getBookmarkType() == bookmarkType
+                        && b.getTargetId().equals(targetId))
+                .forEach(bookmarkRepository::delete);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public List<BookmarkDTO> getBookmarksByUser(String userLoginId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        return bookmarkRepository.findAll().stream()
+                .filter(b -> b.getUser().getId().equals(user.getId()))
+                .filter(b -> b.getBookmarkType() != BookmarkType.QNA || qnaPostRepository.existsById(b.getTargetId()))
path: src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java
user: login: somecreater
body:  qnaPostRepository.existsById(b.getTargetId())) 이거 빼세요
created_at: 2025-05-09T02:58:02Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,99 @@
+package com.petservice.main.bookmark.service;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.Bookmark;
+import com.petservice.main.user.database.entity.BookmarkType;
+import com.petservice.main.user.database.entity.User;
+import com.petservice.main.user.database.mapper.BookmarkMapper;
+import com.petservice.main.user.database.repository.BookmarkRepository;
+import com.petservice.main.user.database.repository.UserRepository;
+import com.petservice.main.qna.database.repository.QnaPostRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class BookmarkServiceImpl implements BookmarkServiceInterface {
+
+    private final BookmarkRepository bookmarkRepository;
+    private final UserRepository userRepository;
+    private final QnaPostRepository qnaPostRepository;
+    private final BookmarkMapper bookmarkMapper;
+
+    @Override
+    @Transactional
+    public void addBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // QNA 게시글 존재 여부 확인
+        if (bookmarkType == BookmarkType.QNA && !qnaPostRepository.existsById(targetId)) {
+            throw new EntityNotFoundException("QnA 게시글이 존재하지 않습니다.");
+        }
+
+        // 이미 북마크했는지 확인 (메모리상에서 체크)
+        boolean exists = bookmarkRepository.findAll().stream()
+                .anyMatch(b -> b.getUser().getId().equals(user.getId())
+                        && b.getBookmarkType() == bookmarkType
+                        && b.getTargetId().equals(targetId));
+        if (exists) return;
+
+        Bookmark bookmark = new Bookmark();
+        bookmark.setUser(user);
+        bookmark.setBookmarkType(bookmarkType);
+        bookmark.setTargetId(targetId);
+        bookmarkRepository.save(bookmark);
+    }
+
+    @Override
+    @Transactional
+    public void removeBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // 메모리상에서 찾아서 삭제
+        bookmarkRepository.findAll().stream()
+                .filter(b -> b.getUser().getId().equals(user.getId())
+                        && b.getBookmarkType() == bookmarkType
+                        && b.getTargetId().equals(targetId))
+                .forEach(bookmarkRepository::delete);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public List<BookmarkDTO> getBookmarksByUser(String userLoginId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        return bookmarkRepository.findAll().stream()
+                .filter(b -> b.getUser().getId().equals(user.getId()))
+                .filter(b -> b.getBookmarkType() != BookmarkType.QNA || qnaPostRepository.existsById(b.getTargetId()))
+                .map(bookmarkMapper::toDTO)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public boolean isBookmarked(String userLoginId, BookmarkType bookmarkType, Long targetId) {
path: src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java
user: login: somecreater
body: findAll 쓰지 마세요
created_at: 2025-05-09T02:58:36Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,99 @@
+package com.petservice.main.bookmark.service;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.Bookmark;
+import com.petservice.main.user.database.entity.BookmarkType;
+import com.petservice.main.user.database.entity.User;
+import com.petservice.main.user.database.mapper.BookmarkMapper;
+import com.petservice.main.user.database.repository.BookmarkRepository;
+import com.petservice.main.user.database.repository.UserRepository;
+import com.petservice.main.qna.database.repository.QnaPostRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class BookmarkServiceImpl implements BookmarkServiceInterface {
+
+    private final BookmarkRepository bookmarkRepository;
+    private final UserRepository userRepository;
+    private final QnaPostRepository qnaPostRepository;
+    private final BookmarkMapper bookmarkMapper;
+
+    @Override
+    @Transactional
+    public void addBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // QNA 게시글 존재 여부 확인
+        if (bookmarkType == BookmarkType.QNA && !qnaPostRepository.existsById(targetId)) {
+            throw new EntityNotFoundException("QnA 게시글이 존재하지 않습니다.");
+        }
+
+        // 이미 북마크했는지 확인 (메모리상에서 체크)
+        boolean exists = bookmarkRepository.findAll().stream()
+                .anyMatch(b -> b.getUser().getId().equals(user.getId())
+                        && b.getBookmarkType() == bookmarkType
+                        && b.getTargetId().equals(targetId));
+        if (exists) return;
+
+        Bookmark bookmark = new Bookmark();
+        bookmark.setUser(user);
+        bookmark.setBookmarkType(bookmarkType);
+        bookmark.setTargetId(targetId);
+        bookmarkRepository.save(bookmark);
+    }
+
+    @Override
+    @Transactional
+    public void removeBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // 메모리상에서 찾아서 삭제
+        bookmarkRepository.findAll().stream()
+                .filter(b -> b.getUser().getId().equals(user.getId())
+                        && b.getBookmarkType() == bookmarkType
+                        && b.getTargetId().equals(targetId))
+                .forEach(bookmarkRepository::delete);
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public List<BookmarkDTO> getBookmarksByUser(String userLoginId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        return bookmarkRepository.findAll().stream()
+                .filter(b -> b.getUser().getId().equals(user.getId()))
+                .filter(b -> b.getBookmarkType() != BookmarkType.QNA || qnaPostRepository.existsById(b.getTargetId()))
+                .map(bookmarkMapper::toDTO)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    @Transactional(readOnly = true)
+    public boolean isBookmarked(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        return bookmarkRepository.findAll().stream()
+                .anyMatch(b -> b.getUser().getId().equals(user.getId())
+                        && b.getBookmarkType() == bookmarkType
+                        && b.getTargetId().equals(targetId));
+    }
+
+    @Override
+    @Transactional
+    public void cleanupDeletedItem(BookmarkType bookmarkType, Long targetId) {
path: src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java
user: login: somecreater
body: findall 쓰지마세요.
created_at: 2025-05-09T02:58:55Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,99 @@
+package com.petservice.main.bookmark.service;
+
+import com.petservice.main.user.database.dto.BookmarkDTO;
+import com.petservice.main.user.database.entity.Bookmark;
+import com.petservice.main.user.database.entity.BookmarkType;
+import com.petservice.main.user.database.entity.User;
+import com.petservice.main.user.database.mapper.BookmarkMapper;
+import com.petservice.main.user.database.repository.BookmarkRepository;
+import com.petservice.main.user.database.repository.UserRepository;
+import com.petservice.main.qna.database.repository.QnaPostRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@RequiredArgsConstructor
+public class BookmarkServiceImpl implements BookmarkServiceInterface {
+
+    private final BookmarkRepository bookmarkRepository;
+    private final UserRepository userRepository;
+    private final QnaPostRepository qnaPostRepository;
+    private final BookmarkMapper bookmarkMapper;
+
+    @Override
+    @Transactional
+    public void addBookmark(String userLoginId, BookmarkType bookmarkType, Long targetId) {
+        User user = userRepository.findByUserLoginId(userLoginId)
+                .orElseThrow(() -> new EntityNotFoundException("사용자를 찾을 수 없습니다"));
+
+        // QNA 게시글 존재 여부 확인
+        if (bookmarkType == BookmarkType.QNA && !qnaPostRepository.existsById(targetId)) {
+            throw new EntityNotFoundException("QnA 게시글이 존재하지 않습니다.");
+        }
+
+        // 이미 북마크했는지 확인 (메모리상에서 체크)
+        boolean exists = bookmarkRepository.findAll().stream()
path: src/main/java/com/petservice/main/bookmark/service/BookmarkServiceImpl.java
user: login: Suu-a
body: BookmarkRepository를 수정하지 않고, findAll()도 쓰지 않으면서 "특정 조건(예: user, type, targetId)에 맞는 데이터만" 가져오려면,
JPA 기본 메서드만으로는 불가능합니다. 라고하는데 어떻게 해야하나요?
created_at: 2025-05-09T03:42:10Z
author_association: COLLABORATOR

PR comments:
user: login: somecreater
created_at: 2025-05-08T06:24:46Z
body: dto, entity, mapper, repository 새로 만들필요 없습니다. 이미 user 패키지에 존재해요. 그부분 삭제하고 다시 구현해주세요
author_association: OWNER
user: login: somecreater
created_at: 2025-05-09T03:00:27Z
body: 최대한 findAll 쓰지 않고 리포지토리 적게 쓰는 방향으로 수정해주시고, 그리고 제가 추후 북마크 DTO에 사용자 로그인 아이디 추가하고 그거에 따른 수정을 할게요
author_association: OWNER