Skip to content

feat: 커뮤니티 좋아요 API#120

Merged
chaechaepower merged 2 commits intomainfrom
feat/#119-community-like
Apr 20, 2026
Merged

feat: 커뮤니티 좋아요 API#120
chaechaepower merged 2 commits intomainfrom
feat/#119-community-like

Conversation

@chaechaepower
Copy link
Copy Markdown
Member

@chaechaepower chaechaepower commented Apr 20, 2026

🧷 이슈

🔨 작업 내용

  • 게시글 좋아요 API
  • 게시글 좋아요 여부 조회 API

👀 리뷰 요구사항

Summary by CodeRabbit

새로운 기능

  • 게시물 좋아요 기능 추가
    • 게시물에 대한 좋아요 설정 및 취소 기능 제공
    • 특정 게시물에 대한 사용자의 좋아요 여부 확인 기능 추가
    • 게시물 목록 및 상세 페이지에서 좋아요 수 표시

@chaechaepower chaechaepower self-assigned this Apr 20, 2026
@chaechaepower chaechaepower added the ✨ Feature 기능 개발 label Apr 20, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Walkthrough

게시물 좋아요 기능을 구현하는 새로운 서비스, 엔티티, 리포지토리, API 컨트롤러 및 응답 DTO들이 추가되었습니다. Post 엔티티에서 likeCount 필드를 제거하고, PostLike 엔티티를 통해 좋아요를 동적으로 관리하도록 변경되었습니다.

Changes

Cohort / File(s) Summary
Service Layer
src/main/java/kr/co/webee/application/post/service/PostLikeService.java, src/main/java/kr/co/webee/application/post/service/PostService.java
PostLikeService 신규 추가로 좋아요 토글 및 상태 조회 기능 제공. PostService는 PostLikeRepository를 주입받아 좋아요 개수를 동적으로 계산하여 응답 DTO에 전달.
Domain Model
src/main/java/kr/co/webee/domain/post/entity/Post.java, src/main/java/kr/co/webee/domain/post/entity/PostLike.java
Post 엔티티에서 likeCount 필드 제거. PostLike 신규 엔티티 추가로 Post와 User 간의 다대일 관계 매핑.
Repository Layer
src/main/java/kr/co/webee/domain/post/repository/PostLikeRepository.java
PostLikeRepository 신규 추가로 특정 게시물의 좋아요 조회, 개수 계산, 사용자 좋아요 여부 확인 메서드 제공.
API & Controller
src/main/java/kr/co/webee/presentation/post/api/PostLikeApi.java, src/main/java/kr/co/webee/presentation/post/controller/PostLikeController.java
PostLikeApi 인터페이스 및 PostLikeController 신규 추가로 좋아요 토글 및 상태 조회 엔드포인트 구현. Swagger 문서화 포함.
Response DTOs
src/main/java/kr/co/webee/presentation/post/dto/response/PostLikeToggleResponse.java, src/main/java/kr/co/webee/presentation/post/dto/response/PostLikeStatusResponse.java, src/main/java/kr/co/webee/presentation/post/dto/response/PostDetailResponse.java, src/main/java/kr/co/webee/presentation/post/dto/response/PostListResponse.java
PostLikeToggleResponse와 PostLikeStatusResponse 신규 추가. PostDetailResponse와 PostListResponse의 from() 메서드 서명을 변경하여 명시적 likeCount 파라미터 추가.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant PostLikeController
    participant PostLikeService
    participant PostRepository
    participant UserRepository
    participant PostLikeRepository
    participant Database

    Client->>PostLikeController: POST /api/v1/posts/{postId}/likes
    PostLikeController->>PostLikeService: toggleLike(postId, userId)
    PostLikeService->>PostRepository: findById(postId)
    PostRepository->>Database: SELECT post WHERE id = ?
    Database-->>PostRepository: post
    PostRepository-->>PostLikeService: post
    alt post not found
        PostLikeService-->>PostLikeController: throw BusinessException(POST_NOT_FOUND)
        PostLikeController-->>Client: 404 error response
    else post exists
        PostLikeService->>UserRepository: findById(userId)
        UserRepository->>Database: SELECT user WHERE id = ?
        Database-->>UserRepository: user
        UserRepository-->>PostLikeService: user
        alt user not found
            PostLikeService-->>PostLikeController: throw EntityNotFoundException
            PostLikeController-->>Client: error response
        else user exists
            PostLikeService->>PostLikeRepository: findByPostIdAndUserId(postId, userId)
            PostLikeRepository->>Database: SELECT postLike WHERE postId = ? AND userId = ?
            Database-->>PostLikeRepository: postLike (if exists)
            PostLikeRepository-->>PostLikeService: Optional<PostLike>
            alt like exists
                PostLikeService->>PostLikeRepository: delete(postLike)
                PostLikeRepository->>Database: DELETE FROM postLike
                Database-->>PostLikeRepository: success
                PostLikeRepository-->>PostLikeService: void
                PostLikeService->>PostLikeRepository: countByPostId(postId)
            else like does not exist
                PostLikeService->>PostLikeRepository: save(new PostLike)
                PostLikeRepository->>Database: INSERT INTO postLike
                Database-->>PostLikeRepository: saved postLike
                PostLikeRepository-->>PostLikeService: postLike
                PostLikeService->>PostLikeRepository: countByPostId(postId)
            end
            PostLikeRepository->>Database: SELECT COUNT(*) FROM postLike WHERE postId = ?
            Database-->>PostLikeRepository: count
            PostLikeRepository-->>PostLikeService: likeCount
            PostLikeService-->>PostLikeController: PostLikeToggleResponse(liked, likeCount)
            PostLikeController-->>Client: 200 OK with response
        end
    end
