From e69a248d83e4692cae0532c72c0bd3aeb496a189 Mon Sep 17 00:00:00 2001 From: krestar Date: Mon, 17 Aug 2026 13:47:50 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20PostgreSQL=20=EC=97=85=EB=AC=B4=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=EC=9D=98=20=EC=9E=A0?= =?UTF-8?q?=EA=B8=88=20=EC=B6=A9=EB=8F=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 읽기 전용 업무 상세 조회에서 SELECT FOR UPDATE가 실행되어 PostgreSQL이 500 오류를 반환하던 문제를 수정한다. - 업무 상세 조회에는 비잠금 근로자 조회를 사용하고, Task 생성·변경 경로의 행 잠금은 유지하여 근로자 보관과의 동시성 제어가 계속 동작하도록 한다. - PostgreSQL 데모 시드 테스트에 업무 상세 조회 회귀 검증을 추가한다. --- .../task/application/TaskWorkflowService.java | 9 +++++++- .../port/WorkerTaskContextReader.java | 2 ++ .../JdbcWorkerTaskContextReader.java | 12 +++++++++++ ...dPostgreSqlApplicationIntegrationTest.java | 21 +++++++++++++++++++ 4 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/fowoco/server/task/application/TaskWorkflowService.java b/src/main/java/com/fowoco/server/task/application/TaskWorkflowService.java index cfccfe50..a885cffa 100644 --- a/src/main/java/com/fowoco/server/task/application/TaskWorkflowService.java +++ b/src/main/java/com/fowoco/server/task/application/TaskWorkflowService.java @@ -240,7 +240,7 @@ public TaskResult findById(UUID taskId, ActorContext actor) { return toResult( task, checklistRepository.findAllByTaskIdAndCompanyId(taskId, actor.companyId()), - findWorker(task, actor.companyId()), + findWorkerReadOnly(task, actor.companyId()), catalogService.requireWorkflow(task.workflowId()) ); } @@ -611,6 +611,13 @@ private WorkerTaskContext findWorker(Task task, UUID companyId) { : null; } + private WorkerTaskContext findWorkerReadOnly(Task task, UUID companyId) { + return task.targetType() == TaskTargetType.WORKER + ? workerReader.findByIdAndCompanyIdReadOnly(task.workerId(), companyId) + .orElseThrow(() -> new ApiException(TaskErrorCode.WORKER_NOT_FOUND)) + : null; + } + private WorkerTaskContext resolveCreateTarget( CreateTaskCommand command, WorkflowDefinition workflow, diff --git a/src/main/java/com/fowoco/server/worker/application/port/WorkerTaskContextReader.java b/src/main/java/com/fowoco/server/worker/application/port/WorkerTaskContextReader.java index 3506bd7f..c79f224c 100644 --- a/src/main/java/com/fowoco/server/worker/application/port/WorkerTaskContextReader.java +++ b/src/main/java/com/fowoco/server/worker/application/port/WorkerTaskContextReader.java @@ -7,4 +7,6 @@ public interface WorkerTaskContextReader { Optional findByIdAndCompanyId(UUID workerId, UUID companyId); + + Optional findByIdAndCompanyIdReadOnly(UUID workerId, UUID companyId); } diff --git a/src/main/java/com/fowoco/server/worker/infrastructure/JdbcWorkerTaskContextReader.java b/src/main/java/com/fowoco/server/worker/infrastructure/JdbcWorkerTaskContextReader.java index 05e5bd80..fe688743 100644 --- a/src/main/java/com/fowoco/server/worker/infrastructure/JdbcWorkerTaskContextReader.java +++ b/src/main/java/com/fowoco/server/worker/infrastructure/JdbcWorkerTaskContextReader.java @@ -36,6 +36,18 @@ public Optional findByIdAndCompanyId(UUID workerId, UUID comp return Optional.empty(); } + return findContext(workerId, companyId); + } + + @Override + public Optional findByIdAndCompanyIdReadOnly( + UUID workerId, + UUID companyId + ) { + return findContext(workerId, companyId); + } + + private Optional findContext(UUID workerId, UUID companyId) { List rows = jdbcTemplate.query( """ SELECT worker_id, work_status, stay_expiry_date, diff --git a/src/test/java/com/fowoco/server/demo/infrastructure/seed/DemoSeedPostgreSqlApplicationIntegrationTest.java b/src/test/java/com/fowoco/server/demo/infrastructure/seed/DemoSeedPostgreSqlApplicationIntegrationTest.java index f916eefd..fac8d3de 100644 --- a/src/test/java/com/fowoco/server/demo/infrastructure/seed/DemoSeedPostgreSqlApplicationIntegrationTest.java +++ b/src/test/java/com/fowoco/server/demo/infrastructure/seed/DemoSeedPostgreSqlApplicationIntegrationTest.java @@ -3,11 +3,16 @@ import static org.assertj.core.api.Assertions.assertThat; import com.fowoco.server.ServerApplication; +import com.fowoco.server.auth.application.ActorContext; +import com.fowoco.server.auth.domain.UserRole; import com.fowoco.server.common.security.PostgreSqlRlsTestLock; +import com.fowoco.server.task.application.TaskResult; +import com.fowoco.server.task.application.TaskWorkflowService; import java.nio.file.Path; import java.sql.Timestamp; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; import java.util.UUID; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; @@ -24,6 +29,10 @@ class DemoSeedPostgreSqlApplicationIntegrationTest { private static final UUID COMPANY_ID = UUID.fromString("90000000-0000-0000-0000-000000000001"); + private static final UUID ADMIN_USER_ID = + UUID.fromString("90000000-0000-0000-0000-000000000002"); + private static final UUID TASK_ID = + UUID.fromString("94000000-0000-0000-0000-000000000003"); private static final UUID REPRESENTATIVE_WORKER_ID = UUID.fromString("92000000-0000-0000-0000-000000000006"); private static final UUID SHOWCASE_CASE_ID = @@ -67,6 +76,7 @@ void bootsFullDemoSeedUpgradesPreviousReleaseAndRestartsIdempotentlyOnPostgreSql )) { JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); assertGoldenFlowStartState(jdbcTemplate); + assertTaskDetailReadable(context); firstBoot = snapshot(jdbcTemplate); emulatePreviousReleaseSeed(jdbcTemplate); } @@ -78,6 +88,7 @@ void bootsFullDemoSeedUpgradesPreviousReleaseAndRestartsIdempotentlyOnPostgreSql )) { JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class); assertGoldenFlowStartState(jdbcTemplate); + assertTaskDetailReadable(context); assertThat(snapshot(jdbcTemplate)).isEqualTo(firstBoot); } @@ -261,6 +272,16 @@ SELECT COUNT(*) } } + private void assertTaskDetailReadable(ConfigurableApplicationContext context) { + TaskResult result = context.getBean(TaskWorkflowService.class).findById( + TASK_ID, + new ActorContext(ADMIN_USER_ID, COMPANY_ID, Set.of(UserRole.ADMIN)) + ); + + assertThat(result.task().taskId()).isEqualTo(TASK_ID); + assertThat(result.task().workerId()).isNotNull(); + } + private SeedSnapshot snapshot(JdbcTemplate jdbcTemplate) { Map counts = new LinkedHashMap<>(); EXPECTED_COUNTS.forEach((table, expected) -> {