Skip to content

feat: [alt-277] 사장님(MANAGER) 공고 API 지원자 수 · 상태 다중 필터 · 공고별 지원자 조회 - #99

Merged
ysw789 merged 19 commits into
devfrom
feat/alt-277
Aug 3, 2026
Merged

feat: [alt-277] 사장님(MANAGER) 공고 API 지원자 수 · 상태 다중 필터 · 공고별 지원자 조회#99
ysw789 merged 19 commits into
devfrom
feat/alt-277

Conversation

@ysw789

@ysw789 ysw789 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

변경사항

https://app.notion.com/p/3ae86553162881588e64dbcd38fabaac

  • 유효 지원 상태(SUBMITTED · SHORTLISTED · ACCEPTED)를 PostingApplicationStatus.ACTIVE_STATUSES 상수로 분리하고, 같은 목록이 하드코딩돼 있던 findAllActiveByUserId 도 이를 재사용하도록 정리
  • 매니저 공고 목록(GET /manager/postings)의 상태 필터를 단일값(eq)에서 다중 선택(in)으로 확장. DELETED 항상 제외를 헬퍼 안으로 접어 넣었고, count · page 쿼리 양쪽에 동일하게 적용. ?status=OPEN 단일 요청 하위 호환 유지
  • 매니저 지원자 목록(GET /manager/postings/applications)에 postingId 필터 추가. 별도 API 신설 대신 기존 필터를 확장했고(공고 모듈의 flat + filter 관례), count · page 쿼리 양쪽에 적용해 page.totalCount 정합성 유지
  • 매니저 공고 상세(GET /manager/postings/{postingId}) 응답에 applicantCount 추가. 유효 지원만 count(*) 로 집계하며, 지원자 목록을 postingId 로 조회한 row 수와 일치
  • 같은 공고에 대한 중복 지원 차단. 다른 근무일정에 지원한 경우도 중복으로 보며, CANCELLED · REJECTED · EXPIRED 이력만 있으면 재지원 가능
  • posting_applications (posting_id, status) 복합 인덱스 마이그레이션 추가 (V9). 로컬 스키마 확인 결과 해당 테이블에 PK 외 인덱스가 없어 신규 카운트 쿼리가 전체 스캔을 유발했고, 적용 후 Index Only Scan 으로 전환됨을 확인

중복 지원의 동시 요청 처리

  • 공고 행을 배타 잠금으로 잡아 같은 공고에 들어온 요청을 차례로 처리합니다. 잠금 대상이 근무일정이 아니라 공고여야 다른 근무일정으로 들어온 요청까지 같은 잠금에 걸립니다.
  • PostgreSQL 은 FOR UPDATE 에 대기 시간을 지정하는 문법이 없어 jakarta.persistence.lock.timeout 힌트가 무시됩니다. 대신 SET LOCAL lock_timeout = '5s' 로 트랜잭션 안에서만 상한을 걸고, 초과하면 429 를 돌려줍니다.
  • 활성 상태 (posting_id, user_id) 부분 유니크 인덱스를 추가했습니다 (V10). 잠금을 거치지 않는 다른 경로에 대비한 DB 차원의 마지막 방어선입니다.
  • 지원 저장 직후의 FCM 직접 호출을 없애고 FcmNotificationEvent 발행으로 바꿨습니다. 실제 발송은 기존 리스너가 AFTER_COMMIT 에서 처리하므로, 알림이 실패해도 지원 트랜잭션에 영향을 주지 않습니다.

테스트

  • CreatePostingApplicationTests (신규, 6케이스) — 공고 미존재 · 잠금 실패 · 모집 종료 · 중복 차단 · 재지원 허용 · 공고 단위 검사 검증
  • ManagerGetPostingDetailTests (신규, 3케이스) — 지원자 수 매핑 · 공고 미존재 처리 검증
  • PostingApplicationStatusTests (신규, 1케이스) — ACTIVE_STATUSES 구성 검증
  • ./gradlew clean build 전체 스위트 통과

참고

