diff --git a/app/src/main/resources/logback-spring.xml b/app/src/main/resources/logback-spring.xml index c3b08e4..1c57c8c 100644 --- a/app/src/main/resources/logback-spring.xml +++ b/app/src/main/resources/logback-spring.xml @@ -46,8 +46,18 @@ + + - + + + 20 + 4096 + 40 + true + true + + @@ -58,7 +68,15 @@ 14 1GB - + + + 20 + 4096 + 40 + true + true + + diff --git a/gradle.properties b/gradle.properties index 9e6eb6d..7b6cf32 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -appVersion=0.2.0 +appVersion=0.2.1 diff --git a/location/src/main/java/com/nalssilog/location/application/LocationService.java b/location/src/main/java/com/nalssilog/location/application/LocationService.java index f7a27cb..4361989 100644 --- a/location/src/main/java/com/nalssilog/location/application/LocationService.java +++ b/location/src/main/java/com/nalssilog/location/application/LocationService.java @@ -73,6 +73,7 @@ public LocationInfo reverseGeocode(double latitude, double longitude) { return locationRepository.findOrCreate(region); } + @Transactional(propagation = Propagation.NOT_SUPPORTED) public PopularLocationSnapshotInfo getPopular() { PopularLocationSnapshotData snapshot = popularLocationSource.latestSnapshot(); List locationIds = snapshot.rankings().stream() diff --git a/location/src/test/java/com/nalssilog/location/application/LocationServiceTest.java b/location/src/test/java/com/nalssilog/location/application/LocationServiceTest.java index 0f12a12..812e743 100644 --- a/location/src/test/java/com/nalssilog/location/application/LocationServiceTest.java +++ b/location/src/test/java/com/nalssilog/location/application/LocationServiceTest.java @@ -21,6 +21,8 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; @SuppressWarnings("java:S5960") // 표준 src/test 소스의 AssertJ 검증을 운영 코드 assertion으로 오인하는 경고. class LocationServiceTest { @@ -34,6 +36,19 @@ class LocationServiceTest { kakaoMapClient ); + @Test + void popularLookupSuspendsTheClassLevelReadOnlyTransaction() throws NoSuchMethodException { + AnnotationTransactionAttributeSource attributeSource = + new AnnotationTransactionAttributeSource(); + var transactionAttribute = attributeSource.getTransactionAttribute( + LocationService.class.getMethod("getPopular"), + LocationService.class); + + assertThat(transactionAttribute).isNotNull(); + assertThat(transactionAttribute.getPropagationBehavior()) + .isEqualTo(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); + } + @Test void resolvesKakaoRegionAndReturnsPersistedLocation() { KakaoRegion region = new KakaoRegion( diff --git a/report/src/main/java/com/nalssilog/report/application/PopularLocationSnapshotService.java b/report/src/main/java/com/nalssilog/report/application/PopularLocationSnapshotService.java index 9e8c08a..c45447e 100644 --- a/report/src/main/java/com/nalssilog/report/application/PopularLocationSnapshotService.java +++ b/report/src/main/java/com/nalssilog/report/application/PopularLocationSnapshotService.java @@ -8,6 +8,7 @@ import java.util.stream.Collectors; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.nalssilog.location.application.dto.PopularLocationSnapshotData; @@ -54,7 +55,7 @@ private static PopularRankMovement movement(Integer rankChange) { return PopularRankMovement.SAME; } - @Transactional + @Transactional(propagation = Propagation.REQUIRES_NEW) public PopularLocationSnapshotData latestOrRefreshAt(Instant now) { PopularLocationSnapshot latest = snapshotRepository .findFirstByOrderByCalculatedAtDescIdDesc() @@ -77,7 +78,7 @@ public PopularLocationSnapshotData latestOrRefreshAt(Instant now) { return createSnapshot(now, latest); } - @Transactional + @Transactional(propagation = Propagation.REQUIRES_NEW) public PopularLocationSnapshotData captureAt(Instant calculatedAt) { lockRepository.acquire(); diff --git a/report/src/test/java/com/nalssilog/report/application/PopularLocationSnapshotServiceTest.java b/report/src/test/java/com/nalssilog/report/application/PopularLocationSnapshotServiceTest.java index f86d0d2..31c6a58 100644 --- a/report/src/test/java/com/nalssilog/report/application/PopularLocationSnapshotServiceTest.java +++ b/report/src/test/java/com/nalssilog/report/application/PopularLocationSnapshotServiceTest.java @@ -23,6 +23,8 @@ import java.util.Optional; import org.junit.jupiter.api.Test; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource; @SuppressWarnings("java:S5960") class PopularLocationSnapshotServiceTest { @@ -49,6 +51,31 @@ class PopularLocationSnapshotServiceTest { lockRepository, PROPERTIES); + @Test + void snapshotWritesAlwaysUseIndependentTransactions() throws NoSuchMethodException { + AnnotationTransactionAttributeSource attributeSource = + new AnnotationTransactionAttributeSource(); + var refreshAttribute = attributeSource.getTransactionAttribute( + PopularLocationSnapshotService.class.getMethod( + "latestOrRefreshAt", + Instant.class), + PopularLocationSnapshotService.class); + var captureAttribute = attributeSource.getTransactionAttribute( + PopularLocationSnapshotService.class.getMethod( + "captureAt", + Instant.class), + PopularLocationSnapshotService.class); + + assertThat(refreshAttribute).isNotNull(); + assertThat(refreshAttribute.getPropagationBehavior()) + .isEqualTo(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + assertThat(refreshAttribute.isReadOnly()).isFalse(); + assertThat(captureAttribute).isNotNull(); + assertThat(captureAttribute.getPropagationBehavior()) + .isEqualTo(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + assertThat(captureAttribute.isReadOnly()).isFalse(); + } + @Test void createsFirstSnapshotWithNewMovementsAndDetailedMetrics() { Instant calculatedAt = Instant.parse("2026-07-30T06:00:00Z");