Skip to content

[Feat] 피드 삭제 api 개발 및 post soft delete 전략 도입#173

Merged
hd0rable merged 30 commits intodevelopfrom
feat/#160-feed-delete
Aug 11, 2025
Merged

[Feat] 피드 삭제 api 개발 및 post soft delete 전략 도입#173
hd0rable merged 30 commits intodevelopfrom
feat/#160-feed-delete

Conversation

@hd0rable
Copy link
Member

@hd0rable hd0rable commented Aug 8, 2025

#️⃣ 연관된 이슈

closes #160

📝 작업 내용

  • 피드 삭제 api를 개발했습니다.
  • post에 소프트 딜리트 전략을도입해 관련 어노테이션을 추가하고, 자식들인 record,feed,vote에도 관련 어노테이션 추가했습니다.
  • 삭제시 용이하게 삭제용 양방향 매핑관계를 추가했습니다. 주석처리를 해놨기때문에 삭제시에만 사용하는걸로 유의하시면될것같습니다!
  • post가 소프트 딜리트로 변하면서 테스트메서드시에 deleteAll처리시 테스트가 실패해서 관련함수 deleteAllInBatch() 처리했습니다
  • 관련 통합 테스트 코드 작성했습니다

📸 스크린샷

image

💬 리뷰 요구사항

간단한 api인데 상속구조 관련 소프트 딜리트시 처리해줘야하는 어노테이션을 하나빼먹어서 개발속도가 느려졌습니다.. 분발하겟습니다.. 관련해서 노션에 문서화 해두었습니다(진짜 별거아닌데 혼자 헤맴)

📌 PR 진행 시 이러한 점들을 참고해 주세요

* P1 : 꼭 반영해 주세요 (Request Changes) - 이슈가 발생하거나 취약점이 발견되는 케이스 등
* P2 : 반영을 적극적으로 고려해 주시면 좋을 것 같아요 (Comment)
* P3 : 이런 방법도 있을 것 같아요~ 등의 사소한 의견입니다 (Chore)

Summary by CodeRabbit

  • 신규 기능

    • 피드 삭제 API 추가 — 사용자가 자신의 피드를 삭제할 수 있습니다.
    • 피드 삭제 시 연관된 사진, 태그, 저장된 피드, 좋아요 및 댓글(및 댓글 좋아요)이 함께 삭제 또는 소프트 삭제됩니다.
    • 댓글·게시물 좋아요 등 연관 데이터 일괄 삭제 지원 추가.
  • 테스트

    • 피드 삭제 동작을 검증하는 통합 테스트 및 도메인 단위 테스트 추가.
    • 댓글 삭제 검증 간소화 및 일부 테스트의 데이터 정리 방식이 배치 삭제로 개선됨.
  • 기타

    • 이미 삭제된 게시물 관련 오류 코드/메시지 추가.

@coderabbitai
Copy link

coderabbitai bot commented Aug 8, 2025

Walkthrough

피드 삭제 기능이 추가되어 컨트롤러, 유스케이스, 서비스, 퍼시스턴스, JPA 엔티티, 레포지토리, 예외·스웨거 정의 및 관련 통합·단위 테스트가 추가/수정되었습니다. 삭제 시 연관된 댓글·댓글좋아요·포스트좋아요·태그·컨텐츠·저장피드 등이 일괄 삭제 또는 soft delete 됩니다.

Changes

Cohort / File(s) Change Summary
Feed 삭제 API 및 서비스
src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java, src/main/java/konkuk/thip/feed/application/port/in/FeedDeleteUseCase.java, src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java
DELETE 엔드포인트 추가, FeedDeleteUseCase 인터페이스와 FeedDeleteService 구현(권한검증, 연관 엔티티 삭제 호출) 추가.
Feed JPA 및 퍼시스턴스 변경
src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java, src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java, src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/.../FeedJpaRepository.java, src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedTag/FeedTagJpaRepository.java, src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/Content/ContentJpaRepository.java, src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java, src/main/java/konkuk/thip/feed/application/port/out/FeedCommandPort.java
Feed 엔티티에 태그/저장피드 연관 컬렉션 추가, findByPostIdAndStatus 조회 추가, FeedCommandPersistenceAdapter에 delete 로직(연관 태그/컨텐츠/저장피드 삭제 후 soft-delete) 추가.
댓글/댓글좋아요 관련 변경
src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java, src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java, src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentJpaRepository.java, src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java, src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java
Comment ↔ CommentLike 양방향 매핑 추가, 댓글 좋아요 일괄 삭제 JPQL 추가, postId 기준 댓글 일괄 soft-delete 메서드 추가 및 포트에 노출.
포스트/포스트좋아요 관련 변경
src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java, src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java, src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java, src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java
Post 엔티티에 댓글/좋아요 연관 컬렉션 및 softDelete 메서드 추가, 포스트 좋아요 일괄 삭제 메서드 추가.
예외·스웨거·에러코드 확장
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java, src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
새로운 에러코드 POST_ALREADY_DELETED 추가 및 Swagger 응답 항목 FEED_DELETE 추가.
테스트 변경/추가
src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java, src/test/java/konkuk/thip/feed/domain/FeedTest.java, 기타 테스트 .../BookDetailSearchControllerTest.java, .../RoomPlayingDetailViewApiTest.java, .../VoteCreateControllerTest.java, .../CommentDeleteAPITest.java
Feed 삭제 통합 테스트 및 Feed.validateDeletable 단위 테스트 추가, 일부 테스트에서 deleteAll → deleteAllInBatch 변경 및 soft-delete 검증 간소화.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FeedCommandController
    participant FeedDeleteService
    participant FeedCommandPort
    participant CommentCommandPort
    participant PostLikeCommandPort

    Client->>FeedCommandController: DELETE /feeds/{feedId} (userId)
    FeedCommandController->>FeedDeleteService: deleteFeed(feedId, userId)
    FeedDeleteService->>FeedCommandPort: getByIdOrThrow(feedId)
    FeedDeleteService->>FeedCommandPort: feed.validateDeletable(userId)
    FeedDeleteService->>CommentCommandPort: softDeleteAllByPostId(feedId)
    FeedDeleteService->>PostLikeCommandPort: deleteAllByPostId(feedId)
    FeedDeleteService->>FeedCommandPort: delete(feed)  -- 삭제: 태그/컨텐츠/저장피드 삭제 후 feed soft-delete
    FeedCommandController-->>Client: 204 No Content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Assessment against linked issues