Summary by CodeRabbit

  • 새 기능

    • 관리자 공고 상세에서 취소·불합격·만료 지원자를 제외한 활성 지원자 수를 확인할 수 있습니다.
    • 공고 상태를 여러 개 선택해 필터링할 수 있습니다.
    • 특정 공고의 지원서만 조회할 수 있습니다.
  • 버그 수정

    • 동일 공고에 대한 활성 중복 지원을 차단합니다.
    • 지원 동시 처리 안정성이 향상되며, 요청이 과도할 경우 적절한 오류를 안내합니다.
  • 테스트

    • 지원 생성, 중복 지원 방지, 공고 상세 지원자 수 조회 시나리오를 검증했습니다.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ysw789, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 39 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6600c2a5-6b27-40f8-ba73-40cf08204fbb

📥 Commits

Reviewing files that changed from the base of the PR and between 2b612a7 and 01cf29b.

📒 Files selected for processing (1)
  • src/test/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetailTests.java
📝 Walkthrough

Walkthrough

공고 상태 필터를 다중 상태 조건으로 변경했습니다. 활성 지원자 조회, 공고 단위 중복 지원 차단, 비관적 잠금, 이벤트 기반 알림, 관리자 상세 지원자 수 응답을 추가했습니다.

Changes

공고 및 지원서 관리

