88import com .taja .domain .station .OperationMode ;
99import com .taja .domain .station .Station ;
1010import com .taja .domain .status .StationStatus ;
11- import com .taja .global .exception .StationNotFoundException ;
1211import com .taja .infrastructure .station .StationJpaRepository ;
1312import java .time .LocalDate ;
1413import java .time .LocalDateTime ;
@@ -35,24 +34,27 @@ class StationRedisRepositoryImplCacheTest {
3534 @ InjectMocks
3635 private StationRedisRepositoryImpl stationRedisRepository ;
3736
38- @ DisplayName ("캐시 미스 시 DB에서 조회 후 캐시에 저장하고 반환한다" )
37+ @ DisplayName ("캐시 미스 시 DB에서 배치 조회 후 캐시에 저장하고 반환한다" )
3938 @ Test
40- void getOrRefresh_whenCacheMiss_loadsFromDbAndSavesToCache () {
39+ void findStationInfos_whenCacheMiss_loadsFromDbInBatchAndSavesToCache () {
4140 // given
4241 Integer stationNumber = 101 ;
4342 double lat = 37.5665 ;
4443 double lon = 126.9780 ;
4544
45+ when (stationHashRepository .findMissingNumbers (List .of (stationNumber )))
46+ .thenReturn (List .of (stationNumber ));
47+ when (stationHashRepository .acquireBulkLoadLock ()).thenReturn (true );
48+
49+ Station station = createTestStation (stationNumber , lat , lon );
50+ when (stationJpaRepository .findAllByNumberIn (List .of (stationNumber )))
51+ .thenReturn (List .of (station ));
52+
4653 StationInfo .StationHashInfo cachedHashInfo = new StationInfo .StationHashInfo (
4754 stationNumber , 1L , "테스트 대여소 101" , 0 , LocalDateTime .now ()
4855 );
4956 when (stationHashRepository .fetchAllFields (stationNumber ))
50- .thenReturn (Optional .empty ()) // 첫 번째: 캐시 미스
51- .thenReturn (Optional .of (cachedHashInfo )); // 두 번째: 캐시 저장 후
52-
53- Station station = createTestStation (stationNumber , lat , lon );
54- when (stationJpaRepository .findByNumber (stationNumber ))
55- .thenReturn (Optional .of (station ));
57+ .thenReturn (Optional .of (cachedHashInfo ));
5658
5759 // when
5860 List <StationInfo .StationGeoInfo > geoInfos = List .of (
@@ -63,9 +65,8 @@ void getOrRefresh_whenCacheMiss_loadsFromDbAndSavesToCache() {
6365 // then
6466 assertThat (results ).hasSize (1 );
6567 assertThat (results .getFirst ().number ()).isEqualTo (stationNumber );
66- verify (stationJpaRepository ).findByNumber ( stationNumber );
68+ verify (stationJpaRepository ).findAllByNumberIn ( List . of ( stationNumber ) );
6769 verify (stationHashRepository ).saveStationInfosWithPipeline (anyList (), any (LocalDateTime .class ));
68- verify (stationHashRepository , times (2 )).fetchAllFields (stationNumber );
6970 }
7071
7172 @ DisplayName ("캐시 히트 시 즉시 반환하고 DB 조회하지 않는다" )
@@ -198,58 +199,60 @@ void refreshCacheWithLock_whenLockAcquired_refreshesCache() throws InterruptedEx
198199 verify (stationHashRepository ).releaseLock (stationNumber );
199200 }
200201
201- @ DisplayName ("DB에 대여소가 없으면 예외가 발생한다 " )
202+ @ DisplayName ("DB에도 대여소가 없으면 해당 대여소를 결과에서 제외한다 " )
202203 @ Test
203- void getOrRefresh_whenStationNotFoundInDb_throwsException () {
204+ void findStationInfos_whenStationNotFoundInDb_excludesStation () {
204205 // given
205206 Integer stationNumber = 999 ;
206207 double lat = 37.5665 ;
207208 double lon = 126.9780 ;
208209
210+ when (stationHashRepository .findMissingNumbers (List .of (stationNumber )))
211+ .thenReturn (List .of (stationNumber ));
212+ when (stationHashRepository .acquireBulkLoadLock ()).thenReturn (true );
213+ when (stationJpaRepository .findAllByNumberIn (List .of (stationNumber )))
214+ .thenReturn (List .of ()); // DB에도 없음
209215 when (stationHashRepository .fetchAllFields (stationNumber ))
210216 .thenReturn (Optional .empty ());
211- when (stationJpaRepository .findByNumber (stationNumber ))
212- .thenReturn (Optional .empty ());
213217
214218 List <StationInfo .StationGeoInfo > geoInfos = List .of (
215219 new StationInfo .StationGeoInfo (stationNumber , lat , lon )
216220 );
217221
218- // when & then
219- assertThatThrownBy (() -> stationRedisRepository .findStationInfos (geoInfos ))
220- . isInstanceOf ( StationNotFoundException . class )
221- . hasMessageContaining ( "999 번 대여소를 찾을 수 없습니다" );
222- verify ( stationJpaRepository ). findByNumber ( stationNumber );
223- verify (stationHashRepository , never ()). saveStationInfosWithPipeline ( anyList (), any ( ));
222+ // when
223+ List < StationInfo . StationFullInfo > results = stationRedisRepository .findStationInfos (geoInfos );
224+
225+ // then
226+ assertThat ( results ). isEmpty ( );
227+ verify (stationJpaRepository ). findAllByNumberIn ( List . of ( stationNumber ));
224228 }
225229
226- @ DisplayName ("여러 대여소 조회 시 각각 캐시 상태에 따라 처리한다 " )
230+ @ DisplayName ("여러 대여소 조회 시 캐시 미스된 것만 DB에서 배치 조회한다 " )
227231 @ Test
228- void findStationInfos_whenMultipleStations_processesEachIndependently () {
232+ void findStationInfos_whenMultipleStations_loadsOnlyMissingFromDb () {
229233 // given
230234 StationInfo .StationGeoInfo geo1 = new StationInfo .StationGeoInfo (201 , 37.5665 , 126.9780 );
231235 StationInfo .StationGeoInfo geo2 = new StationInfo .StationGeoInfo (202 , 37.5670 , 126.9785 );
232236
233- // 첫 번째: 캐시 히트
237+ // 202번만 캐시 미스
238+ when (stationHashRepository .findMissingNumbers (List .of (201 , 202 )))
239+ .thenReturn (List .of (202 ));
240+ when (stationHashRepository .findMissingNumbers (List .of (202 )))
241+ .thenReturn (List .of (202 ));
242+ when (stationHashRepository .acquireBulkLoadLock ()).thenReturn (true );
243+
244+ Station station2 = createTestStation (202 , 37.5670 , 126.9785 );
245+ when (stationJpaRepository .findAllByNumberIn (List .of (202 )))
246+ .thenReturn (List .of (station2 ));
247+
234248 StationInfo .StationHashInfo hashInfo1 = new StationInfo .StationHashInfo (
235249 201 , 1L , "테스트 대여소 201" , 5 , LocalDateTime .now ()
236250 );
237- when (stationHashRepository .fetchAllFields (201 ))
238- .thenReturn (Optional .of (hashInfo1 ));
239- when (stationHashRepository .isThresholdReached (201 ))
240- .thenReturn (false );
241-
242- // 두 번째: 캐시 미스
243251 StationInfo .StationHashInfo hashInfo2 = new StationInfo .StationHashInfo (
244252 202 , 2L , "테스트 대여소 202" , 0 , null
245253 );
246- when (stationHashRepository .fetchAllFields (202 ))
247- .thenReturn (Optional .empty ())
248- .thenReturn (Optional .of (hashInfo2 ));
249-
250- Station station2 = createTestStation (202 , 37.5670 , 126.9785 );
251- when (stationJpaRepository .findByNumber (202 ))
252- .thenReturn (Optional .of (station2 ));
254+ when (stationHashRepository .fetchAllFields (201 )).thenReturn (Optional .of (hashInfo1 ));
255+ when (stationHashRepository .fetchAllFields (202 )).thenReturn (Optional .of (hashInfo2 ));
253256
254257 // when
255258 List <StationInfo .StationFullInfo > results = stationRedisRepository .findStationInfos (List .of (geo1 , geo2 ));
@@ -258,7 +261,7 @@ void findStationInfos_whenMultipleStations_processesEachIndependently() {
258261 assertThat (results ).hasSize (2 );
259262 assertThat (results .get (0 ).number ()).isEqualTo (201 );
260263 assertThat (results .get (1 ).number ()).isEqualTo (202 );
261- verify (stationJpaRepository , times ( 1 )). findByNumber ( 202 ); // 두 번째만 DB 조회
264+ verify (stationJpaRepository ). findAllByNumberIn ( List . of ( 202 )) ; // 미스된 것만 배치 조회
262265 }
263266
264267 @ DisplayName ("update 시 캐시 누락된 대여소가 있으면 DB에서 정적 정보를 조회해 재적재한 뒤 갱신한다" )
0 commit comments