Loading
sequenceDiagram
    participant Client
    participant PostLikeController
    participant PostLikeService
    participant PostRepository
    participant PostLikeRepository
    participant Database

    Client->>PostLikeController: GET /api/v1/posts/{postId}/likes/me
    PostLikeController->>PostLikeService: getMyLikeStatus(postId, userId)
    PostLikeService->>PostRepository: findById(postId)
    PostRepository->>Database: SELECT post WHERE id = ?
    Database-->>PostRepository: post
    PostRepository-->>PostLikeService: post
    alt post not found
        PostLikeService-->>PostLikeController: throw BusinessException(POST_NOT_FOUND)
        PostLikeController-->>Client: 404 error response
    else post exists
        PostLikeService->>PostLikeRepository: existsByPostIdAndUserId(postId, userId)
        PostLikeRepository->>Database: SELECT EXISTS(...) FROM postLike WHERE postId = ? AND userId = ?
        Database-->>PostLikeRepository: boolean
        PostLikeRepository-->>PostLikeService: liked (true/false)
        PostLikeService-->>PostLikeController: PostLikeStatusResponse(liked)
        PostLikeController-->>Client: 200 OK with response
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #116: Post 도메인의 PostService, Post 엔티티, 게시물 DTO들을 수정하여 이 PR의 likeCount 필드 제거 및 PostLike 관계 추가와 직접적으로 연관됨.

Poem

🐰 새로운 좋아요 기능이 꼬물꼬물
PostLike 테이블에 사랑이 쌓이고
동적 계산으로 한 번 더 깡충깡충
리포지토리가 정확히 세어주니
게시물 하나하나에 스며드는 행복이네 ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 '커뮤니티 좋아요 API'로 변경 사항의 핵심(게시글 좋아요 기능 추가)을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed 게시글 좋아요 토글 API, 좋아요 여부 조회 API 구현으로 이슈 #119의 커뮤니티 좋아요 CRUD API 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed Post 엔티티에서 likeCount 필드 제거는 좋아요 기능 구현과 관련된 정상적인 리팩토링으로, 모든 변경이 요구사항 범위 내에 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#119-community-like

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
src/main/java/kr/co/webee/application/post/service/PostService.java (1)

29-40: 목록 조회의 per-row count 쿼리를 배치 집계로 줄이는 것을 권장합니다.