Layer / File(s) Summary
공고 필터 및 활성 지원 조회 기반
src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/*FilterDto.java, src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/*QueryRepositoryImpl.java, src/main/java/com/dreamteam/alter/domain/posting/..., src/main/resources/db/migration/V9__*.sql
공고 상태 필터를 Set<PostingStatus> 기반으로 변경했습니다. 지원서 목록에 postingId 필터를 추가했습니다. 공고별 활성 지원자 수 조회와 관련 인덱스를 추가했습니다.
공고 단위 중복 지원 차단
src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java, src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java, src/main/resources/db/migration/V10__*.sql, src/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTests.java, src/main/java/com/dreamteam/alter/common/exception/handler/GlobalExceptionHandler.java
공고를 비관적 잠금으로 조회하고 활성 지원 중복을 공고 ID 기준으로 확인합니다. 중복 지원을 차단하고 지원 저장 후 FcmNotificationEvent를 발행합니다. 잠금 실패를 TOO_MANY_REQUESTS로 처리합니다.
관리자 상세 지원자 수 매핑
src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.java, src/main/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetail.java, src/test/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetailTests.java
관리자 공고 상세 응답에 활성 지원자 수를 추가했습니다. DTO 변환 메서드를 of로 변경하고 지원자 수 매핑을 검증했습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Applicant
  participant CreatePostingApplication
  participant PostingQueryRepository
  participant PostingApplicationQueryRepository
  participant PostingApplicationRepository
  participant ApplicationEventPublisher
  Applicant->>CreatePostingApplication: 지원 생성 요청
  CreatePostingApplication->>PostingQueryRepository: 공고 비관적 잠금 조회
  PostingQueryRepository-->>CreatePostingApplication: 공고 반환
  CreatePostingApplication->>PostingApplicationQueryRepository: 활성 지원 중복 조회
  PostingApplicationQueryRepository-->>CreatePostingApplication: 중복 여부 반환
  CreatePostingApplication->>PostingApplicationRepository: 지원서 저장
  CreatePostingApplication->>ApplicationEventPublisher: FcmNotificationEvent 발행
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.94% 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 제목은 매니저 공고 API의 지원자 수, 상태 다중 필터, 공고별 지원자 조회 변경을 정확히 요약합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/alt-277

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java`:
- Around line 58-62: 보이는 사전 조회만으로는 동시 요청의 중복 지원을 막을 수 없으므로,
CreatePostingApplication 흐름에 DB 수준 보장을 추가하세요. posting_applications의 posting_id와
user_id에 활성 상태만 대상으로 하는 부분 유니크 인덱스를 마이그레이션으로 생성하고,
postingApplicationRepository.save에서 발생하는 유니크 제약 위반을 기존의 중복 지원
CustomException/ErrorCode 처리로 변환하세요.

In
`@src/main/resources/db/migration/V7__add_posting_applications_posting_status_index.sql`:
- Around line 7-8: Update the migration creating
idx_posting_applications_posting_status to use PostgreSQL’s concurrent index
creation, and configure Flyway’s PostgreSQL transactional lock setting so this
migration is not executed inside a transaction. Ensure the Flyway configuration
explicitly disables the database lock or selects the supported non-transactional
migration path required by concurrent index creation.

In
`@src/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTest.java`:
- Around line 176-185: Move activeStatusesContainsOnlyLiveApplications from
CreatePostingApplicationTest into a dedicated PostingApplicationStatusTest (or
PostingApplicationStatusTests) class, keeping the ACTIVE_STATUSES assertion
unchanged. Import PostingApplicationStatus and reference its constants directly
instead of using fully qualified names.
- Around line 38-186: Update
src/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTest.java
(lines 38-186) by renaming the class and file to CreatePostingApplicationTests,
adding explicit // given, // when, and // then sections to each test, replacing
verify calls with then(...).should(...), and grouping related tests in `@Nested`
classes with `@DisplayName`. Apply the same conventions to
src/test/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetailTest.java
(lines 38-131), renaming it to ManagerGetPostingDetailTests, adding
Given-When-Then comments, and converting the never() verification in
ManagerGetPostingDetailTests to BDD Mockito syntax.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 82c64b40-8880-42ab-a418-505212ceaa6d

📥 Commits

Reviewing files that changed from the base of the PR and between 17f7452 and b2857a7.

📒 Files selected for processing (12)
  • src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.java
  • src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingListFilterDto.java
  • src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/PostingApplicationListFilterDto.java
  • src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingApplicationQueryRepositoryImpl.java
  • src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java
  • src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java
  • src/main/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetail.java
  • src/main/java/com/dreamteam/alter/domain/posting/port/outbound/PostingApplicationQueryRepository.java
  • src/main/java/com/dreamteam/alter/domain/posting/type/PostingApplicationStatus.java
  • src/main/resources/db/migration/V7__add_posting_applications_posting_status_index.sql
  • src/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTest.java
  • src/test/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetailTest.java

Comment on lines +176 to +185
@Test
@DisplayName("ACTIVE_STATUSES 는 지원 완료·서류 합격·최종 합격만 포함한다")
void activeStatusesContainsOnlyLiveApplications() {
assertThat(com.dreamteam.alter.domain.posting.type.PostingApplicationStatus.ACTIVE_STATUSES)
.containsExactlyInAnyOrder(
com.dreamteam.alter.domain.posting.type.PostingApplicationStatus.SUBMITTED,
com.dreamteam.alter.domain.posting.type.PostingApplicationStatus.SHORTLISTED,
com.dreamteam.alter.domain.posting.type.PostingApplicationStatus.ACCEPTED
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

ACTIVE_STATUSES 테스트는 별도 테스트 클래스로 옮기세요.

activeStatusesContainsOnlyLiveApplicationsPostingApplicationStatus 열거형을 검증하며 CreatePostingApplication의 동작과 관련이 없습니다. 이 테스트를 PostingApplicationStatusTest(또는 Tests) 클래스로 옮기세요.

또한 PostingApplicationStatus를 정규화된 전체 경로명 대신 import하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTest.java`
around lines 176 - 185, Move activeStatusesContainsOnlyLiveApplications from
CreatePostingApplicationTest into a dedicated PostingApplicationStatusTest (or
PostingApplicationStatusTests) class, keeping the ACTIVE_STATUSES assertion
unchanged. Import PostingApplicationStatus and reference its constants directly
instead of using fully qualified names.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java`:
- Around line 333-345: Update findByIdWithPessimisticLock to apply the
jakarta.persistence.lock.timeout hint alongside PESSIMISTIC_WRITE, and handle
LockTimeoutException so CreatePostingApplication.execute() returns a clear
client-facing response instead of waiting indefinitely. Also add a fetch join
for qPosting.workspace using QWorkspace.workspace so the workspace relationship
needed by the creation flow is loaded in the same query.

In
`@src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java`:
- Around line 43-48: Update the exception mapping in CreatePostingApplication so
the posting lookup via findByIdWithPessimisticLock throws
ErrorCode.POSTING_NOT_FOUND when no posting exists; keep the subsequent
findByIdAndPostingId lookup using POSTING_SCHEDULE_NOT_FOUND.

In
`@src/main/java/com/dreamteam/alter/domain/posting/port/outbound/PostingQueryRepository.java`:
- Line 22: Rename the PostingQueryRepository method findByIdWithPessimisticLock
to the implementation-agnostic findByIdForUpdate, and add Javadoc documenting
that it performs an exclusive read to prevent concurrent modifications. Update
all implementations and callers to use the renamed contract.

In
`@src/main/resources/db/migration/V8__prevent_active_duplicate_posting_applications.sql`:
- Around line 4-6: Update the migration’s unique-index statement to use
PostgreSQL’s CONCURRENTLY option, and add the Flyway transactional=false header
so it runs outside a transaction. Also verify whether the production
spring.flyway.postgresql transactional-lock setting requires adjustment for this
non-transactional migration.

In
`@src/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTest.java`:
- Around line 51-53: The CreatePostingApplicationTest coverage is missing for
the empty Optional branch of findByIdWithPessimisticLock. Add a test near the
existing posting creation tests that stubs
postingQueryRepository.findByIdWithPessimisticLock to return Optional.empty(),
asserts execute throws CustomException, and verifies
postingApplicationRepository.save is never called.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e716c696-4fd2-444e-b085-f9c7edf7e6c0

📥 Commits

Reviewing files that changed from the base of the PR and between b2857a7 and bc1ae1a.

📒 Files selected for processing (5)
  • src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java
  • src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java
  • src/main/java/com/dreamteam/alter/domain/posting/port/outbound/PostingQueryRepository.java
  • src/main/resources/db/migration/V8__prevent_active_duplicate_posting_applications.sql
  • src/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTest.java

Comment on lines +4 to +6
CREATE UNIQUE INDEX IF NOT EXISTS uq_posting_applications_active_posting_user
ON posting_applications (posting_id, user_id)
WHERE status IN ('SUBMITTED', 'SHORTLISTED', 'ACCEPTED');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Flyway automatically run CREATE INDEX CONCURRENTLY outside a transaction, and is spring.flyway.postgresql.transactional-lock required to avoid hangs?

💡 Result:

Flyway does not automatically run CREATE INDEX CONCURRENTLY outside a transaction in a way that avoids hangs when using the default transactional advisory lock [1][2][3]. Because CREATE INDEX CONCURRENTLY cannot run inside a transaction block in PostgreSQL, it must be executed non-transactionally [2][4]. However, Flyway's default behavior is to use a transactional advisory lock to coordinate migrations [5][3]. This lock is held within a transaction, and because the migration script itself is often wrapped in a transaction or Flyway otherwise maintains an open transaction for the lock, the PostgreSQL engine encounters a conflict where the non-transactional statement waits for the lock-holding transaction to complete, leading to a deadlock or indefinite hang [1][2]. To avoid this, you must explicitly disable the transactional lock by setting flyway.postgresql.transactional-lock to false [5][4]. In a Spring Boot application, you can configure this using the property spring.flyway.postgresql.transactional-lock=false in your application configuration (e.g., application.properties or application.yml) [6][7]. While recent versions of Flyway have introduced improvements to attempt better handling of non-transactional migrations, setting flyway.postgresql.transactional-lock=false remains the standard and recommended solution for resolving hangs associated with CREATE INDEX CONCURRENTLY [5][1][4]. Top results: [5][1][2][6][3]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== migration file =="
cat -n src/main/resources/db/migration/V8__prevent_active_duplicate_posting_applications.sql 2>/dev/null || true

echo
echo "== Flyway PostgreSQL transaction configuration search =="
rg -n "spring\.flyway|flyway|postgresql|transactional-lock|non-transactional|postgresUrlProperties" -S . \
  -g '!build/**' -g '!target/**' -g '!node_modules/**' | head -200

echo
echo "== application configuration files =="
fd -a '^(application|bootstrap)\.(properties|yml|yaml)$' . | sed 's#^\./##' | sort
for f in $(fd '^(application|bootstrap)\.(properties|yml|yaml)$' . | sort); do
  echo "--- $f"
  sed -n '1,220p' "$f"
done

Repository: alter-app/alter-backend

Length of output: 9070


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== build.gradle relevant sections =="
sed -n '1,120p' build.gradle
echo
echo "== application config PostgreSQL/Flyway keys =="
python3 - <<'PY'
from pathlib import Path
p=Path('src/main/resources/application.yml')
text=p.read_text()
for i,line in enumerate(text.splitlines(),1):
    if any(s in line for s in ['flyway:', 'postgresql', 'database:', 'driver-class-name:', 'jdbc:', 'flyway:']):
        print(i, line)
PY

Repository: alter-app/alter-backend

Length of output: 3622


🌐 Web query:

Spring Boot migration flyway default transaction postgreSQL transactions enabled non transactional DDL indexes

💡 Result:

In Spring Boot with Flyway and PostgreSQL, running non-transactional DDL statements like CREATE INDEX CONCURRENTLY presents a challenge because Flyway attempts to run migrations within a transaction by default [1][2][3]. Since PostgreSQL does not allow these statements inside a transaction, the migration must be marked as non-transactional [1][3][4]. The primary mechanism to handle this is to disable transactions for specific migrations using a SQL comment at the top of your migration file [1]: -- flyway.transactional=false CREATE INDEX CONCURRENTLY IF NOT EXISTS index_name ON table_name (column_name); Configuration and Deadlocks Even when a migration is marked non-transactional, Flyway typically acquires an advisory lock to prevent concurrent migration runs. In older versions or specific configurations, this lock could be held within a transaction that conflicts with the non-transactional statement, causing the process to hang [3][5][6]. To resolve these hanging issues, you must ensure Flyway uses a session-level lock instead of a transactional lock [7][8]. This is configured globally via your application.yml or application.properties [2][8]: spring: flyway: postgresql: transactional-lock: false Best Practices 1. Global Configuration: Setting transactional-lock to false is generally recommended when your migrations frequently require non-transactional DDL [7][8]. 2. Targeted Execution: While Flyway's standard approach is to wrap migrations in transactions, always verify that your non-transactional migrations are properly configured with the -- flyway.transactional=false header [1]. 3. Versioning: Ensure you are using a modern version of Flyway (9.19.4 or higher is recommended) to benefit from fixes regarding deadlock handling and lock management [9][5]. If you find that setting this globally is undesirable, note that while current architecture often requires the global setting to prevent deadlocks during non-transactional operations, modern Flyway developments have been exploring finer-grained locking strategies to avoid this requirement [6].

Citations:


PostgreSQL에서 CONCURRENTLY를 실행할 수 있도록 Flyway 마이그레이션 트랜잭션을 비활성화하십시오.

CREATE UNIQUE INDEX는 인덱스 생성이 끝날 때까지 posting_applications의 쓰기 작업을 차단할 수 있습니다. CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS ...로 바꾸면 PostgreSQL의 CONCURRENTLY 한계 때문에 트랜잭션 내부에서 실행될 수 없습니다. Flyway는 기본값으로 마이그레이션을 트랜잭션에서 실행하므로, 이 마이그레이션에 -- flyway.transactional=false 헤더를 도입하고, spring.flyway.postgresql: transactional-lock 설정도 실제 운영 환경에서 필요 여부를 확인하십시오.

🧰 Tools
🪛 Squawk (2.61.0)

[warning] 4-6: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.

(require-concurrent-index-creation)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/resources/db/migration/V8__prevent_active_duplicate_posting_applications.sql`
around lines 4 - 6, Update the migration’s unique-index statement to use
PostgreSQL’s CONCURRENTLY option, and add the Flyway transactional=false header
so it runs outside a transaction. Also verify whether the production
spring.flyway.postgresql transactional-lock setting requires adjustment for this
non-transactional migration.

Source: Linters/SAST tools

@ysw789
ysw789 requested a review from hodoon August 2, 2026 14:39
@ysw789 ysw789 self-assigned this Aug 2, 2026

@hodoon hodoon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code review

라인별 코멘트로 남겼습니다. 우선순위는 아래와 같습니다.

머지 전 필수

  1. Flyway V7 버전 충돌 — 현재 상태로 dev에 머지하면 애플리케이션이 기동되지 않습니다.

의도한 동작이 실제로는 걸리지 않는 부분
2. jakarta.persistence.lock.timeout 힌트가 PostgreSQL에서 무시됩니다.
3. @Repository 예외 변환 때문에 catch (LockTimeoutException) 이 잡히지 않습니다.
4. PESSIMISTIC_WRITE + leftJoin().fetchJoin() 조합이 PostgreSQL 제약에 걸립니다.
5. V8 부분 유니크 인덱스가 기존 중복 데이터 때문에 조용히 무력화될 수 있습니다.

좋았던 점: ACTIVE_STATUSES 상수화로 하드코딩 제거, count·page 쿼리 양쪽에 동일 필터를 적용해 page.totalCount 정합성을 지킨 점, NotificationService 직접 호출을 @TransactionalEventListener(AFTER_COMMIT) 로 전환해 알림 실패가 지원 트랜잭션을 오염시키지 않게 한 점 모두 적절합니다.

Comment thread src/main/resources/application.yml Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java`:
- Around line 44-51: CustomException에 ErrorCode와 Throwable 원인을 함께 받는 생성자를 추가하고,
CreatePostingApplication의 PessimisticLockingFailureException 처리에서 e를 원인 예외로 전달해
TOO_MANY_REQUESTS를 생성하도록 수정하세요.

In
`@src/test/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetailTests.java`:
- Around line 116-125: 0건 지원자 응답 테스트가 countActiveApplicationsByPostingId 호출 누락을
감지하지 못합니다. ManagerGetPostingDetailTests의 해당 테스트에서 결과 검증과 함께
postingApplicationQueryRepository.countActiveApplicationsByPostingId(POSTING_ID)가
호출되었는지 BDD-style Mockito 검증으로 확인하세요.
- Around line 89-141: Rename the three test methods in the
ManagerGetPostingDetailTests class to follow the action_condition_expectedResult
convention: execute_applicantsExist_returnsApplicantCount,
execute_noApplicants_returnsZero, and execute_postingNotFound_throwsException.
Keep each test’s existing behavior and assertions unchanged.

In
`@src/test/java/com/dreamteam/alter/domain/posting/type/PostingApplicationStatusTests.java`:
- Around line 14-20: PostingApplicationStatus.ACTIVE_STATUSES 테스트를 명시적인
Given-When-Then 구조로 분리하세요. `// given`에서 기대 상태를 정의하고, `// when`에서 실제
ACTIVE_STATUSES를 조회한 뒤, `// then`에서 해당 결과를 AssertJ로 검증하도록 수정하세요.
- Around line 11-13: Rename the test method
activeStatusesContainsOnlyLiveApplications to follow the project’s
action_condition_expectedResult convention, using
activeStatuses_whenAccessed_containsOnlyLiveApplications or an equivalent name
that clearly expresses the condition and expected result.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 98325906-91c4-4ee5-afc3-335e31a0a467

📥 Commits

Reviewing files that changed from the base of the PR and between bc1ae1a and 2b612a7.

📒 Files selected for processing (10)
  • src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.java
  • src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.java
  • src/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.java
  • src/main/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetail.java
  • src/main/java/com/dreamteam/alter/common/exception/handler/GlobalExceptionHandler.java
  • src/main/resources/db/migration/V10__prevent_active_duplicate_posting_applications.sql
  • src/main/resources/db/migration/V9__add_posting_applications_posting_status_index.sql
  • src/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTests.java
  • src/test/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetailTests.java
  • src/test/java/com/dreamteam/alter/domain/posting/type/PostingApplicationStatusTests.java

@ysw789
ysw789 merged commit f6e392c into dev Aug 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants