Skip to content

refactor: Recruitment 생성 시 기간 중복 검증 추가#1355

Merged
kimsh1017 merged 7 commits intodevelopfrom
refactor/1352-recruitment-period-validate
Mar 8, 2026
Merged

refactor: Recruitment 생성 시 기간 중복 검증 추가#1355
kimsh1017 merged 7 commits intodevelopfrom
refactor/1352-recruitment-period-validate

Conversation

@kimsh1017
Copy link
Member

@kimsh1017 kimsh1017 commented Mar 5, 2026

🌱 관련 이슈

📌 작업 내용 및 특이사항

[문제 상황]

  • 프론엔드에서 개발 서버에 리쿠르팅 생성 중 지속적으로 기간을 잘못 입력하는 경우 발생
  • 동일한 기간으로 학기 2개 이상 생성 시 현재 리쿠르팅 조회 로직에서 NonUniqueResultException 예외 발생
public interface RecruitmentCustomRepository {
    Optional<Recruitment> findCurrentRecruitment(LocalDateTime now);
}

[해결 방안]

  • 리쿠르팅 생성 시 기간 검증 로직 추가
  • DB 쿼리에서 분기문이 들어가므로 통합 테스트 케이스 더 상세히 보강

📝 참고사항

📚 기타

Summary by CodeRabbit

  • 개선 사항

    • 리크루팅 생성 검증 강화: 학기 중복과 기간(시작/종료) 중복을 각각 별도로 검사하도록 확장
    • 오류 메시지 개선: 학기 중복과 기간 중복에 대해 보다 구체적이고 명확한 안내 제공
  • 테스트

    • 학기 중복 및 기간 중복 각각에 대한 검증 시나리오 추가 및 기존 테스트 갱신

@kimsh1017 kimsh1017 requested a review from a team as a code owner March 5, 2026 07:25
@github-actions github-actions bot changed the title Refactor/1352 recruitment period validate refactor: Recruitment 생성 시 기간 중복 검증 추가 Mar 5, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4720d84c-7ae4-4c48-886d-8de46bf3b05b

📥 Commits

Reviewing files that changed from the base of the PR and between 87f9b24 and 5784dff.

📒 Files selected for processing (1)
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java

📝 Walkthrough

Walkthrough

리크루팅 생성 검증을 분리해 학기 중복(isRecruitmentSemesterOverlap)과 기간 중복(isRecruitmentPeriodOverlap)을 독립적으로 검사하도록 변경했습니다. Repository에 기간 중복 조회 메서드(existsPeriodOverlapping)를 추가하고, Service에서 두 중복 플래그를 생성해 Validator로 전달하며 에러 코드를 세분화했습니다.

Changes

