diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/service/PersonalSpaceService.java b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/service/PersonalSpaceService.java index cc502dfd..667c5764 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/service/PersonalSpaceService.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/main/java/org/dromara/aihr/personal/service/PersonalSpaceService.java @@ -38,6 +38,14 @@ public class PersonalSpaceService { } } + /** + * Ensures and locks the current owner's space, then validates that one future item of the given size can fit. + * This check intentionally does not mutate usage counters. The future ingestion application service must call + * it inside its outer transaction and perform the item insert plus counter mutation in that same transaction so + * the row lock remains effective. This method must not use an independent transaction. + * + * @return the locked personal space id + */ @Transactional public long reserve(PersonalOwner owner, long bytes) { if (bytes < 0 || properties.getMaxItems() <= 0 || properties.getMaxSpaceMb() <= 0) { @@ -54,14 +62,6 @@ public class PersonalSpaceService { throw new ServiceException(QUOTA_EXCEEDED); } - int updated = jdbcTemplate.update(""" - update aihr_personal_space - set used_bytes = used_bytes + ?, item_count = item_count + 1 - where id = ? and tenant_id = ? and owner_user_id = ? - """, bytes, spaceId, owner.tenantId(), owner.userId()); - if (updated != 1) { - throw new ServiceException(QUOTA_EXCEEDED); - } return spaceId; } diff --git a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/PersonalSpaceServiceTest.java b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/PersonalSpaceServiceTest.java index 7d3a506b..259a48d4 100644 --- a/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/PersonalSpaceServiceTest.java +++ b/backend/ruoyi-modules/ruoyi-aihr/src/test/java/org/dromara/aihr/personal/PersonalSpaceServiceTest.java @@ -76,12 +76,10 @@ class PersonalSpaceServiceTest { } @Test - void reserveUpsertsOwnerSpaceBeforeLockAndAtomicallyConsumesCapacity() { + void reserveUpsertsOwnerSpaceBeforeLockAndReturnsWithoutMutatingCounters() { JdbcTemplate jdbc = mock(JdbcTemplate.class); when(jdbc.queryForMap(contains("from aihr_personal_space"), eq("000000"), eq(101L))) .thenReturn(space(7L, 500L * 1024 * 1024, 0L, 0)); - when(jdbc.update(contains("set used_bytes = used_bytes + ?"), - eq(1024L), eq(7L), eq("000000"), eq(101L))).thenReturn(1); PersonalSpaceService service = new PersonalSpaceService(jdbc, properties()); assertEquals(7L, service.reserve(new PersonalOwner("000000", 101L, "ext-101"), 1024L)); @@ -90,25 +88,22 @@ class PersonalSpaceServiceTest { order.verify(jdbc).update(contains("on duplicate key update"), eq("000000"), eq(101L), eq("ext-101"), eq(500L * 1024 * 1024)); order.verify(jdbc).queryForMap(contains("for update"), eq("000000"), eq(101L)); - order.verify(jdbc).update( - contains("where id = ? and tenant_id = ? and owner_user_id = ?"), - eq(1024L), eq(7L), eq("000000"), eq(101L)); verifyNoMoreInteractions(jdbc); } @Test - void reserveZeroBytesStillConsumesOneItemSlot() { + void reserveZeroBytesIsValidWithoutMutatingCounters() { JdbcTemplate jdbc = mock(JdbcTemplate.class); when(jdbc.queryForMap(contains("from aihr_personal_space"), eq("000000"), eq(101L))) .thenReturn(space(7L, 1024L, 500L, 3)); - when(jdbc.update(contains("set used_bytes = used_bytes + ?"), - eq(0L), eq(7L), eq("000000"), eq(101L))).thenReturn(1); PersonalSpaceService service = new PersonalSpaceService(jdbc, properties()); assertEquals(7L, service.reserve(new PersonalOwner("000000", 101L, null), 0L)); - verify(jdbc).update(contains("item_count = item_count + 1"), - eq(0L), eq(7L), eq("000000"), eq(101L)); + verify(jdbc).update(contains("on duplicate key update"), + eq("000000"), eq(101L), eq(null), eq(500L * 1024 * 1024)); + verify(jdbc).queryForMap(contains("for update"), eq("000000"), eq(101L)); + verifyNoMoreInteractions(jdbc); } @Test