feat: [alt-277] 사장님(MANAGER) 공고 API 지원자 수 · 상태 다중 필터 · 공고별 지원자 조회 - #99
Conversation
|
Warning Review limit reached
Next review available in: 39 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough공고 상태 필터를 다중 상태 조건으로 변경했습니다. 활성 지원자 조회, 공고 단위 중복 지원 차단, 비관적 잠금, 이벤트 기반 알림, 관리자 상세 지원자 수 응답을 추가했습니다. Changes공고 및 지원서 관리
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 발행
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (12)
src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.javasrc/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingListFilterDto.javasrc/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/PostingApplicationListFilterDto.javasrc/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingApplicationQueryRepositoryImpl.javasrc/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.javasrc/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.javasrc/main/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetail.javasrc/main/java/com/dreamteam/alter/domain/posting/port/outbound/PostingApplicationQueryRepository.javasrc/main/java/com/dreamteam/alter/domain/posting/type/PostingApplicationStatus.javasrc/main/resources/db/migration/V7__add_posting_applications_posting_status_index.sqlsrc/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTest.javasrc/test/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetailTest.java
| @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 | ||
| ); | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
ACTIVE_STATUSES 테스트는 별도 테스트 클래스로 옮기세요.
activeStatusesContainsOnlyLiveApplications는 PostingApplicationStatus 열거형을 검증하며 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.
There was a problem hiding this comment.
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
📒 Files selected for processing (5)
src/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.javasrc/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.javasrc/main/java/com/dreamteam/alter/domain/posting/port/outbound/PostingQueryRepository.javasrc/main/resources/db/migration/V8__prevent_active_duplicate_posting_applications.sqlsrc/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTest.java
| 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'); |
There was a problem hiding this comment.
🚀 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:
- 1: Automatically use session-level PostgreSQL advisory lock for non-transactional migrations flyway/flyway#4267
- 2: Transactional locks hanging for non-transactional migrations flyway/flyway#3497
- 3: https://documentation.red-gate.com/flyway/reference/database-driver-reference/postgresql-database
- 4: CREATE CONCURRENT INDEX waits indefinitely on PostgessDB flyway/flyway#3854
- 5: https://documentation.red-gate.com/fd/flyway-postgresql-transactional-lock-setting-277579114.html
- 6: Provide a configuration property for configuring Flyway's use of transactional locks with PostgreSQL spring-projects/spring-boot#32629
- 7: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.2-Configuration-Changelog
🏁 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"
doneRepository: 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)
PYRepository: 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:
- 1: https://runebook.dev/en/docs/spring_boot/application-properties/application-properties.data-migration.spring.flyway.execute-in-transaction
- 2: https://oneuptime.com/blog/post/2026-01-25-database-migrations-flyway-spring-boot/view
- 3: https://goldlapel.com/grounds/spring-java/flyway-create-index-concurrently-hang
- 4: https://documentation.red-gate.com/flyway/flyway-concepts/migrations/migration-transaction-handling
- 5: CREATE CONCURRENT INDEX waits indefinitely on PostgessDB flyway/flyway#3854
- 6: Automatically use session-level PostgreSQL advisory lock for non-transactional migrations flyway/flyway#4267
- 7: https://documentation.red-gate.com/flyway/reference/database-driver-reference/postgresql-database
- 8: https://documentation.red-gate.com/flyway/reference/configuration/flyway-namespace/flyway-postgresql-namespace/flyway-postgresql-transactional-lock-setting
- 9: Transactional Lock False not working anymore for PostgreSQL flyway/flyway#3684
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
hodoon
left a comment
There was a problem hiding this comment.
Code review
라인별 코멘트로 남겼습니다. 우선순위는 아래와 같습니다.
머지 전 필수
- 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) 로 전환해 알림 실패가 지원 트랜잭션을 오염시키지 않게 한 점 모두 적절합니다.
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
src/main/java/com/dreamteam/alter/adapter/inbound/manager/posting/dto/ManagerPostingDetailResponseDto.javasrc/main/java/com/dreamteam/alter/adapter/outbound/posting/persistence/PostingQueryRepositoryImpl.javasrc/main/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplication.javasrc/main/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetail.javasrc/main/java/com/dreamteam/alter/common/exception/handler/GlobalExceptionHandler.javasrc/main/resources/db/migration/V10__prevent_active_duplicate_posting_applications.sqlsrc/main/resources/db/migration/V9__add_posting_applications_posting_status_index.sqlsrc/test/java/com/dreamteam/alter/application/posting/usecase/CreatePostingApplicationTests.javasrc/test/java/com/dreamteam/alter/application/posting/usecase/ManagerGetPostingDetailTests.javasrc/test/java/com/dreamteam/alter/domain/posting/type/PostingApplicationStatusTests.java
변경사항
https://app.notion.com/p/3ae86553162881588e64dbcd38fabaac
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 으로 전환됨을 확인중복 지원의 동시 요청 처리
FOR UPDATE에 대기 시간을 지정하는 문법이 없어jakarta.persistence.lock.timeout힌트가 무시됩니다. 대신SET LOCAL lock_timeout = '5s'로 트랜잭션 안에서만 상한을 걸고, 초과하면 429 를 돌려줍니다.(posting_id, user_id)부분 유니크 인덱스를 추가했습니다 (V10). 잠금을 거치지 않는 다른 경로에 대비한 DB 차원의 마지막 방어선입니다.FcmNotificationEvent발행으로 바꿨습니다. 실제 발송은 기존 리스너가AFTER_COMMIT에서 처리하므로, 알림이 실패해도 지원 트랜잭션에 영향을 주지 않습니다.테스트
CreatePostingApplicationTests(신규, 6케이스) — 공고 미존재 · 잠금 실패 · 모집 종료 · 중복 차단 · 재지원 허용 · 공고 단위 검사 검증ManagerGetPostingDetailTests(신규, 3케이스) — 지원자 수 매핑 · 공고 미존재 처리 검증PostingApplicationStatusTests(신규, 1케이스) —ACTIVE_STATUSES구성 검증./gradlew clean build전체 스위트 통과참고
fix/alt-276에 이미 구현되어 있어 이 PR 범위에서 제외했습니다. 공유 파일인PostingQueryRepositoryImpl은 수정 헝크가 분리되어 있습니다.Summary by CodeRabbit
새 기능
버그 수정
테스트