Cohort / File(s) Summary
리포지토리 - 커스텀 조회 추가
src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepository.java, src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java, src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentRepository.java
기간 중복 검사용 existsPeriodOverlapping(LocalDateTime...) 인터페이스 및 구현 추가. 중복 판별용 헬퍼(isOverlappingPeriod)와 쿼리 로직 추가.
서비스 계층
src/main/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentService.java
단일 중복 플래그를 학기 중복(existsBySemester)과 기간 중복(existsPeriodOverlapping) 두 플래그로 분리해 Validator에 전달하도록 시그니처 호출 및 변수명 변경.
검증 로직
src/main/java/com/gdschongik/gdsc/domain/recruitment/domain/service/RecruitmentValidator.java
검증 메서드 시그니처를 두 불린 파라미터로 변경하고, 학기 중복/기간 중복 각각에 대해 다른 예외 코드(RECRUITMENT_SEMESTER_OVERLAP, RECRUITMENT_PERIOD_OVERLAP)를 던지도록 분기 분리.
에러 코드
src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java
기존 단일 RECRUITMENT_OVERLAP를 제거하고 RECRUITMENT_SEMESTER_OVERLAP, RECRUITMENT_PERIOD_OVERLAP 두 상수로 분리 및 메시지 추가/조정.
테스트
src/test/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentServiceTest.java, src/test/java/com/gdschongik/gdsc/domain/recruitment/domain/RecruitmentValidatorTest.java
학기 중복과 기간 중복 각각의 시나리오를 검증하는 테스트 추가 및 기존 테스트를 새 시그니처/에러 코드에 맞게 수정. 긍정 케이스의 기대 결과(저장 수) 업데이트.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Service as AdminRecruitmentService
    participant Repo as RecruitmentRepository
    participant Validator as RecruitmentValidator
    participant DB as Database

    Client->>Service: createRecruitment(request)
    Service->>Repo: existsBySemester(academicYear, semester)
    Repo->>DB: SELECT ... WHERE academicYear, semester
    DB-->>Repo: result (true/false)
    Service->>Repo: existsPeriodOverlapping(startDateTime, endDateTime)
    Repo->>DB: SELECT ... WHERE period overlaps
    DB-->>Repo: result (true/false)
    Service->>Validator: validateRecruitmentCreate(isRecruitmentSemesterOverlap, isRecruitmentPeriodOverlap)
    alt semester overlap
        Validator-->>Service: throw RECRUITMENT_SEMESTER_OVERLAP
    else period overlap
        Validator-->>Service: throw RECRUITMENT_PERIOD_OVERLAP
    else no overlap
        Service->>Repo: save(new Recruitment)
        Repo->>DB: INSERT ...
        DB-->>Repo: saved
        Repo-->>Service: persisted entity
        Service-->>Client: created response
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

♻️ refactor

Poem

🐰 학기랑 기간을 갈라놓고 뛰었네
살금살금 겹침을 캐내는 발자국 소리
검증이 한 번 더 눈을 찡긋하고
테스트는 도란도란 웃으며 달려와
당근 한 입 축하해요, 깔끔해진 코드에 🥕

🚥 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 제목은 주요 변경사항인 Recruitment 생성 시 기간 중복 검증 추가를 명확하게 설명하고 있습니다.
Linked Issues check ✅ Passed 코드 변경사항이 #1352의 모든 주요 요구사항을 충족합니다: 기간 중복 검증 로직 추가, 저장소 쿼리 개선, 통합 테스트 확장.
Out of Scope Changes check ✅ Passed 모든 코드 변경사항이 #1352의 기간 중복 검증 추가 요구사항과 직접 관련되어 있으며 범위 내입니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/1352-recruitment-period-validate

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

Job Summary for Gradle

Check Style and Test to Develop :: build-test
Gradle Root Project Requested Tasks Gradle Version Build Outcome Build Scan®
gdsc check 8.5 Build Scan published

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

🧹 Nitpick comments (2)
src/test/java/com/gdschongik/gdsc/domain/recruitment/domain/RecruitmentValidatorTest.java (1)

15-38: 두 플래그가 동시에 true일 때 우선순위 테스트도 있으면 좋겠습니다.

현재 단일 조건별 실패는 잘 검증됩니다. isRecruitmentSemesterOverlap=true, isRecruitmentPeriodOverlap=true 동시 입력 시 어떤 에러를 우선 반환하는지 테스트를 고정해두면 회귀 방지에 유리합니다.

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

In
`@src/test/java/com/gdschongik/gdsc/domain/recruitment/domain/RecruitmentValidatorTest.java`
around lines 15 - 38, Add a new test in RecruitmentValidatorTest that calls
recruitmentValidator.validateRecruitmentCreate with both
isRecruitmentSemesterOverlap=true and isRecruitmentPeriodOverlap=true and assert
which CustomException message is returned (decide and assert either
RECRUITMENT_SEMESTER_OVERLAP.getMessage() or
RECRUITMENT_PERIOD_OVERLAP.getMessage()); this pins the priority behavior when
both flags are true and prevents regressions of validateRecruitmentCreate's
error precedence.
src/test/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentServiceTest.java (1)

