From 4bde1a8352bbd5c069857299f082cf57d8c344c2 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 5 Oct 2026 17:03:01 +0800 Subject: [PATCH] fix(firstleg): avoid shared lock upgrades when initializing customs --- .../customs/container/mapper/ContainerCustomsMapper.java | 3 ++- .../customs/container/service/ContainerCustomsService.java | 7 ++++--- .../container/ContainerCustomsMapperContractTest.java | 7 +++++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/customs/container/mapper/ContainerCustomsMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/customs/container/mapper/ContainerCustomsMapper.java index 96b2c564d..0cb37d577 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/customs/container/mapper/ContainerCustomsMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/customs/container/mapper/ContainerCustomsMapper.java @@ -88,9 +88,10 @@ public interface ContainerCustomsMapper { Long completedPlanForOrder(@Param("tenantId") long tenantId, @Param("orderId") long orderId); @Insert(""" - INSERT IGNORE INTO fl_customs_container + INSERT INTO fl_customs_container (id,loading_plan_id,container_booking_id,status,created_at,updated_at,version) VALUES (#{id},#{planId},#{containerBookingId},#{status},CURRENT_TIMESTAMP(3),CURRENT_TIMESTAMP(3),0) + ON DUPLICATE KEY UPDATE id=id """) int insertContainer(@Param("id") long id,@Param("planId") long planId, @Param("containerBookingId") long containerBookingId,@Param("status") String status); diff --git a/firstleg/src/main/java/com/baiyue/firstleg/customs/container/service/ContainerCustomsService.java b/firstleg/src/main/java/com/baiyue/firstleg/customs/container/service/ContainerCustomsService.java index faa94dbaf..7882613ba 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/customs/container/service/ContainerCustomsService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/customs/container/service/ContainerCustomsService.java @@ -563,9 +563,10 @@ public class ContainerCustomsService { if (!"COMPLETED".equals(plan.loadingPlanStatus())) throw conflict("装柜完成后才能处理报关资料"); boolean refund = rows.stream().anyMatch(row -> "TAX_REFUND".equals(row.taxMode())); mapper.insertContainer(IdWorker.getId(), planId, plan.containerBookingId(), refund ? WAITING : READY); - // A concurrent INSERT IGNORE may wait for another request's insert to commit. The - // transaction's earlier plan reads can retain an RR snapshot that cannot see that row. - // Read the winning aggregate under its row lock, rather than reusing a snapshot read. + // The no-op upsert already takes an exclusive lock on a duplicate, avoiding the + // shared-to-exclusive lock upgrade of INSERT IGNORE followed by FOR UPDATE. + // Earlier plan reads may retain an RR snapshot predating the winning insert, so + // retrieve the real aggregate with a current read, not a snapshot read. ContainerRow container = mapper.lockContainer(planId); if (container == null) throw conflict("整柜报关记录初始化失败"); return refresh(planId, container); diff --git a/firstleg/src/test/java/com/baiyue/firstleg/customs/container/ContainerCustomsMapperContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/customs/container/ContainerCustomsMapperContractTest.java index a8f005c3b..101bcff6c 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/customs/container/ContainerCustomsMapperContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/customs/container/ContainerCustomsMapperContractTest.java @@ -11,6 +11,13 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; class ContainerCustomsMapperContractTest { + @Test void duplicateInitializationTakesUpdateLockWithoutChangingExistingFacts() throws Exception { + String sql = String.join(" ", ContainerCustomsMapper.class.getMethod("insertContainer", + long.class,long.class,long.class,String.class).getAnnotation(org.apache.ibatis.annotations.Insert.class).value()); + assertThat(sql).contains("ON DUPLICATE KEY UPDATE id=id").doesNotContain("INSERT IGNORE"); + assertThat(sql.substring(sql.indexOf("ON DUPLICATE KEY UPDATE"))).isEqualTo("ON DUPLICATE KEY UPDATE id=id\n"); + } + @Test void initializationFlushesSessionCacheBeforeCurrentAggregateRead() { var configuration = new org.apache.ibatis.session.Configuration(); configuration.addMapper(ContainerCustomsMapper.class);