From 4f4a832843ae226fae56f3f363b9eb05e31e4182 Mon Sep 17 00:00:00 2001 From: 1Seob Date: Fri, 9 Jan 2026 18:59:57 +0900 Subject: [PATCH] =?UTF-8?q?refactor(report):=20=EC=9D=BC=EA=B0=84=20?= =?UTF-8?q?=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EC=83=9D=EC=84=B1=20=ED=8A=B8?= =?UTF-8?q?=EB=9E=9C=EC=9E=AD=EC=85=98=20=EB=8B=A8=EC=88=9C=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EC=95=88=EC=A0=95?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 일간 리포트 준비 단계를 단일 트랜잭션으로 묶어 원자성 보장, DB 유니크 제약 기반 중복 생성 방지 및 멱등 처리 적용, 유니크 충돌 시 재조회 방식으로 동시 요청 안정성 개선 --- .../core/service/AnswerEntryService.java | 17 +++++++++++++---- .../core/service/PendingDailyReportService.java | 17 +++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java index 27d8675..04ab19b 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/AnswerEntryService.java @@ -7,8 +7,8 @@ import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto; import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -21,15 +21,24 @@ public class AnswerEntryService { private final AnswerEntryRepository answerEntryRepository; - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional public AnswerEntry getOrCreateTodayAnswerEntry(User user, DailyQuestion dq, String answerText) { TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange(); LocalDate today = TodayDateTimeProvider.getTodayDate(); - return answerEntryRepository.findByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow()) - .orElseGet(() -> answerEntryRepository.save(AnswerEntry.create(user, dq, answerText, today))); + return answerEntryRepository. + findByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow()) + .orElseGet(() -> { + try { + return answerEntryRepository.save(AnswerEntry.create(user, dq, answerText, today)); + } catch (DataIntegrityViolationException e) { + // 동시 요청에서 이미 누가 만들었을 수 있음 -> 재조회로 멱등 처리 + return answerEntryRepository.findByUserAndCreatedAtBetween(user, range.startOfToday(), range.startOfTomorrow()) + .orElseThrow(() -> e); + } + }); } } diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java index 6b85967..5fe7037 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/service/PendingDailyReportService.java @@ -9,8 +9,8 @@ import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; import com.devkor.ifive.nadab.global.shared.util.dto.TodayDateTimeRangeDto; import lombok.RequiredArgsConstructor; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; @@ -23,15 +23,24 @@ public class PendingDailyReportService { private final DailyReportRepository dailyReportRepository; - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional public DailyReport getOrCreatePendingDailyReport(AnswerEntry entry) { TodayDateTimeRangeDto range = TodayDateTimeProvider.getRange(); LocalDate today = TodayDateTimeProvider.getTodayDate(); - DailyReport report = dailyReportRepository.findByAnswerEntryAndCreatedAtBetween(entry, range.startOfToday(), range.startOfTomorrow()) - .orElseGet(() -> dailyReportRepository.save(DailyReport.createPending(entry, today))); + DailyReport report = dailyReportRepository. + findByAnswerEntryAndCreatedAtBetween(entry, range.startOfToday(), range.startOfTomorrow()) + .orElseGet(() -> { + try { + return dailyReportRepository.save(DailyReport.createPending(entry, today)); + } catch (DataIntegrityViolationException e) { + // 동시 요청에서 이미 누가 만들었을 수 있음 -> 재조회로 멱등 처리 + return dailyReportRepository.findByAnswerEntryAndCreatedAtBetween(entry, range.startOfToday(), range.startOfTomorrow()) + .orElseThrow(() -> e); + } + }); if (report.getStatus() == DailyReportStatus.COMPLETED) { throw new ConflictException(ErrorCode.DAILY_REPORT_ALREADY_COMPLETED);