From 124b3e23f48aad5b5f5e00c08698fe2356c9bfdd Mon Sep 17 00:00:00 2001 From: simhani1 Date: Mon, 18 Aug 2025 16:47:41 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=B9=B4=ED=8E=98=EC=9D=B8=20?= =?UTF-8?q?=EC=BA=90=EC=8B=9C=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 8ed3a25..35953d4 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,8 @@ dependencies { implementation 'org.springframework.retry:spring-retry' implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + implementation 'org.springframework.boot:spring-boot-starter-cache' + implementation 'com.github.ben-manes.caffeine:caffeine' /* Swagger */ implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.4.0' From 71526bdf805eaef289385d9f2e7d782f6ad6f91f Mon Sep 17 00:00:00 2001 From: simhani1 Date: Mon, 18 Aug 2025 17:00:24 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EC=BA=90=EC=8B=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../planetrush/PlanetrushApplication.java | 2 ++ .../planetrush/core/config/AppConfig.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/com/planetrush/planetrush/PlanetrushApplication.java b/src/main/java/com/planetrush/planetrush/PlanetrushApplication.java index 1a8ebeb..238ea48 100644 --- a/src/main/java/com/planetrush/planetrush/PlanetrushApplication.java +++ b/src/main/java/com/planetrush/planetrush/PlanetrushApplication.java @@ -2,8 +2,10 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.scheduling.annotation.EnableScheduling; +@EnableCaching @EnableScheduling @SpringBootApplication public class PlanetrushApplication { diff --git a/src/main/java/com/planetrush/planetrush/core/config/AppConfig.java b/src/main/java/com/planetrush/planetrush/core/config/AppConfig.java index 52e2fd3..9d41ecf 100644 --- a/src/main/java/com/planetrush/planetrush/core/config/AppConfig.java +++ b/src/main/java/com/planetrush/planetrush/core/config/AppConfig.java @@ -1,11 +1,17 @@ package com.planetrush.planetrush.core.config; +import java.util.concurrent.TimeUnit; + +import org.springframework.cache.CacheManager; +import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.retry.annotation.EnableRetry; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.web.client.RestTemplate; +import com.github.benmanes.caffeine.cache.Caffeine; + @EnableAsync @EnableRetry @Configuration @@ -16,4 +22,12 @@ public RestTemplate restTemplate() { return new RestTemplate(); } + @Bean + public CacheManager cacheManager() { + CaffeineCacheManager cacheManager = new CaffeineCacheManager("challengeProgressRate"); + cacheManager.setCaffeine( + Caffeine.newBuilder().expireAfterWrite(24, TimeUnit.HOURS).recordStats() + ); + return cacheManager; + } } From a7b095bb192c936fbd6c0842dbc950973f0e8804 Mon Sep 17 00:00:00 2001 From: simhani1 Date: Mon, 18 Aug 2025 18:00:34 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=ED=86=B5=EA=B3=84=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=EC=97=90=20=EC=B9=B4=ED=8E=98=EC=9D=B8=20=EC=BA=90?= =?UTF-8?q?=EC=8B=9C=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../planetrush/core/config/AppConfig.java | 2 +- .../member/service/MemberServiceImpl.java | 3 +- .../planetrush/ServiceUnitTest.java | 8 ++ .../member/MemberIntegrationTest.java | 88 +++++++++++++++++++ .../member/MemberServiceUnitTest.java | 25 ++++++ 5 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 src/test/java/com/planetrush/planetrush/ServiceUnitTest.java create mode 100644 src/test/java/com/planetrush/planetrush/member/MemberIntegrationTest.java create mode 100644 src/test/java/com/planetrush/planetrush/member/MemberServiceUnitTest.java diff --git a/src/main/java/com/planetrush/planetrush/core/config/AppConfig.java b/src/main/java/com/planetrush/planetrush/core/config/AppConfig.java index 9d41ecf..ca530c5 100644 --- a/src/main/java/com/planetrush/planetrush/core/config/AppConfig.java +++ b/src/main/java/com/planetrush/planetrush/core/config/AppConfig.java @@ -24,7 +24,7 @@ public RestTemplate restTemplate() { @Bean public CacheManager cacheManager() { - CaffeineCacheManager cacheManager = new CaffeineCacheManager("challengeProgressRate"); + CaffeineCacheManager cacheManager = new CaffeineCacheManager("challengeProgressAvg"); cacheManager.setCaffeine( Caffeine.newBuilder().expireAfterWrite(24, TimeUnit.HOURS).recordStats() ); diff --git a/src/main/java/com/planetrush/planetrush/member/service/MemberServiceImpl.java b/src/main/java/com/planetrush/planetrush/member/service/MemberServiceImpl.java index 0498a1a..554c005 100644 --- a/src/main/java/com/planetrush/planetrush/member/service/MemberServiceImpl.java +++ b/src/main/java/com/planetrush/planetrush/member/service/MemberServiceImpl.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -53,7 +54,7 @@ public List getPlanetCollections(CollectionSearchCond searc *

이 메서드는 반환값을 캐싱하여 관리합니다.

*

캐시 미스가 발생하는 경우에만 플라스크 서버로 API 요청을 전송하여 새로운 데이터로 캐시에 저장합니다.

*/ - // TODO: 카페인 캐시를 이용하도록 변경 + @Cacheable(cacheNames = "challengeProgressAvg", key = "#memberId") @Override public GetMyProgressAvgDto getMyProgressAvgPer(Long memberId) { Member member = memberRepository.findById(memberId) diff --git a/src/test/java/com/planetrush/planetrush/ServiceUnitTest.java b/src/test/java/com/planetrush/planetrush/ServiceUnitTest.java new file mode 100644 index 0000000..3464eb3 --- /dev/null +++ b/src/test/java/com/planetrush/planetrush/ServiceUnitTest.java @@ -0,0 +1,8 @@ +package com.planetrush.planetrush; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public abstract class ServiceUnitTest { +} diff --git a/src/test/java/com/planetrush/planetrush/member/MemberIntegrationTest.java b/src/test/java/com/planetrush/planetrush/member/MemberIntegrationTest.java new file mode 100644 index 0000000..9a6f0aa --- /dev/null +++ b/src/test/java/com/planetrush/planetrush/member/MemberIntegrationTest.java @@ -0,0 +1,88 @@ +package com.planetrush.planetrush.member; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +import com.planetrush.planetrush.IntegrationTest; +import com.planetrush.planetrush.fixture.MemberFixture; +import com.planetrush.planetrush.infra.flask.util.FlaskApiClient; +import com.planetrush.planetrush.member.domain.Member; +import com.planetrush.planetrush.member.repository.MemberRepository; +import com.planetrush.planetrush.member.service.MemberService; +import com.planetrush.planetrush.member.service.dto.GetMyProgressAvgDto; + +public class MemberIntegrationTest extends IntegrationTest { + + @Autowired + private MemberService memberService; + + @Autowired + private CacheManager cacheManager; + + @MockBean + private MemberRepository memberRepository; + + @MockBean + private FlaskApiClient flaskApiClient; + + @DisplayName("통계 데이터를 조회할 때 캐싱된 값을 반환해야 한다.") + @Test + void should_use_cache_when_call_get_my_progress_avg() { + // GIVEN + Member member = Mockito.spy(MemberFixture.activeMember()); + GetMyProgressAvgDto dto = GetMyProgressAvgDto.builder() + .challengeCnt(10) + .completionCnt(10) + + .beautyAvg(1.0) + .lifeAvg(1.0) + .exerciseAvg(1.0) + .studyAvg(1.0) + .etcAvg(1.0) + .totalAvg(1.0) + + .myBeautyAvg(1.0) + .myLifeAvg(1.0) + .myExerciseAvg(1.0) + .myStudyAvg(1.0) + .myEtcAvg(1.0) + .myTotalAvg(1.0) + + .myBeautyPer(1.0) + .myLifePer(1.0) + .myExercisePer(1.0) + .myStudyPer(1.0) + .myEtcPer(1.0) + .myTotalPer(1.0) + + .build(); + + // WHEN + when(member.getId()).thenReturn(1L); + when(memberRepository.findById(anyLong())).thenReturn(Optional.of(member)); + when(flaskApiClient.getMyProgressAvg(anyLong())).thenReturn(dto); + + for (int i = 0; i < 10; i++) { + memberService.getMyProgressAvgPer(member.getId()); + } + + // THEN + Cache cache = cacheManager.getCache("challengeProgressAvg"); + assertThat(cache).isNotNull() + .extracting(it -> it.get(member.getId())).isNotNull() + .extracting(it -> it.get()).isNotNull() + .isInstanceOf(GetMyProgressAvgDto.class); + verify(flaskApiClient, times(1)).getMyProgressAvg(member.getId()); + } +} \ No newline at end of file diff --git a/src/test/java/com/planetrush/planetrush/member/MemberServiceUnitTest.java b/src/test/java/com/planetrush/planetrush/member/MemberServiceUnitTest.java new file mode 100644 index 0000000..ac291df --- /dev/null +++ b/src/test/java/com/planetrush/planetrush/member/MemberServiceUnitTest.java @@ -0,0 +1,25 @@ +package com.planetrush.planetrush.member; + +import org.mockito.InjectMocks; +import org.mockito.Mock; + +import com.planetrush.planetrush.ServiceUnitTest; +import com.planetrush.planetrush.infra.flask.util.FlaskApiClient; +import com.planetrush.planetrush.member.repository.MemberRepository; +import com.planetrush.planetrush.member.repository.custom.ChallengeHistoryRepositoryCustom; +import com.planetrush.planetrush.member.service.MemberServiceImpl; + +public abstract class MemberServiceUnitTest extends ServiceUnitTest { + + @InjectMocks + protected MemberServiceImpl memberService; + + @Mock + protected FlaskApiClient flaskApiClient; + + @Mock + protected MemberRepository memberRepository; + + @Mock + protected ChallengeHistoryRepositoryCustom challengeHistoryRepositoryCustom; +}