현재 findAll(pageable) 이후 게시글마다 좋아요 수와 댓글 수를 각각 조회해 목록 크기가 N이면 대략 1 + 2N개의 쿼리가 발생합니다. 새로 추가된 Line 38 때문에 목록 API의 DB 부하가 더 커질 수 있으니, 현재 Slice의 post id들을 모아 GROUP BY post_id 집계로 한 번에 가져오는 방식이 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/kr/co/webee/application/post/service/PostService.java` around
lines 29 - 40, getAllPosts currently calls postRepository.findAll(pageable) then
for each Post invokes postLikeRepository.countByPostId(...) and
postCommentRepository.countByPostId(...), causing O(N) extra queries; instead,
collect the Slice<Post> ids, call batch aggregate queries (e.g., new repository
methods like countByPostIdInGroup or a custom `@Query` that returns postId->count
maps) to fetch like and comment counts in two grouped queries, then map those
counts back to each Post when calling PostListResponse.from so you build the
Slice with no per-row count queries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/kr/co/webee/application/post/service/PostLikeService.java`:
- Around line 33-55: Add a safe concurrency fix for toggling likes: either (A)
enforce DB uniqueness by adding `@Table`(uniqueConstraints =
`@UniqueConstraint`(columnNames = {"post_id","user_id"})) to the PostLike entity
and create the corresponding migration, then catch
DataIntegrityViolationException in PostLikeService around
postLikeRepository.save(...) to convert the race-error into a successful
"already liked" PostLikeToggleResponse (use
postLikeRepository.countByPostId(postId) for the count), or (B) make the
check-then-write atomic by adding `@Lock`(LockModeType.PESSIMISTIC_WRITE) to the
repository method used by
postLikeRepository.findByPostIdAndUserId(postId,userId) so concurrent toggles
serialize; pick one approach and update PostLikeService to handle the chosen
behavior consistently (refer to PostLike, PostLikeService,
postLikeRepository.findByPostIdAndUserId, postLikeRepository.save, and
PostLikeToggleResponse).

In `@src/main/java/kr/co/webee/domain/post/entity/PostLike.java`:
- Around line 21-33: Add a DB-level unique constraint on (post_id, user_id) and
handle races in toggleLike(): annotate the PostLike entity with
`@Table`(uniqueConstraints = `@UniqueConstraint`(columnNames =
{"post_id","user_id"})) to ensure uniqueness at the schema level and add a DB
migration (ALTER TABLE ... ADD CONSTRAINT/UNIQUE INDEX) so the constraint exists
independent of ddl-auto; then update the toggleLike() flow to avoid
find-then-act races by attempting a single save and catching
DataIntegrityViolationException (or a specific constraint violation) to treat
concurrent insert as "already liked" and re-query using
repository.findByPostIdAndUserId(...) as needed, or perform a delete via
repository.deleteByPostAndUser(...) for unlike operations; ensure methods
countByPostId() and findByPostIdAndUserId() are still used only for
read-after-write verification, not for enforcing uniqueness.

---

Nitpick comments:
In `@src/main/java/kr/co/webee/application/post/service/PostService.java`:
- Around line 29-40: getAllPosts currently calls
postRepository.findAll(pageable) then for each Post invokes
postLikeRepository.countByPostId(...) and
postCommentRepository.countByPostId(...), causing O(N) extra queries; instead,
collect the Slice<Post> ids, call batch aggregate queries (e.g., new repository
methods like countByPostIdInGroup or a custom `@Query` that returns postId->count
maps) to fetch like and comment counts in two grouped queries, then map those
counts back to each Post when calling PostListResponse.from so you build the
Slice with no per-row count queries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 77919fdc-0c07-4b72-9ba2-4f29d18a833e

📥 Commits

Reviewing files that changed from the base of the PR and between 229bcae and 66bd283.

📒 Files selected for processing (11)
  • src/main/java/kr/co/webee/application/post/service/PostLikeService.java
  • src/main/java/kr/co/webee/application/post/service/PostService.java
  • src/main/java/kr/co/webee/domain/post/entity/Post.java
  • src/main/java/kr/co/webee/domain/post/entity/PostLike.java
  • src/main/java/kr/co/webee/domain/post/repository/PostLikeRepository.java
  • src/main/java/kr/co/webee/presentation/post/api/PostLikeApi.java
  • src/main/java/kr/co/webee/presentation/post/controller/PostLikeController.java
  • src/main/java/kr/co/webee/presentation/post/dto/response/PostDetailResponse.java
  • src/main/java/kr/co/webee/presentation/post/dto/response/PostLikeStatusResponse.java
  • src/main/java/kr/co/webee/presentation/post/dto/response/PostLikeToggleResponse.java
  • src/main/java/kr/co/webee/presentation/post/dto/response/PostListResponse.java