Objective Addressed Explanation
피드 삭제 API 개발 (#160)
피드 삭제시 연관 엔티티 일괄 삭제/soft delete 처리 (#160)
피드 삭제 권한 검증 구현 (#160)
피드 삭제 통합/단위 테스트 추가 (#160)

Possibly related PRs

Suggested labels

🍀 refactor

Suggested reviewers

  • seongjunnoh
  • buzz0331

Poem

🥕
내가 깡충 뛴 건 새 피드 한 채,
댓글·좋아요·태그도 함께 안녕이라네.
삭제 요청 오면 깔끔히 정리하고,
토끼는 또 밭으로 달려가지요.
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 67812e5 and 2319004.

📒 Files selected for processing (1)
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#160-feed-delete

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java (1)

13-14: VoteJpaEntity와 동일한 CASCADE 설정 호환성 확인이 필요합니다.

RecordJpaEntity에도 @OnDelete(action = OnDeleteAction.CASCADE) 애노테이션이 추가되었는데, 이는 VoteJpaEntity와 동일한 패턴입니다. soft delete 전략과 DATABASE 레벨 CASCADE 삭제 간의 호환성을 확인해야 합니다.

이전에 VoteJpaEntity에서 요청한 검증이 RecordJpaEntity에도 동일하게 적용됩니다.

Also applies to: 21-21

🧹 Nitpick comments (3)
src/main/java/konkuk/thip/feed/application/port/in/FeedDeleteUseCase.java (1)

4-6: 피드 삭제 Use Case 인터페이스가 잘 설계되었습니다.

Clean Architecture 원칙을 따르는 명확한 인터페이스입니다. feedIduserId 파라미터를 통해 삭제할 피드와 권한 검증에 필요한 정보를 모두 제공합니다.

향후 유지보수성을 위해 JavaDoc 주석 추가를 고려해보세요:

+/**
+ * 피드 삭제를 처리하는 Use Case 인터페이스
+ */
 public interface FeedDeleteUseCase {
+    /**
+     * 피드를 삭제합니다.
+     * @param feedId 삭제할 피드 ID
+     * @param userId 요청한 사용자 ID (권한 검증용)
+     * @throws BusinessException 권한이 없거나 피드를 찾을 수 없는 경우
+     */
     void deleteFeed(Long feedId, Long userId);
 }
src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (1)

107-107: 세미콜론 중복 제거

Line 107에 불필요한 세미콜론이 중복되어 있습니다.

-        tagJpaRepository.deleteAllInBatch();;
+        tagJpaRepository.deleteAllInBatch();
src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java (1)

46-48: 피드 태그 연관관계 매핑이 올바름

주석에 "삭제요"라는 오타가 있지만, 매핑 설정 자체는 CascadeType.REMOVE와 orphanRemoval = true로 적절히 구성되었습니다.

-    // 삭제요 피드 태그 양방향 매핑 관계
+    // 삭제용 피드 태그 양방향 매핑 관계
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ded26f5 and 2329047.

📒 Files selected for processing (19)
  • src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java (2 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java (3 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java (2 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java (3 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/port/in/FeedDeleteUseCase.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/port/out/FeedCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java (1 hunks)
  • src/main/java/konkuk/thip/feed/domain/Feed.java (1 hunks)
  • src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java (2 hunks)
  • src/main/java/konkuk/thip/record/adapter/out/jpa/RecordJpaEntity.java (1 hunks)
  • src/main/java/konkuk/thip/vote/adapter/out/jpa/VoteJpaEntity.java (1 hunks)
  • src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchControllerTest.java (2 hunks)
  • src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java (3 hunks)
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (1 hunks)
  • src/test/java/konkuk/thip/feed/domain/FeedTest.java (1 hunks)
  • src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java (1 hunks)
  • src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-03T03:05:05.031Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#43
File: src/main/java/konkuk/thip/book/application/port/out/BookCommandPort.java:0-0
Timestamp: 2025-07-03T03:05:05.031Z
Learning: THIP 프로젝트에서는 CQRS Port 분리 시 다음 컨벤션을 따름: CommandPort에는 findByXXX를 통해 도메인 엔티티를 찾아오는 메서드를 추가하고, QueryPort에는 조회 API의 response에 해당하는 데이터들을 DB로부터 조회하는 메서드를 추가함.

Applied to files:

  • src/main/java/konkuk/thip/feed/application/port/out/FeedCommandPort.java
📚 Learning: 2025-06-29T09:47:31.299Z
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#36
File: src/main/java/konkuk/thip/user/adapter/out/persistence/UserJpaRepository.java:7-7
Timestamp: 2025-06-29T09:47:31.299Z
Learning: Spring Data JPA에서 findBy{FieldName} 패턴의 메서드는 명시적 선언 없이 자동으로 생성되며, Optional<Entity> 반환 타입을 사용하는 것이 null 안전성을 위해 권장됩니다.

Applied to files:

  • src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedJpaRepository.java
📚 Learning: 2025-07-23T17:41:55.507Z
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#101
File: src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java:118-265
Timestamp: 2025-07-23T17:41:55.507Z
Learning: CommentControllerTest는 댓글 생성 API의 검증 로직과 예외 상황만을 테스트하는 단위 테스트이며, 성공 케이스는 별도의 통합 테스트(CommentCreateAPITest)에서 다룬다.

Applied to files:

  • src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java
🔇 Additional comments (31)
src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1)

195-198: 피드 삭제 API에 대한 Swagger 문서화가 적절합니다.

FEED_DELETE enum 상수가 올바른 에러 코드들(FEED_NOT_FOUND, FEED_ACCESS_FORBIDDEN)과 함께 추가되어, 피드 삭제 API의 예외 상황을 잘 문서화하고 있습니다.

src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedJpaRepository.java (2)

9-10: Optional import 추가가 적절합니다.

새로 추가된 메소드의 반환 타입을 위해 필요한 import입니다.


19-19: Soft delete 전략을 위한 메소드가 적절하게 추가되었습니다.

findByPostIdAndStatus 메소드는 Spring Data JPA의 메소드 네이밍 컨벤션을 따르며, Optional 반환 타입으로 null 안전성을 보장합니다. StatusType을 활용한 soft delete 전략에 필요한 기능입니다.

src/test/java/konkuk/thip/room/adapter/in/web/RoomPlayingDetailViewApiTest.java (1)

80-80: 테스트 정리 로직이 batch 삭제로 개선되었습니다.

deleteAllInBatch()는 단일 쿼리로 여러 엔티티를 삭제하여 성능상 이점을 제공합니다. Soft delete 전략과 cascade 관계가 복잡해진 상황에서 적절한 변경입니다.

src/main/java/konkuk/thip/feed/application/port/out/FeedCommandPort.java (1)

19-19: Feed 삭제를 위한 CommandPort 메소드가 적절하게 추가되었습니다.

delete(Feed feed) 메소드는 CQRS 패턴의 CommandPort 컨벤션에 부합하며, 도메인 엔티티를 매개변수로 받는 올바른 구조입니다. 피드 삭제 기능 구현에 필요한 포트 메소드입니다.

src/test/java/konkuk/thip/vote/adapter/in/web/VoteCreateControllerTest.java (1)

81-81: 테스트 정리 성능이 batch 삭제로 개선되었습니다.

다른 테스트 클래스들과 일관되게 deleteAllInBatch()로 변경되어 테스트 실행 성능이 향상되었습니다. Soft delete 전략 도입에 맞춘 적절한 개선입니다.

src/main/java/konkuk/thip/feed/domain/Feed.java (2)

132-132: 메시지 업데이트가 적절합니다.

삭제 기능 추가에 맞춰 예외 메시지가 "수정/삭제"로 업데이트되어 사용자에게 명확한 권한 정보를 제공합니다.


136-138: 삭제 권한 검증 메서드가 잘 구현되었습니다.

현재는 validateCreator를 호출하는 단순한 구현이지만, 도메인의 의도를 명확히 하고 향후 삭제 전용 검증 로직을 추가할 수 있는 확장성을 제공합니다.

src/test/java/konkuk/thip/comment/adapter/in/web/CommentDeleteAPITest.java (3)

115-115: 테스트 검증 로직 개선이 적절합니다.

findByCommentIdAndStatus 메서드를 사용하여 soft delete 검증을 단일 assertion으로 간소화한 것이 좋습니다. 기존의 2단계 검증보다 가독성과 효율성이 향상되었습니다.


135-135: 일관된 검증 방식 적용이 우수합니다.

대댓글 삭제 테스트에서도 동일한 패턴으로 soft delete 검증을 개선한 것이 좋습니다. 테스트 코드의 일관성이 유지되었습니다.


157-157: 검증 로직 통일이 적절합니다.

모든 댓글 삭제 테스트에서 동일한 검증 방식을 사용하여 코드 품질과 유지보수성이 향상되었습니다.

src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java (3)

26-26: 필요한 import 추가가 적절합니다.

ACTIVE 상태 상수를 import하여 soft delete 전략을 지원하는 변경이 좋습니다.


44-44: Soft delete 전략에 맞는 조회 로직 개선입니다.

findByPostIdAndStatus를 사용하여 활성 상태의 피드만 조회하도록 변경한 것이 soft delete 전략에 적합합니다.


111-117: 피드 삭제 구현이 적절합니다.

피드 존재 여부 검증 후 삭제하는 로직이 간단하고 명확하게 구현되었습니다. 예외 처리도 적절합니다.

src/test/java/konkuk/thip/feed/domain/FeedTest.java (1)

350-358: 삭제 권한 검증 테스트가 적절합니다.

작성자가 아닌 유저의 삭제 시도 시 FEED_ACCESS_FORBIDDEN 예외가 발생하는지 검증하는 로직이 올바르게 구현되었습니다.

src/main/java/konkuk/thip/comment/adapter/out/jpa/CommentJpaEntity.java (2)

13-14: 필요한 import 추가가 적절합니다.

ArrayList와 List import 추가가 새로운 필드를 위해 필요한 변경입니다.


58-60: Cascade delete 전략을 위한 양방향 매핑이 적절합니다.

댓글 삭제 시 관련 좋아요도 함께 삭제되도록 CascadeType.REMOVEorphanRemoval = true 설정이 올바르게 구현되었습니다. 주석으로 용도를 명시한 것도 좋습니다.

src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java (1)

1-30: 피드 삭제 서비스 구현이 우수합니다.

서비스 구조와 로직이 잘 설계되었습니다:

  • @Transactional 어노테이션으로 트랜잭션 경계 설정이 적절
  • 3단계 삭제 로직이 명확하고 순서가 적절 (조회 → 권한검증 → 삭제)
  • TODO 주석으로 S3 이미지 삭제에 대한 향후 개선 계획을 명시한 것이 좋음
  • 의존성 주입과 예외 처리가 적절하게 구현됨
src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java (4)

4-4: 적절한 임포트 추가

소프트 삭제와 연관관계 매핑을 위한 필요한 임포트들이 정확히 추가되었습니다.

Also applies to: 10-13


21-21: 소프트 삭제 전략이 올바르게 구현됨

@SQLDelete 애노테이션을 통해 물리적 삭제 대신 status 컬럼을 'INACTIVE'로 업데이트하는 소프트 삭제 전략이 적절히 구현되었습니다.


48-50: 포스트 좋아요 연관관계 매핑이 적절함

CascadeType.REMOVE와 orphanRemoval = true 설정으로 게시물 삭제 시 관련 좋아요도 함께 삭제되도록 올바르게 구성되었습니다.


44-46: 댓글 엔티티 소프트 삭제 적용 확인 완료

댓글만을 물리 삭제하지 않고 상태만 ‘INACTIVE’로 변경하도록 @SQLDelete(sql = "UPDATE comments SET status = 'INACTIVE' WHERE comment_id = ?")CommentJpaEntity에 선언되어 있습니다.
따라서 cascade = CascadeType.REMOVE + orphanRemoval = true 설정 시에도 소프트 삭제가 정상 동작하여 추가 조치가 필요 없습니다.

src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (4)

44-48: 통합 테스트 설정이 적절함

@SpringBootTest, @activeprofiles("test"), @AutoConfigureMockMvc 설정이 피드 삭제 API 통합 테스트에 적합하게 구성되었습니다.


76-93: 테스트 데이터 설정이 포괄적임

피드 삭제 시 연쇄 삭제되어야 하는 모든 관련 엔티티들(태그, 댓글, 좋아요, 저장된 피드 등)을 적절히 생성하여 완전한 테스트 시나리오를 구성했습니다.


95-110: 테스트 정리 로직이 소프트 삭제 전략에 맞게 구현됨

소프트 삭제로 인해 기존 deleteAll() 대신 deleteAllInBatch()를 사용하여 테스트 데이터를 물리적으로 삭제하는 것이 올바른 접근입니다.


112-141: 삭제 검증 로직이 철저함

소프트 삭제된 피드(INACTIVE 상태)와 연관 엔티티들의 물리적 삭제를 모두 검증하여 삭제 API의 동작을 완전히 확인했습니다.

src/main/java/konkuk/thip/feed/adapter/in/web/FeedCommandController.java (2)

18-18: 의존성 추가가 적절함

FeedDeleteUseCase 의존성이 올바르게 추가되고 생성자 주입을 통해 관리되고 있습니다.

Also applies to: 39-39


96-107: 피드 삭제 엔드포인트가 잘 구현됨

OpenAPI 문서화, 예외 처리 설명, 파라미터 설정이 다른 엔드포인트와 일관성 있게 구현되었으며, 삭제 후 적절한 응답을 반환합니다.

src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java (3)

9-9: 삭제 지원을 위한 임포트가 적절히 추가됨

연관 엔티티 및 데이터베이스 레벨 cascade 삭제를 위한 필요한 임포트들이 정확히 추가되었습니다.

Also applies to: 15-16, 18-18


26-26: 데이터베이스 레벨 cascade 삭제 설정이 적절함

@onDelete(action = OnDeleteAction.CASCADE) 애노테이션을 통해 데이터베이스 레벨에서도 연쇄 삭제가 보장되도록 설정되었습니다.


42-44: 저장된 피드 연관관계 매핑이 올바름

삭제용 양방향 매핑이 CascadeType.REMOVE와 orphanRemoval = true로 적절히 설정되어 피드 삭제 시 저장 관계도 함께 정리됩니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2329047 and 56a466a.

📒 Files selected for processing (3)
  • src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java (2 hunks)
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (1 hunks)
  • src/test/java/konkuk/thip/feed/domain/FeedTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/java/konkuk/thip/feed/domain/FeedTest.java
  • src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-23T17:41:55.507Z
Learnt from: hd0rable
PR: THIP-TextHip/THIP-Server#101
File: src/test/java/konkuk/thip/comment/adapter/in/web/CommentControllerTest.java:118-265
Timestamp: 2025-07-23T17:41:55.507Z
Learning: CommentControllerTest는 댓글 생성 API의 검증 로직과 예외 상황만을 테스트하는 단위 테스트이며, 성공 케이스는 별도의 통합 테스트(CommentCreateAPITest)에서 다룬다.

Applied to files:

  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (4)
src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (4)

1-48: 테스트 클래스 구조와 import 구성이 적절합니다.

통합 테스트에 필요한 모든 의존성이 올바르게 import되었고, Spring Boot 테스트 어노테이션 구성도 적절합니다. 한국어 DisplayName도 명확하게 테스트 목적을 설명하고 있습니다.


50-75: 의존성 주입이 체계적으로 구성되었습니다.

피드 삭제와 관련된 모든 Repository가 적절히 주입되어 있어 테스트 데이터 설정과 검증이 가능합니다. 필드 선언도 테스트에 필요한 엔티티들로 구성되어 있습니다.


76-93: 테스트 데이터 설정이 포괄적이고 현실적입니다.

피드 삭제 시나리오를 검증하기 위해 필요한 모든 연관 엔티티(사용자, 카테고리, 도서, 태그, 피드, 댓글, 좋아요, 저장된 피드)가 적절히 생성되었습니다. 특히 cascading delete 동작을 확인하기 위한 복합적인 관계 설정이 잘 되어 있습니다.


112-141: 테스트 검증 로직이 soft delete 전략을 정확히 검증합니다.

각 연관 엔티티의 삭제 동작(soft delete vs hard delete)을 명확히 구분하여 검증하고 있습니다. 특히 피드와 댓글은 INACTIVE 상태로 soft delete되고, 매핑 테이블들은 완전히 삭제되는 것을 올바르게 확인하고 있습니다.

seongjunnoh
seongjunnoh previously approved these changes Aug 8, 2025
Copy link
Collaborator

@seongjunnoh seongjunnoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다! 간단히 궁금한점 하나 리뷰로 남겼는데 확인부탁드립니다!

}

public void validateDeletable(Long userId) {
validateCreator(userId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 이 메서드 굉장히 유용하게 쓰이네요 굳굳

// 2. 피드 삭제 권한 검증
feed.validateDeletable(userId);

// TODO S3 이미지 삭제 이벤트 기반 처리 or 배치 삭제
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳 이건 동기적으로 삭제하는게 아니라 일단 TODO로 정의하고 리펙단계에서 일괄적으로 처리하시죠

Comment on lines +42 to +48
// 삭제용 피드 저장 양방향 매핑 관계
@OneToMany(mappedBy = "feedJpaEntity", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<SavedFeedJpaEntity> savedFeeds = new ArrayList<>();

// 삭제용 피드 태그 양방향 매핑 관계
@OneToMany(mappedBy = "feedJpaEntity", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<FeedTagJpaEntity> feedTags = new ArrayList<>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 확인했습니다

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이러면 피드 삭제시에 이와 1:n 으로 연관된 친구들은 실제로 삭제가 되고, feed에 해당하는 post 는 soft delete 가 되는 구조겠네요. 굳굳

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OneToMany는 소프트 딜리트 전파가 된다고 하셨던 것 같은데 맞나요?? @hd0rable

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 아니용 전파안되고 그 엔티티에 sqlDelete 어노테이션 적용안되어있으면 하드딜리트되는 걸 확인했습니당

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그래서 SavedFeed,FeedTag는 엔티티자체에 소프트 딜리트 어노테이션이 적용되지않기때문에 하드딜리트됩니닷

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 저는 연쇄 삭제가 일어날때 SQLDelete가 달려있는 엔티티는 소프트 딜리트가 되는게 맞는지 궁금합니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다~~

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

화긴이요~~

@DiscriminatorValue("FEED")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@OnDelete(action = OnDeleteAction.CASCADE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 찾아보니 해당 어노테이션은 joined 전략으로 연결된 posts 테이블의 hard delete 가 발생할 경우, 이와 연관된 row의 hard delete 가 발생하도록 하는 hibernate 의 어노테이션이네요
그런데 저희는 posts table 에서 soft delete 를 적용하니(= DB에 delete 쿼리가 아니라 update 쿼리가 날라가니) 실제로 @onDelete 가 동작하지는 않을 거 같은데, 이는 가독성을 위해 추가하신 어노테이션인 걸까요?? 궁금해서 여쭤봅니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아니요.. 이어노테이션을 적용하지않으면 post는 sotf delete 되지만 해당 자식 테이블인 record,vote,feed의 row가 사라지는 문제가 발생해서 이 처리를 해줘야한다고하네요.. 실제로 이부분에서 많이 헤매서 개발속도가 늦어졌습니다.. 관련하여 노션에 문서화해두었으니 확인해주시면 감사하겠습니다!!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 찾아보니 @OnDelete 어노테이션은 하드 딜리트를 전파하는 기능이지, 소프트 딜리트를 전파하는 기능이 아니어서 삭제 전파 용도로는 동작하지 않는 것이 맞는 것 같습니다.

그렇다면 왜 @OnDelete를 붙였을때는 Feed의 row가 삭제되지 않고, 붙이지 않았을 때는 Feed의 row가 삭제되는지를 찾아봤습니다. 원인은 Hibernate의 JOINED 상속시 삭제 처리 순서에 있었습니다.

Hibernate에서는 JOINED 전략의 하위 엔티티에 대해 삭제할 때 다음 순서로 동작합니다.

  1. 서브 테이블 DELETE
  2. 부모 테이블 DELETE

현재 저희는 1번 단계에서 @SQLDelete가 적용되어 있지 않기 때문에(Feed 테이블에 status가 없어 적용 불가) Feed row가 하드 딜리트로 삭제됩니다.
이후 2번 단계에서는 부모(Post)에 @SQLDelete가 적용되어 있어 실제 DELETE 대신 UPDATE로 status만 INACTIVE로 변경됩니다.

결론적으로, 현재 희진님이 추가하신 @onDelete는 DB 연쇄 삭제 용도라기보다는 서브 테이블 삭제를 Hibernate가 생략하도록 만드는 효과로 동작하고 있다고 보는 게 맞을 것 같습니다.

지금은 원하는 동작이 나오고 있으니 그대로 둬도 무방하겠지만, @SQLDelete@OnDelete는 철학이 다르기 때문에 혼용 시 주의가 필요해보입니다.
개인적으로는 Post ↔ Feed, Record, Vote 같이 상속 관계에 있는 엔티티에서는 @SQLDelete를 제거하고, delete 호출 시 JPQL로 명시적으로 UPDATE를 날리는 방식이 더 명확하지 않을까 싶은데..

@hd0rable @seongjunnoh 두 분 의견이 궁금합니다!!

Copy link
Collaborator

@seongjunnoh seongjunnoh Aug 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 자세한 설명 감사합니다!!!

@onDelete 어노테이션 덕분에 피드 -> 포스트 로의 delete 연산이 수행된 것이 아니라, 포스트 -> 피드 로의 delete 연산이 발생하는데, 이때 포스트는 delete 쿼리가 나가는 hard delete 가 아니므로 결론적으로 피드에도 delete 쿼리가 나가지 않아 피드의 row가 삭제되지 않는 상황이군요!!

그러면 현준님이 제안하신 방식으로 피드 삭제를 진행한다면

  1. 영속성 adapter 의 delete 메서드 내부에서 FeedJpaEntity 의 부모인 PostJpaEntity 에 대하여 status update 메서드 호출(@SQLDelete 사용 X)
  2. 이후 FeedJpaEntity 와 연관관계를 맺고 있는 모든 엔티티를 삭제

이런 플로우가 되는 걸까요??

2번의 과정을 위해 추가적인 코드가 필요하겠지만, 이 방식이 더 명시적이고 덜 헷갈릴 것 같습니다!! (잘 모르는 jpa 기술들에 의존하니 이해하기도 어렵네요 하하)

추가로 이렇게 수정하면 @onetomany CascadeType.REMOVE 속성에 의해 연관된 엔티티들을 삭제하는 것이 아니므로, 현준님이 리뷰에서 언급하신 1:n 관계인 엔티티들 삭제시 n+1 문제 발생 도 해결할 수 있을 것 같습니다!!
-> 연관된 모든 엔티티들 조회 + 이들 일괄 삭제 : 이렇게 2번의 쿼리로 해결되지 않을까요 ??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현준님이 제안하신대로 @SQLDelete 없이 post의 status를 업데이트 한 후 save하여 변경감지로 인해 피드가 소프트 딜리트 되도록 수정했습니다!

추가로 이렇게 수정하면 @onetomany CascadeType.REMOVE 속성에 의해 연관된 엔티티들을 삭제하는 것이 아니므로, 현준님이 리뷰에서 언급하신 1:n 관계인 엔티티들 삭제시 n+1 문제 발생 도 해결할 수 있을 것 같습니다!!
-> 연관된 모든 엔티티들 조회 + 이들 일괄 삭제 : 이렇게 2번의 쿼리로 해결되지 않을까요 ??

이부부분에 대해서는 피드 삭제 시의 연관된 모든 엔티티를 배치 쿼리로 삭제하므로 연관된 엔티티당 1회 쿼리가 발생하여

피드 댓글의 좋아요 DELETE 1쿼리 (서브쿼리로 일괄 삭제)
피드 댓글 소프트 딜리트 UPDATE 1쿼리
피드 좋아요 DELETE 1쿼리
피드 태그 관계 DELETE 1쿼리
피드 콘텐츠 DELETE 1쿼리
피드 저장 관계 DELETE 1쿼리
피드 쿼리 소프트 딜리트 UPDATE 1쿼리

총 7번 쿼리가 나가는 것을 확인했습니다
image
image
image

@Override
public Optional<Feed> findById(Long id) {
return feedJpaRepository.findById(id)
return feedJpaRepository.findByPostIdAndStatus(id, ACTIVE)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳굳

Comment on lines +44 to +50
// 삭제용 게시물 댓글 양방향 매핑 관계
@OneToMany(mappedBy = "postJpaEntity", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<CommentJpaEntity> commentList = new ArrayList<>();

// 삭제용 게시물 좋아요 양방향 매핑 관계
@OneToMany(mappedBy = "postJpaEntity", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<PostLikeJpaEntity> postLikeList = new ArrayList<>();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다

Comment on lines +122 to +140
// then: 1) 피드 soft delete (status=INACTIVE)
assertThat(feedJpaRepository.findByPostIdAndStatus(feed.getPostId(), INACTIVE)).isPresent();

// 2) 피드 태그 관계 삭제
assertTrue(feedTagJpaRepository.findAll().isEmpty());

// 3) 콘텐츠 삭제
assertTrue(contentJpaRepository.findAll().isEmpty());

// 4) 댓글 삭제 soft delete
assertThat(commentJpaRepository.findById(comment.getCommentId())).isPresent();
assertThat(commentJpaRepository.findById(comment.getCommentId()).get().getStatus()).isEqualTo(INACTIVE);

// 5) 댓글 좋아요 삭제
long commentLikeCountAfter = commentLikeJpaRepository.count();
assertThat(commentLikeCountAfter).isEqualTo(0);

// 6) 피드 저장 관계 삭제
assertTrue(savedFeedJpaRepository.findAll().isEmpty());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳굳

Copy link
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다~~ 새로운 어노테이션이 보여 조금 찾아보느라 리뷰가 늦었습니다!!

카톡에서 말씀드렸던 것처럼 현재 OneToMany에 의해서 삭제 쿼리가 나가는 것을 보니 Feed 하나에 Content 3개가 매핑되어 있을때 Content를 한번에 삭제시키는 것이 아닌 3번의 delete 쿼리를 호출하고 있어, N+1 문제가 발생하고 있는 것 같네요. 우선 저희 연관관계가 너무 복잡하다 보니 OneToMany를 이용하고 추후에 삭제가 이루어지는 순서를 확인해서 Adapter 쪽에서 저희가 명시적으로 delete를 해주는게 좋아보입니다!

@DiscriminatorValue("FEED")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@OnDelete(action = OnDeleteAction.CASCADE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 찾아보니 @OnDelete 어노테이션은 하드 딜리트를 전파하는 기능이지, 소프트 딜리트를 전파하는 기능이 아니어서 삭제 전파 용도로는 동작하지 않는 것이 맞는 것 같습니다.

그렇다면 왜 @OnDelete를 붙였을때는 Feed의 row가 삭제되지 않고, 붙이지 않았을 때는 Feed의 row가 삭제되는지를 찾아봤습니다. 원인은 Hibernate의 JOINED 상속시 삭제 처리 순서에 있었습니다.

Hibernate에서는 JOINED 전략의 하위 엔티티에 대해 삭제할 때 다음 순서로 동작합니다.

  1. 서브 테이블 DELETE
  2. 부모 테이블 DELETE

현재 저희는 1번 단계에서 @SQLDelete가 적용되어 있지 않기 때문에(Feed 테이블에 status가 없어 적용 불가) Feed row가 하드 딜리트로 삭제됩니다.
이후 2번 단계에서는 부모(Post)에 @SQLDelete가 적용되어 있어 실제 DELETE 대신 UPDATE로 status만 INACTIVE로 변경됩니다.

결론적으로, 현재 희진님이 추가하신 @onDelete는 DB 연쇄 삭제 용도라기보다는 서브 테이블 삭제를 Hibernate가 생략하도록 만드는 효과로 동작하고 있다고 보는 게 맞을 것 같습니다.

지금은 원하는 동작이 나오고 있으니 그대로 둬도 무방하겠지만, @SQLDelete@OnDelete는 철학이 다르기 때문에 혼용 시 주의가 필요해보입니다.
개인적으로는 Post ↔ Feed, Record, Vote 같이 상속 관계에 있는 엔티티에서는 @SQLDelete를 제거하고, delete 호출 시 JPQL로 명시적으로 UPDATE를 날리는 방식이 더 명확하지 않을까 싶은데..

@hd0rable @seongjunnoh 두 분 의견이 궁금합니다!!

Comment on lines +42 to +48
// 삭제용 피드 저장 양방향 매핑 관계
@OneToMany(mappedBy = "feedJpaEntity", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<SavedFeedJpaEntity> savedFeeds = new ArrayList<>();

// 삭제용 피드 태그 양방향 매핑 관계
@OneToMany(mappedBy = "feedJpaEntity", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<FeedTagJpaEntity> feedTags = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OneToMany는 소프트 딜리트 전파가 된다고 하셨던 것 같은데 맞나요?? @hd0rable

Comment on lines 111 to 117
@Override
public void delete(Feed feed) {
FeedJpaEntity feedJpaEntity = feedJpaRepository.findById(feed.getId())
.orElseThrow(() -> new EntityNotFoundException(FEED_NOT_FOUND));

feedJpaRepository.delete(feedJpaEntity);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3: 흠 크게 문제가 될 것 같진않지만 이미 서비스 로직에서 findBy를 하고 온 상태라 id만 넘기면 어떨까 싶긴합니다!!

Copy link
Member Author

@hd0rable hd0rable Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇근데 그러면 서비스에서 무조건 id를 찾는다는 의존성이 생길것같아서 deleteById로 차라리 서비스에서 조회하고 온경우/조회하지않은경우 사용하도록 deleteById 함수를 하나 뚫어놓는건 어떨까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗근데 직접 엔티티의 status를 인액티브로 바꾸려면 어차피 조회를 다시해야하네욧..id만으로는 delete를 하지못하럿같습니다!

Comment on lines +122 to +140
// then: 1) 피드 soft delete (status=INACTIVE)
assertThat(feedJpaRepository.findByPostIdAndStatus(feed.getPostId(), INACTIVE)).isPresent();

// 2) 피드 태그 관계 삭제
assertTrue(feedTagJpaRepository.findAll().isEmpty());

// 3) 콘텐츠 삭제
assertTrue(contentJpaRepository.findAll().isEmpty());

// 4) 댓글 삭제 soft delete
assertThat(commentJpaRepository.findById(comment.getCommentId())).isPresent();
assertThat(commentJpaRepository.findById(comment.getCommentId()).get().getStatus()).isEqualTo(INACTIVE);

// 5) 댓글 좋아요 삭제
long commentLikeCountAfter = commentLikeJpaRepository.count();
assertThat(commentLikeCountAfter).isEqualTo(0);

// 6) 피드 저장 관계 삭제
assertTrue(savedFeedJpaRepository.findAll().isEmpty());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

삭제 전략을 각 엔티티에 따라 엄청 세세하게 테스트 하셨네요!! LGTM!!

# Conflicts:
#	src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java
#	src/main/java/konkuk/thip/feed/application/port/out/FeedCommandPort.java
@github-actions
Copy link

github-actions bot commented Aug 10, 2025

Test Results

370 tests   370 ✅  31s ⏱️
109 suites    0 💤
109 files      0 ❌

Results for commit 2319004.

♻️ This comment has been updated with latest results.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (5)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (2)

170-170: 메시지 띄어쓰기 통일(맞춤법): “게시물입니다.”

파일 내 메시지들이 “입니다.”/“ 입니다.”가 혼재합니다. 본 라인은 붙여쓰기가 맞춤법상 권장됩니다.

-    POST_ALREADY_DELETED(HttpStatus.BAD_REQUEST, 180001, "이미 삭제된 게시물 입니다."),
+    POST_ALREADY_DELETED(HttpStatus.BAD_REQUEST, 180001, "이미 삭제된 게시물입니다."),

169-171: 삭제 API 재삭제 요청 처리 로직 확인 및 API 계약 검토 필요

– PostJpaEntity.softDelete()에서 상태가 INACTIVE인 경우
· InvalidStateException(POST_ALREADY_DELETED) 발생
· ErrorCode.POST_ALREADY_DELETED에 매핑된 HttpStatus.BAD_REQUEST(400)로 응답
– FeedCommandPersistenceAdapter#delete 흐름: softDelete() 호출 후 save 처리 확인
– GlobalExceptionHandler의 BusinessExceptionHandler에서 InvalidStateException → 400으로 변환됨

현재 코드베이스의 “이미 좋아요/언팔로우” 유사 케이스와 일관되나, 삭제 API의 멱등성을 고려해 아래 방안을 API 계약 차원에서 합의하세요:

  • 재삭제 시에도 200 또는 204로 성공 응답하여 멱등성 보장
  • 에러 유지 시 409 (Conflict) 또는 410 (Gone) 적용

위 검토 후 API 명세에 반영 바랍니다.

src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java (1)

8-8: 포트 계약에 삭제 영향 행 수 반환 고려(선택)

대량 삭제의 결과를 상위 계층에서 검증/로깅하려면 int 반환이 유용합니다. 현재 void여도 동작에는 문제 없습니다만, 일관된 모니터링을 원하시면 변경을 검토해 주세요.

-    void deleteAllByPostId(Long postId);
+    int deleteAllByPostId(Long postId);

포트 시그니처 변경 시 어댑터/서비스/테스트의 연쇄 반영이 필요합니다.

src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java (1)

26-26: 명명/계약 일관성 및 책임 범위 명시 제안

메서드가 댓글의 soft delete를 수행하고, 그 전에 댓글 좋아요 물리 삭제가 선행되는 도메인 규칙이라면 Javadoc로 책임과 순서를 명시해 주세요. 포트 내 다른 메서드(delete vs softDelete)와의 용어 일관성도 검토 부탁드립니다.

예)

  • softDeleteAllByPostId는 “댓글 좋아요 제거 후 댓글 soft delete” 수행을 명시
  • 또는 삭제 정책(soft/hard)을 인자로 추상화한 별도 유스케이스로 분리
src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java (1)

17-18: 단순 조회는 파생 쿼리로 대체 가능하며, soft delete 정합성도 점검해주세요

  • 현재 JPQL은 파생 메서드로 대체 가능합니다. 장점: 필드명 변경 시 컴파일 타임 검출에 유리, 유지보수성 향상.
  • 예: List<SavedFeedJpaEntity> findAllByUserJpaEntityUserId(Long userId);
  • 만약 연관 Feed가 soft delete일 때 제외되어야 한다면, 현 전략(삭제 전에 관련 SavedFeed를 하드 삭제)으로 충분한지, 또는 별도 필터링이 필요한지 확인 권장합니다.
  • N+1 우려가 있다면 별도 읽기 전용 메서드에 join fetch를 고려하세요.

권장 변경안:

-    @Query("SELECT sf FROM SavedFeedJpaEntity sf WHERE sf.userJpaEntity.userId = :userId")
-    List<SavedFeedJpaEntity> findAllByUserId(@Param("userId") Long userId);
+    List<SavedFeedJpaEntity> findAllByUserJpaEntityUserId(Long userId);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56a466a and e69a8f2.

📒 Files selected for processing (19)
  • src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java (3 hunks)
  • src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentLikeJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/comment/application/port/out/CommentCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java (2 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java (4 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/Content/ContentJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedTag/FeedTagJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/port/out/FeedCommandPort.java (1 hunks)
  • src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java (1 hunks)
  • src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java (2 hunks)
  • src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java (1 hunks)
  • src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeJpaRepository.java (1 hunks)
  • src/main/java/konkuk/thip/post/application/port/out/PostLikeCommandPort.java (1 hunks)
  • src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchControllerTest.java (2 hunks)
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/test/java/konkuk/thip/book/adapter/in/web/BookDetailSearchControllerTest.java
  • src/main/java/konkuk/thip/feed/application/port/out/FeedCommandPort.java
  • src/main/java/konkuk/thip/common/swagger/SwaggerResponseDescription.java
  • src/main/java/konkuk/thip/feed/adapter/out/persistence/FeedCommandPersistenceAdapter.java
  • src/main/java/konkuk/thip/post/adapter/out/jpa/PostJpaEntity.java
  • src/main/java/konkuk/thip/feed/adapter/out/jpa/FeedJpaEntity.java
  • src/test/java/konkuk/thip/feed/adapter/in/web/FeedDeleteAPITest.java
  • src/main/java/konkuk/thip/feed/application/service/FeedDeleteService.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
src/main/java/konkuk/thip/common/exception/code/ErrorCode.java (1)

169-171: 에러 코드 중복 검사 완료
전역 검사 결과 중복된 에러 코드가 없으며, 코드 180001도 유일하게 존재합니다. 추가 변경 불필요합니다.

src/main/java/konkuk/thip/post/adapter/out/persistence/PostLikeCommandPersistenceAdapter.java (1)

47-51: 이미 상위 서비스에서 트랜잭션이 보장됩니다

FeedDeleteService.deleteFeed 메서드에 @Transactional이 적용되어 있어, PostLikeCommandPersistenceAdapter.deleteAllByPostId는 이미 트랜잭션 내부에서 호출됩니다.
따라서 어댑터 메서드에 별도로 @Transactional을 추가할 필요는 없습니다.

  • 리포지토리 메서드를 int 반환으로 변경하여 삭제 건수를 로깅하면 운영 가시성 향상에 도움이 됩니다.
src/main/java/konkuk/thip/comment/adapter/out/persistence/repository/CommentJpaRepository.java (1)

6-8: 필요한 import 추가, 적절합니다

벌크 업데이트용 @Modifying, @query, @param 도입은 적절합니다.

src/main/java/konkuk/thip/comment/adapter/out/persistence/CommentCommandPersistenceAdapter.java (2)

6-6: CommentLikeJpaRepository 의존성 추가, 적절합니다

댓글 삭제(소프트)와 함께 좋아요 정리를 위한 의존성 주입은 타당합니다.


34-34: 필드 주입(생성자 기반) 추가, 적절합니다

생성자 주입 방식으로 CommentLikeJpaRepository 추가는 일관되고 안전합니다.

Comment on lines +23 to +25
@Modifying
@Query("DELETE FROM SavedFeedJpaEntity sf WHERE sf.feedJpaEntity.postId = :feedId")
int deleteAllByFeedId(@Param("feedId") Long feedId);
Copy link

@coderabbitai coderabbitai bot Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

신규 bulk delete: clearAutomatically 추가 권장 + 설계 일관성 확인

  • bulk 연산 후 1차 캐시를 자동 정리하도록 @Modifying(clearAutomatically = true)를 권장합니다.
  • 반환 타입을 int로 둔 점 좋습니다(삭제 건수 확인 가능).
  • 의도: Feed는 soft delete, SavedFeed는 하드 삭제로 보입니다. 도메인 의도라면 일관됩니다. 향후 SavedFeed도 soft delete 대상이라면 DELETE 대신 soft 플래그 업데이트로 전환되어야 합니다.

권장 변경안:

-    @Modifying
+    @Modifying(clearAutomatically = true)
     @Query("DELETE FROM SavedFeedJpaEntity sf WHERE sf.feedJpaEntity.postId = :feedId")
     int deleteAllByFeedId(@Param("feedId") Long feedId);

추가 확인 포인트:

  • 경로 sf.userJpaEntity.userId / sf.feedJpaEntity.postId가 실제 엔티티 필드와 일치하는지.
  • 호출부가 항상 트랜잭션 내부인지(서비스/어댑터 @Transactional) — bulk delete 특성상 중요.

🏁 Script executed:

#!/bin/bash
# 1) 엔티티 필드 경로 검증(간이): 관련 엔티티의 필드명을 확인
rg -n --hidden --ignore-case "class SavedFeedJpaEntity|userJpaEntity|feedJpaEntity" -g "**/*.java"

# 2) soft delete 적용 대상 확인: SavedFeed/Content/FeedTag/Feed의 @SQLDelete/@Where 존재 여부
rg -n "@SQLDelete|@Where" -g "**/*JpaEntity.java"

# 3) 본 메서드 호출부 확인(일관된 사용/트랜잭션 경계 확인)
rg -n "deleteAllByFeedId\\(" -g "**/*.java"

# 4) Feed 삭제 유스케이스/어댑터 트랜잭션 확인
rg -n "@Transactional|FeedDeleteService|FeedCommandPersistenceAdapter" -g "src/**/*.java"

Length of output: 164629


bulk 삭제 후 1차 캐시 자동 정리 적용 권장

  • 대상 메서드:
    src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java
    deleteAllByFeedId(@param("feedId") Long feedId)
  • 쿼리 경로(sf.feedJpaEntity.postId, sf.userJpaEntity.userId)와 반환 타입(int)은 모두 정확합니다.
  • 이 메서드는 FeedDeleteService(@transactional) 내부에서 호출되어 트랜잭션 경계가 보장됩니다.
  • SavedFeedJpaEntity에는 @SQLDelete/@where가 적용되어 있지 않아 하드 삭제가 의도된 설계이며, 도메인 의도에도 부합합니다.
  • 향후 SavedFeed에도 soft delete를 적용해야 할 경우 DELETE 대신 상태 플래그 업데이트로 전환하세요.

권장 변경안:

-    @Modifying
+    @Modifying(clearAutomatically = true)
     @Query("DELETE FROM SavedFeedJpaEntity sf WHERE sf.feedJpaEntity.postId = :feedId")
     int deleteAllByFeedId(@Param("feedId") Long feedId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Modifying
@Query("DELETE FROM SavedFeedJpaEntity sf WHERE sf.feedJpaEntity.postId = :feedId")
int deleteAllByFeedId(@Param("feedId") Long feedId);
@Modifying(clearAutomatically = true)
@Query("DELETE FROM SavedFeedJpaEntity sf WHERE sf.feedJpaEntity.postId = :feedId")
int deleteAllByFeedId(@Param("feedId") Long feedId);
🤖 Prompt for AI Agents
In
src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/SavedFeedJpaRepository.java
around lines 23 to 25, the deleteAllByFeedId method performs a bulk delete but
does not clear the first-level cache automatically. To fix this, add the
clearAutomatically = true attribute to the @Modifying annotation to ensure the
persistence context is cleared after the bulk operation, preventing stale
entities in the cache.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Modifying(clearAutomatically = true) 이 어노테이션이 무슨 역할을 하는데?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

왜 안알려줘

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai @Modifying(clearAutomatically = true)는 무슨 역할을 하는 어노테이션이야?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@buzz0331, @Modifying(clearAutomatically = true) 어노테이션은 Spring Data JPA에서 다음과 같은 역할을 합니다:

주요 기능

1. 영속성 컨텍스트 자동 정리

  • Bulk 연산(DELETE, UPDATE) 실행 후 1차 캐시를 자동으로 비웁니다
  • JPA의 영속성 컨텍스트에 남아있는 엔티티들을 clear() 처리합니다

왜 필요한가?

캐시 불일치 문제 방지:

// 예시: clearAutomatically = false (기본값)인 경우
SavedFeedJpaEntity entity = repository.findById(1L); // 1차 캐시에 저장됨
repository.deleteAllByFeedId(feedId); // DB에서는 삭제되지만 캐시는 그대로
SavedFeedJpaEntity stale = repository.findById(1L); // 캐시에서 가져옴 (삭제된 데이터!)

clearAutomatically = true 설정 시:

SavedFeedJpaEntity entity = repository.findById(1L); 
repository.deleteAllByFeedId(feedId); // DB 삭제 + 캐시 자동 정리
SavedFeedJpaEntity fresh = repository.findById(1L); // DB에서 새로 조회 (null 반환)

현재 코드에 적용이 필요한 이유

deleteAllByFeedId 메서드는 bulk DELETE 연산이므로:

  • 여러 SavedFeed 엔티티를 한 번에 삭제합니다
  • 이 엔티티들이 이미 영속성 컨텍스트에 로드되어 있을 수 있습니다
  • clearAutomatically = true 없이는 삭제된 엔티티가 캐시에 남아 데이터 불일치를 일으킬 수 있습니다

특히 FeedDeleteService처럼 하나의 트랜잭션 내에서 여러 연관 엔티티를 다룰 때 이런 설정이 중요합니다.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai 그렇다면 @Modifying 어노테이션의 default는 뭘로 설정되어 있는데 다 false로 설정되어 있나?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for @Modifying annotation definition in Spring Data JPA
# First, let's look for any @Modifying usage in the codebase to understand the pattern
rg "@Modifying" -A 1 -B 1 --type java

# Count how many @Modifying annotations are used without explicit parameters
echo "---"
echo "Modifying annotations without explicit parameters:"
rg "@Modifying\s*$" --type java | wc -l

# Count how many have clearAutomatically set
echo "Modifying annotations with clearAutomatically:"
rg "@Modifying.*clearAutomatically" --type java | wc -l

Length of output: 5436


🌐 Web query:

Spring Data JPA @Modifying annotation default values clearAutomatically flushAutomatically

💡 Result:

The default values for the Spring Data JPA @Modifying annotation are clearAutomatically = false and flushAutomatically = false, meaning neither automatic clearing nor flushing of the persistence context is performed unless configured explicitly[6][7][8].

Context and Supporting Details:

  • @Modifying annotation marks a query as a modifying query (for INSERT, UPDATE, DELETE, or DDL) rather than a read-only query[6].
  • clearAutomatically:
    • Default: false
    • If set to true, the persistence context is cleared automatically after executing the modifying query. This is useful to prevent any out-of-sync issues between your context and the actual database, especially after bulk operations[2][6][9].
  • flushAutomatically:
    • Default: false
    • If set to true, it causes the persistence context to be flushed before executing the modifying query, ensuring that all pending changes are synchronized with the database before performing the operation[6][7].
Attribute Default Value Purpose
clearAutomatically false Clears the persistence context after the query
flushAutomatically false Flushes the persistence context before the query

Why change defaults:

  • Setting clearAutomatically = true is recommended for bulk updates/deletes to avoid unexpected results due to dirty entities remaining in memory[2][4].
  • Setting flushAutomatically = true can prevent pending changes from being lost if the persistence context is cleared after the operation, which can occur when using both options together[4].

Clarification of Ambiguity:

  • Some older or informal sources may suggest different defaults, but the official Spring Data JPA documentation lists both as false by default in all major versions[6][7][8].
  • Both attributes are only meaningful when using @Modifying in combination with a custom @query on a repository method[6].

If further clarification is needed on a specific version or scenario, please specify.

Citations:


@buzz0331, 네, @Modifying 어노테이션의 기본값은 모두 false로 설정되어 있습니다:

기본값 정리

속성 기본값 설명
clearAutomatically false 쿼리 실행 후 영속성 컨텍스트를 자동으로 비우지 않음
flushAutomatically false 쿼리 실행 전 영속성 컨텍스트를 자동으로 flush하지 않음

현재 코드베이스 상황

코드베이스를 확인한 결과:

  • 12개@Modifying 어노테이션이 파라미터 없이 사용 중
  • 0개clearAutomatically를 명시적으로 설정
  • 즉, 모든 bulk 연산이 기본값(false)을 사용 중

영향받는 메서드들:

  • SavedFeedJpaRepository: deleteByUserIdAndFeedId, deleteAllByFeedId
  • PostLikeJpaRepository: deleteByUserIdAndPostId, deleteAllByPostId
  • CommentJpaRepository: softDeleteAllByPostId
  • CommentLikeJpaRepository: deleteByUserIdAndCommentId, deleteAllByCommentId, deleteAllByPostId
  • FeedTagJpaRepository: deleteAllByFeedId
  • ContentJpaRepository: deleteAllByFeedId
  • 등등...

잠재적 문제

기본값 사용 시 다음과 같은 상황이 발생할 수 있습니다:

// 예시: FeedDeleteService 내부
Feed feed = feedRepository.findById(feedId);  // 1차 캐시에 로드
savedFeedRepository.deleteAllByFeedId(feedId); // DB에서만 삭제, 캐시는 유지
// feed와 연관된 SavedFeed들이 캐시에 남아있어 불일치 발생 가능

특히 피드 삭제처럼 여러 연관 엔티티를 한 트랜잭션에서 처리하는 경우, 캐시 불일치로 인한 예기치 않은 동작이 발생할 수 있습니다.

buzz0331
buzz0331 previously approved these changes Aug 10, 2025
Copy link
Contributor

@buzz0331 buzz0331 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿굿 수고하셨습니다~~~ 연관관계가 많아서 확실히 어질어질하네여 😵‍💫

Comment on lines +34 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 서브 쿼리 굿굿입니다!!

Comment on lines 31 to 35
// 3. 피드 삭제
commentCommandPort.softDeleteAllByPostId(feedId);
postLikeCommandPort.deleteAllByPostId(feedId);
feedCommandPort.delete(feed);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 port로 너무 잘 나눠두셨네요 너무 좋습니다!!

  • CommentCommandPort#softDeleteAllByPostId : 댓글 좋아요 하드 딜리트 -> 댓글 소프트 딜리트
  • PostLikeCommandPort#deleteAllByPostId : 게시글 좋아요 하드 딜리트
  • FeedCommandPort#delete : 피드 태그 관계 하드 딜리트 -> 컨텐츠 하드 딜리트 -> 저장된 피드 하드 딜리트 -> 피드 소프트 딜리트

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

역시 문귀,문악,문장

Comment on lines +23 to +25
@Modifying
@Query("DELETE FROM SavedFeedJpaEntity sf WHERE sf.feedJpaEntity.postId = :feedId")
int deleteAllByFeedId(@Param("feedId") Long feedId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai @Modifying(clearAutomatically = true)는 무슨 역할을 하는 어노테이션이야?

Comment on lines +15 to +17

@Modifying
@Query("UPDATE CommentJpaEntity c SET c.status = 'INACTIVE' WHERE c.postJpaEntity.postId = :postId")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 이러면 단일 Comment 삭제시에는 그냥 delete로 SQLDelete 어노테이션을 사용하고 다른 엔티티에 연관된 Comment 삭제시에는 위 쿼리를 사용해서 벌크 삭제를 하는 식이겠네요! 좋습니다!!

@hd0rable hd0rable merged commit 4354c72 into develop Aug 11, 2025
4 checks passed
@hd0rable hd0rable deleted the feat/#160-feed-delete branch August 11, 2025 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[THIP2025-218] [feat] 피드 삭제 api 개발

3 participants