80-99: 기간 중복 경계값 테스트를 추가해 주세요.

현재는 “완전 중복”만 검증합니다. 기존 종료일 == 신규 시작일 경계 케이스도 포함해야 중복 판정 회귀를 안정적으로 막을 수 있습니다.

✅ 테스트 케이스 추가 예시
+        `@Test`
+        void 기존_종료일과_신규_시작일이_같다면_실패한다() {
+            // given
+            Recruitment previousRecruitment =
+                    Recruitment.create(FEE_NAME, FEE, Period.of(SEMESTER_START_DATE, SEMESTER_END_DATE), SEMESTER);
+            recruitmentRepository.save(previousRecruitment);
+
+            RecruitmentCreateRequest request = new RecruitmentCreateRequest(
+                    SEMESTER_END_DATE, // 기존 종료일과 동일한 시작일
+                    SEMESTER_END_DATE.plusDays(10),
+                    ACADEMIC_YEAR + 1,
+                    SEMESTER_TYPE,
+                    FEE_AMOUNT,
+                    FEE_NAME);
+
+            // when & then
+            assertThatThrownBy(() -> adminRecruitmentService.createRecruitment(request))
+                    .isInstanceOf(CustomException.class)
+                    .hasMessage(RECRUITMENT_PERIOD_OVERLAP.getMessage());
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/test/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentServiceTest.java`
around lines 80 - 99, Add a boundary test to AdminRecruitmentServiceTest that
verifies the overlap rule treats previous end == new start as overlapping:
create and save a previous Recruitment via Recruitment.create(...,
Period.of(SEMESTER_START_DATE, SEMESTER_END_DATE), ...), then build a
RecruitmentCreateRequest whose start date equals that previous recruitment's end
date (SEMESTER_END_DATE) and call
adminRecruitmentService.createRecruitment(request); assert it throws
CustomException with RECRUITMENT_PERIOD_OVERLAP.getMessage(). Place this in a
new test method (e.g., 학기_기간_경계가_겹치면_실패한다) to explicitly cover the boundary
case.
🤖 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/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentService.java`:
- Around line 41-45: The existence checks
(recruitmentRepository.existsBySemester and
recruitmentRepository.existsByPeriodOverlapping) and subsequent save must be
made atomic to avoid race conditions; wrap the create flow in a transaction with
an elevated isolation level or use a pessimistic lock on the recruitment table
(e.g., `@Transactional`(isolation = Isolation.SERIALIZABLE) on the service method
that calls recruitmentValidator.validateRecruitmentCreate and
recruitmentRepository.save) and also add a DB-level unique constraint for
academicYear+semesterType and handle constraint violations by catching
DataIntegrityViolationException (or the specific persistence exception) to
translate into the same validation error path used by
recruitmentValidator.validateRecruitmentCreate.

In
`@src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java`:
- Around line 40-41: The overlap check in isOverlappingPeriod currently uses
recruitment.semesterPeriod.endDate.gt(startDate), which treats an existing
endDate equal to the new startDate as non-overlapping; change that comparison to
recruitment.semesterPeriod.endDate.goe(startDate) so the end boundary is
inclusive and such edge cases are considered overlapping; update the
BooleanExpression in isOverlappingPeriod accordingly to use goe for the endDate
comparison.

---

Nitpick comments:
In
`@src/test/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentServiceTest.java`:
- Around line 80-99: Add a boundary test to AdminRecruitmentServiceTest that
verifies the overlap rule treats previous end == new start as overlapping:
create and save a previous Recruitment via Recruitment.create(...,
Period.of(SEMESTER_START_DATE, SEMESTER_END_DATE), ...), then build a
RecruitmentCreateRequest whose start date equals that previous recruitment's end
date (SEMESTER_END_DATE) and call
adminRecruitmentService.createRecruitment(request); assert it throws
CustomException with RECRUITMENT_PERIOD_OVERLAP.getMessage(). Place this in a
new test method (e.g., 학기_기간_경계가_겹치면_실패한다) to explicitly cover the boundary
case.

In
`@src/test/java/com/gdschongik/gdsc/domain/recruitment/domain/RecruitmentValidatorTest.java`:
- Around line 15-38: Add a new test in RecruitmentValidatorTest that calls
recruitmentValidator.validateRecruitmentCreate with both
isRecruitmentSemesterOverlap=true and isRecruitmentPeriodOverlap=true and assert
which CustomException message is returned (decide and assert either
RECRUITMENT_SEMESTER_OVERLAP.getMessage() or
RECRUITMENT_PERIOD_OVERLAP.getMessage()); this pins the priority behavior when
both flags are true and prevents regressions of validateRecruitmentCreate's
error precedence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 627a2834-cf5f-444b-a159-61a62b6235fb

📥 Commits

Reviewing files that changed from the base of the PR and between 84127e2 and 1c0be7d.

📒 Files selected for processing (7)
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentService.java
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepository.java
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/domain/service/RecruitmentValidator.java
  • src/main/java/com/gdschongik/gdsc/global/exception/ErrorCode.java
  • src/test/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentServiceTest.java
  • src/test/java/com/gdschongik/gdsc/domain/recruitment/domain/RecruitmentValidatorTest.java

Comment on lines +41 to +45
boolean isRecruitmentSemesterOverlap = recruitmentRepository.existsBySemester(semester);
boolean isRecruitmentPeriodOverlap =
recruitmentRepository.existsByPeriodOverlapping(request.semesterStartDate(), request.semesterEndDate());

recruitmentValidator.validateRecruitmentCreate(isRecruitmentOverlap);
recruitmentValidator.validateRecruitmentCreate(isRecruitmentSemesterOverlap, isRecruitmentPeriodOverlap);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

중복 체크와 저장이 원자적이지 않아 동시성에서 중복 생성이 가능합니다.

현재는 existsBy... 검사 후 save까지 경쟁 구간이 열려 있습니다. 동시 요청 2건이 모두 검증을 통과하면 중복 리크루팅이 저장될 수 있습니다.

🔒 최소 완화안 (트랜잭션 격리 상향)
+import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Transactional;
@@
-    `@Transactional`
+    `@Transactional`(isolation = Isolation.SERIALIZABLE)
     public void createRecruitment(RecruitmentCreateRequest request) {

추가로, 학기 중복은 DB 유니크 제약(academicYear + semesterType)으로 반드시 이중 방어하는 것을 권장합니다.

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

In
`@src/main/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentService.java`
around lines 41 - 45, The existence checks
(recruitmentRepository.existsBySemester and
recruitmentRepository.existsByPeriodOverlapping) and subsequent save must be
made atomic to avoid race conditions; wrap the create flow in a transaction with
an elevated isolation level or use a pessimistic lock on the recruitment table
(e.g., `@Transactional`(isolation = Isolation.SERIALIZABLE) on the service method
that calls recruitmentValidator.validateRecruitmentCreate and
recruitmentRepository.save) and also add a DB-level unique constraint for
academicYear+semesterType and handle constraint violations by catching
DataIntegrityViolationException (or the specific persistence exception) to
translate into the same validation error path used by
recruitmentValidator.validateRecruitmentCreate.

Copy link
Member

@Sangwook02 Sangwook02 left a comment

Choose a reason for hiding this comment

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

확인했어요

public interface RecruitmentCustomRepository {
Optional<Recruitment> findCurrentRecruitment(LocalDateTime now);

boolean existsByPeriodOverlapping(LocalDateTime startDate, LocalDateTime endDate);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
boolean existsByPeriodOverlapping(LocalDateTime startDate, LocalDateTime endDate);
boolean existsPeriodOverlapping(LocalDateTime startDate, LocalDateTime endDate);

Period를 통째로 넘기는 상황도 아니고, querydsl이라 By 안 써도 될 것 같습니다.

}

private BooleanExpression isOverlappingPeriod(LocalDateTime startDate, LocalDateTime endDate) {
return recruitment.semesterPeriod.startDate.loe(endDate).and(recruitment.semesterPeriod.endDate.gt(startDate));
Copy link
Member

Choose a reason for hiding this comment

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

가독성 측면에서 수식 쓰는 것과 같은 순서로 하면 더 좋을 것 같아요

Suggested change
return recruitment.semesterPeriod.startDate.loe(endDate).and(recruitment.semesterPeriod.endDate.gt(startDate));
return recruitment.semesterPeriod.endDate.gt(startDate).and(recruitment.semesterPeriod.startDate.loe(endDate));

Comment on lines +29 to +38
@Override
public boolean existsByPeriodOverlapping(LocalDateTime startDate, LocalDateTime endDate) {
Integer fetchOne = queryFactory
.selectOne()
.from(recruitment)
.where(isOverlappingPeriod(startDate, endDate))
.fetchFirst();

return fetchOne != null;
}
Copy link
Member

Choose a reason for hiding this comment

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

public 메서드들을 private 메서드들 위에 모아두지 않았었나요?
이건 기억이 안 나는데 다른 구현체 보고 컨벤션에 맞게 수정 부탁드립니다!

@github-actions
Copy link

github-actions bot commented Mar 5, 2026

Job Summary for Gradle

Check Style and Test to Develop :: build-test
Gradle Root Project Requested Tasks Gradle Version Build Outcome Build Scan®
gdsc check 8.5 Build Scan published

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.

♻️ Duplicate comments (2)
src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java (1)

40-42: ⚠️ Potential issue | 🔴 Critical

기간 겹침 경계값이 배타(gt)라 동일 경계 케이스를 허용합니다.

Line [41]의 endDate.gt(startDate)는 기존 종료시각과 신규 시작시각이 같은 경우를 비중복으로 처리합니다. 현재 기간 판정이 포함 경계(goe) 기준이라면 일관성 깨집니다.

🐛 제안 수정
 private BooleanExpression isOverlappingPeriod(LocalDateTime startDate, LocalDateTime endDate) {
-    return recruitment.semesterPeriod.endDate.gt(startDate).and(recruitment.semesterPeriod.startDate.loe(endDate));
+    return recruitment.semesterPeriod.endDate.goe(startDate).and(recruitment.semesterPeriod.startDate.loe(endDate));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java`
around lines 40 - 42, The overlap check in isOverlappingPeriod uses
recruitment.semesterPeriod.endDate.gt(startDate), which treats identical
boundary times as non-overlapping; change this to an inclusive comparison (use
goe) so the condition matches the function's intended inclusive semantics—update
recruitment.semesterPeriod.endDate.gt(startDate) to
recruitment.semesterPeriod.endDate.goe(startDate) (and keep the existing
recruitment.semesterPeriod.startDate.loe(endDate)) so identical boundaries are
considered overlapping.
src/main/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentService.java (1)

41-52: ⚠️ Potential issue | 🔴 Critical

중복 검사와 저장이 원자적이지 않아 동시성에서 중복 생성이 가능합니다.

Line [41]-Line [45]의 조회 기반 검증 뒤 Line [52]에서 저장하는 구조는 경쟁 구간이 열려 있습니다. 동시 요청 2건이 모두 검증을 통과하면 중복 리크루팅이 저장될 수 있습니다.

🔒 최소 완화안
+import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Transactional;

 `@Slf4j`
 `@Service`
 `@RequiredArgsConstructor`
 `@Transactional`(readOnly = true)
 public class AdminRecruitmentService {

-    `@Transactional`
+    `@Transactional`(isolation = Isolation.SERIALIZABLE)
     public void createRecruitment(RecruitmentCreateRequest request) {

추가로 DB 유니크 제약(예: 학기 키)과 제약 위반 예외 변환까지 같이 두는 이중 방어를 권장합니다.

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

In
`@src/main/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentService.java`
around lines 41 - 52, The concurrent duplicate-creation risk arises because
AdminRecruitmentService performs read checks
(recruitmentRepository.existsBySemester and
recruitmentRepository.existsPeriodOverlapping) and then saves
(recruitmentRepository.save) outside an atomic operation; fix by making the
create flow transactional and relying on a DB-unique constraint on the semester
(or period) as the primary guard, i.e., wrap the creation in a transaction in
AdminRecruitmentService and perform
recruitmentRepository.save(Recruitment.create(...)) within that transaction, and
additionally catch and translate the persistence-level constraint violation
(e.g., DataIntegrityViolationException / ConstraintViolationException) into the
domain validation exception that recruitmentValidator.validateRecruitmentCreate
would throw so concurrent requests result in a single persistent record and a
clear domain error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@src/main/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentService.java`:
- Around line 41-52: The concurrent duplicate-creation risk arises because
AdminRecruitmentService performs read checks
(recruitmentRepository.existsBySemester and
recruitmentRepository.existsPeriodOverlapping) and then saves
(recruitmentRepository.save) outside an atomic operation; fix by making the
create flow transactional and relying on a DB-unique constraint on the semester
(or period) as the primary guard, i.e., wrap the creation in a transaction in
AdminRecruitmentService and perform
recruitmentRepository.save(Recruitment.create(...)) within that transaction, and
additionally catch and translate the persistence-level constraint violation
(e.g., DataIntegrityViolationException / ConstraintViolationException) into the
domain validation exception that recruitmentValidator.validateRecruitmentCreate
would throw so concurrent requests result in a single persistent record and a
clear domain error.

In
`@src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java`:
- Around line 40-42: The overlap check in isOverlappingPeriod uses
recruitment.semesterPeriod.endDate.gt(startDate), which treats identical
boundary times as non-overlapping; change this to an inclusive comparison (use
goe) so the condition matches the function's intended inclusive semantics—update
recruitment.semesterPeriod.endDate.gt(startDate) to
recruitment.semesterPeriod.endDate.goe(startDate) (and keep the existing
recruitment.semesterPeriod.startDate.loe(endDate)) so identical boundaries are
considered overlapping.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d3fa0fbc-e481-4c9c-8908-e515682fae6e

📥 Commits

Reviewing files that changed from the base of the PR and between 1c0be7d and 87f9b24.

📒 Files selected for processing (3)
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/application/AdminRecruitmentService.java
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepository.java
  • src/main/java/com/gdschongik/gdsc/domain/recruitment/dao/RecruitmentCustomRepositoryImpl.java

Copy link
Member

@AshCircle AshCircle left a comment

Choose a reason for hiding this comment

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

LGTM

}

private BooleanExpression isOverlappingPeriod(LocalDateTime startDate, LocalDateTime endDate) {
return recruitment.semesterPeriod.endDate.gt(startDate).and(recruitment.semesterPeriod.startDate.loe(endDate));
Copy link
Member

Choose a reason for hiding this comment

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

근데 하나는 equal까지 들어가고 하나는 안 들어가는 이유가 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

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

실수입니다
goe, loe 사용하도록 변경했습니다.

Copy link
Member

@Sangwook02 Sangwook02 left a comment

Choose a reason for hiding this comment

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

코멘트 하나 달아뒀는데 확인해보시고 문제 없으면 바로 머지하시면 될 것 같습니다!

@github-actions
Copy link

github-actions bot commented Mar 8, 2026

Job Summary for Gradle

Check Style and Test to Develop :: build-test
Gradle Root Project Requested Tasks Gradle Version Build Outcome Build Scan®
gdsc check 8.5 Build Scan published

@kimsh1017 kimsh1017 merged commit 0756e33 into develop Mar 8, 2026
3 checks passed
@kimsh1017 kimsh1017 deleted the refactor/1352-recruitment-period-validate branch March 8, 2026 11:52
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.

♻️ Recruitment 생성 시 기간 중복 검증 추가

3 participants