💤 Files with no reviewable changes (1)
  • src/main/java/kr/co/webee/domain/post/entity/Post.java

Comment on lines +33 to +55
return postLikeRepository.findByPostIdAndUserId(postId, userId)
.map(postLike -> {
postLikeRepository.delete(postLike);

return PostLikeToggleResponse.of(
false,
postLikeRepository.countByPostId(postId)
);

})
.orElseGet(() -> {
postLikeRepository.save(
PostLike.builder()
.post(post)
.user(user)
.build()
);

return PostLikeToggleResponse.of(
true,
postLikeRepository.countByPostId(postId)
);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 설명: PostLike의 유니크 제약/잠금/충돌 처리 여부를 확인합니다.
# 기대 결과: (post_id, user_id) 유니크 제약과 DataIntegrityViolationException 처리 또는 잠금 전략이 확인되어야 합니다.

fd -i 'PostLike|schema|migration|changelog' -t f | xargs -r sed -n '1,220p'

rg -n -C3 --type java 'DataIntegrityViolationException|PESSIMISTIC_WRITE|LockModeType|findByPostIdAndUserId'

rg -n -C3 'uniqueConstraints|unique\s*=\s*true|post_id.*user_id|user_id.*post_id|CREATE UNIQUE|UNIQUE INDEX'

Repository: BeeKeeprs/was

Length of output: 11640


🏁 Script executed:

# PostLike 엔티티의 완전한 정의 확인 (모든 어노테이션 포함)
cat src/main/java/kr/co/webee/domain/post/entity/PostLike.java

Repository: BeeKeeprs/was

Length of output: 1236


🏁 Script executed:

# 마이그레이션/스키마 파일 찾기
fd -type f \( -name '*.sql' -o -name '*.xml' -o -name '*.yaml' -o -name '*migration*' -o -name '*changelog*' \) | head -20

Repository: BeeKeeprs/was

Length of output: 227


🏁 Script executed:

# ErrorType.VIOLATION_OCCURRED의 정의 확인
rg -n 'VIOLATION_OCCURRED' --type java -B2 -A2

Repository: BeeKeeprs/was

Length of output: 1951


🏁 Script executed:

# 모든 마이그레이션/스키마 파일 찾기
find . -type f \( -name '*.sql' -o -name '*.xml' -o -name '*migration*' -o -name '*changelog*' \)

Repository: BeeKeeprs/was

Length of output: 39


🏁 Script executed:

# 리소스 디렉토리에서 스키마 관련 파일 찾기
find src/main/resources src/test/resources -type f 2>/dev/null | grep -i -E '(schema|migration|liquibase|flyway|sql)'

Repository: BeeKeeprs/was

Length of output: 39


🏁 Script executed:

# post_id와 user_id의 조합을 언급하는 SQL/XML 파일 검색
rg -l 'post_id.*user_id|user_id.*post_id|PostLike' --type sql --type xml 2>/dev/null || echo "No matches"

Repository: BeeKeeprs/was

Length of output: 67


🏁 Script executed:

# `@Table` 어노테이션이 있는지 전체 PostLike 파일 확인
cat -n src/main/java/kr/co/webee/domain/post/entity/PostLike.java | head -40

Repository: BeeKeeprs/was

Length of output: 1516


🏁 Script executed:

# 혹시 다른 곳에서 unique constraint를 정의했는지 확인
rg -i 'uniqueConstraints|@UniqueConstraint' --type java

Repository: BeeKeeprs/was

Length of output: 39


🏁 Script executed:

# H2 또는 다른 embedded DB 설정 확인
find . -name 'application*.yml' -o -name 'application*.yaml' -o -name 'application*.properties' | xargs -r grep -l 'spring.jpa\|spring.datasource' 2>/dev/null | head -5

Repository: BeeKeeprs/was

Length of output: 39


동시 토글 요청에서 중복 좋아요나 500 응답이 발생할 수 있습니다.

현재 PostLike 엔티티에 @UniqueConstraint가 없고, 데이터베이스 마이그레이션 파일도 확인되지 않아 (post_id, user_id) 조합에 대한 유니크 제약이 없습니다. findByPostIdAndUserIdsave/delete하는 check-then-write 구조에서 같은 사용자/게시글에 대한 동시 요청이 둘 다 "좋아요 없음"으로 판단할 수 있습니다.

결과:

  • 유니크 제약이 없으면: 동시 요청 시 중복 좋아요가 저장됩니다.
  • 유니크 제약이 있어도: DataIntegrityViolationException이 발생하면 406 상태코드로 사용자에게 노출됩니다.

다음 중 하나를 적용해 주세요:

  1. @Table(uniqueConstraints = @UniqueConstraint(columnNames = {"post_id", "user_id"})) 추가 + 예외 처리 개선
  2. 잠금 기반 접근: findByPostIdAndUserId(postId, userId) 쿼리에 @Lock(LockModeType.PESSIMISTIC_WRITE) 적용
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/kr/co/webee/application/post/service/PostLikeService.java`
around lines 33 - 55, Add a safe concurrency fix for toggling likes: either (A)
enforce DB uniqueness by adding `@Table`(uniqueConstraints =
`@UniqueConstraint`(columnNames = {"post_id","user_id"})) to the PostLike entity
and create the corresponding migration, then catch
DataIntegrityViolationException in PostLikeService around
postLikeRepository.save(...) to convert the race-error into a successful
"already liked" PostLikeToggleResponse (use
postLikeRepository.countByPostId(postId) for the count), or (B) make the
check-then-write atomic by adding `@Lock`(LockModeType.PESSIMISTIC_WRITE) to the
repository method used by
postLikeRepository.findByPostIdAndUserId(postId,userId) so concurrent toggles
serialize; pick one approach and update PostLikeService to handle the chosen
behavior consistently (refer to PostLike, PostLikeService,
postLikeRepository.findByPostIdAndUserId, postLikeRepository.save, and
PostLikeToggleResponse).

Comment on lines +21 to +33
@Entity
public class PostLike extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false)
private Post post;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: PostLike의 post_id/user_id 복합 유니크 제약이 엔티티나 DB migration에 정의되어 있는지 확인합니다.
rg -n -C3 'PostLike|post_like|post_id|user_id|UniqueConstraint|uniqueConstraints|uk_post_like|UNIQUE|unique'

Repository: BeeKeeprs/was

Length of output: 29487


🏁 Script executed:

cat -n src/main/java/kr/co/webee/domain/post/entity/PostLike.java

Repository: BeeKeeprs/was

Length of output: 1516


🏁 Script executed:

find . -type f \( -name "*.sql" -o -name "*.xml" -o -name "*V*.sql" -o -name "*V*.java" \) | grep -iE "(migration|flyway|liquibase)" | head -20

Repository: BeeKeeprs/was

Length of output: 39


🏁 Script executed:

rg -l "post_like|PostLike" --type-list | grep -iE "(migration|sql|xml)" | head -10

Repository: BeeKeeprs/was

Length of output: 446


🏁 Script executed:

find . -type d \( -name "migration" -o -name "migrations" -o -name "flyway" -o -name "liquibase" -o -name "db" \) 2>/dev/null

Repository: BeeKeeprs/was

Length of output: 39


🏁 Script executed:

fd -t f -e sql -e xml . | grep -iE "(migration|flyway|db)" | head -20

Repository: BeeKeeprs/was

Length of output: 39


🏁 Script executed:

rg "post_like|PostLike" --type sql --type xml 2>/dev/null | head -30

Repository: BeeKeeprs/was

Length of output: 39


🏁 Script executed:

find . -type f -name "*.sql" 2>/dev/null | head -20

Repository: BeeKeeprs/was

Length of output: 39


🏁 Script executed:

ls -la src/main/resources/ 2>/dev/null || echo "No resources directory"

Repository: BeeKeeprs/was

Length of output: 454


🏁 Script executed:

find . -path ./node_modules -prune -o -type d -name "resources" -print 2>/dev/null

Repository: BeeKeeprs/was

Length of output: 98


🏁 Script executed:

rg "CREATE TABLE.*post_like|CREATE TABLE.*PostLike" --type sql 2>/dev/null || rg "post_like" --type sql 2>/dev/null || echo "No SQL files found"

Repository: BeeKeeprs/was

Length of output: 75


🏁 Script executed:

cat src/main/resources/application.yml | grep -A 10 -B 2 "jpa\|hibernate\|ddl"

Repository: BeeKeeprs/was

Length of output: 358


🏁 Script executed:

rg "existsByPostIdAndUserId" -A 5 -B 2 src/main/java/kr/co/webee/domain/post/repository/PostLikeRepository.java

Repository: BeeKeeprs/was

Length of output: 158


🏁 Script executed:

cat -n src/main/java/kr/co/webee/application/post/service/PostLikeService.java | head -70

Repository: BeeKeeprs/was

Length of output: 3171


post_id/user_id 복합 유니크 제약을 추가해 주세요.

현재 엔티티는 @Table 어노테이션으로 제약을 정의하지 않고 있으며, 데이터베이스 마이그레이션 파일도 없습니다. ddl-auto: update 설정에서는 엔티티 정의 없이는 제약이 생성되지 않습니다.

더욱이 toggleLike() 메서드의 find-then-act 패턴(라인 33-55)에서, 동시에 여러 요청이 들어오면 같은 사용자가 같은 게시글을 여러 번 좋아요한 중복 row가 생길 수 있습니다. 이는 countByPostId()의 결과를 오염시키고, findByPostIdAndUserId()의 단건 조회 가정도 깨뜨립니다.

🔒 제안 수정
 import jakarta.persistence.Entity;
 import jakarta.persistence.FetchType;
 import jakarta.persistence.GeneratedValue;
 import jakarta.persistence.GenerationType;
 import jakarta.persistence.Id;
 import jakarta.persistence.JoinColumn;
 import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.UniqueConstraint;
 import kr.co.webee.domain.common.BaseTimeEntity;
 import kr.co.webee.domain.user.entity.User;

 `@Getter`
 `@NoArgsConstructor`(access = AccessLevel.PROTECTED)
 `@Entity`
+@Table(
+        uniqueConstraints = `@UniqueConstraint`(
+                name = "uk_post_like_post_id_user_id",
+                columnNames = {"post_id", "user_id"}
+        )
+)
 public class PostLike extends BaseTimeEntity {
📝 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
@Entity
public class PostLike extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false)
private Post post;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
`@Entity`
`@Table`(
uniqueConstraints = `@UniqueConstraint`(
name = "uk_post_like_post_id_user_id",
columnNames = {"post_id", "user_id"}
)
)
public class PostLike extends BaseTimeEntity {
`@Id`
`@GeneratedValue`(strategy = GenerationType.IDENTITY)
private Long id;
`@ManyToOne`(fetch = FetchType.LAZY)
`@JoinColumn`(name = "post_id", nullable = false)
private Post post;
`@ManyToOne`(fetch = FetchType.LAZY)
`@JoinColumn`(name = "user_id", nullable = false)
private User user;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/kr/co/webee/domain/post/entity/PostLike.java` around lines 21 -
33, Add a DB-level unique constraint on (post_id, user_id) and handle races in
toggleLike(): annotate the PostLike entity with `@Table`(uniqueConstraints =
`@UniqueConstraint`(columnNames = {"post_id","user_id"})) to ensure uniqueness at
the schema level and add a DB migration (ALTER TABLE ... ADD CONSTRAINT/UNIQUE
INDEX) so the constraint exists independent of ddl-auto; then update the
toggleLike() flow to avoid find-then-act races by attempting a single save and
catching DataIntegrityViolationException (or a specific constraint violation) to
treat concurrent insert as "already liked" and re-query using
repository.findByPostIdAndUserId(...) as needed, or perform a delete via
repository.deleteByPostAndUser(...) for unlike operations; ensure methods
countByPostId() and findByPostIdAndUserId() are still used only for
read-after-write verification, not for enforcing uniqueness.

@chaechaepower chaechaepower merged commit 3eee337 into main Apr 20, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

커뮤니티 좋아요 API

1 participant