-
Notifications
You must be signed in to change notification settings - Fork 2
perf: 캘린더 캐시 조회 추가 #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
perf: 캘린더 캐시 조회 추가 #252
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package inu.codin.codin.common.config; | ||
|
|
||
| import org.springframework.cache.CacheManager; | ||
| import org.springframework.cache.annotation.EnableCaching; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.data.redis.cache.RedisCacheConfiguration; | ||
| import org.springframework.data.redis.cache.RedisCacheManager; | ||
| import org.springframework.data.redis.connection.RedisConnectionFactory; | ||
| import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; | ||
| import org.springframework.data.redis.serializer.RedisSerializationContext; | ||
| import org.springframework.data.redis.serializer.StringRedisSerializer; | ||
|
|
||
| import java.time.Duration; | ||
|
|
||
| @EnableCaching | ||
| @Configuration | ||
| public class CacheConfig { | ||
|
|
||
| @Bean | ||
| public CacheManager cacheManager(RedisConnectionFactory cf) { | ||
| RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() | ||
| .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) | ||
| .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) | ||
| .entryTtl(Duration.ofMinutes(30)); | ||
|
|
||
| return RedisCacheManager.RedisCacheManagerBuilder | ||
| .fromConnectionFactory(cf) | ||
| .cacheDefaults(redisCacheConfiguration) | ||
| .build(); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,19 +7,37 @@ | |||||||||||||||||
| import inu.codin.codin.domain.calendar.exception.CalendarException; | ||||||||||||||||||
| import inu.codin.codin.domain.calendar.repository.CalendarRepository; | ||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||
| import org.bson.types.ObjectId; | ||||||||||||||||||
| import org.springframework.cache.Cache; | ||||||||||||||||||
| import org.springframework.cache.CacheManager; | ||||||||||||||||||
| import org.springframework.cache.annotation.Cacheable; | ||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||
| import java.time.YearMonth; | ||||||||||||||||||
| import java.util.*; | ||||||||||||||||||
|
|
||||||||||||||||||
| @Slf4j | ||||||||||||||||||
| @Service | ||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||
| public class CalendarService { | ||||||||||||||||||
|
|
||||||||||||||||||
| private final CalendarRepository calendarRepository; | ||||||||||||||||||
|
|
||||||||||||||||||
| private final String CACHE_NAME = "calendar"; | ||||||||||||||||||
| private final CacheManager cacheManager; | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * 캘린더 조회 | ||||||||||||||||||
| * + 조회 캐시 Write-Through 전략 사용 | ||||||||||||||||||
| * @param year 년도 ex) 2025 | ||||||||||||||||||
| * @param month 월 ex) 8 | ||||||||||||||||||
| * @return CalendarMonthResponse | ||||||||||||||||||
| */ | ||||||||||||||||||
| @Cacheable(value = CACHE_NAME, key = "'month:' + T(java.time.YearMonth).of(#year, #month)") | ||||||||||||||||||
| public CalendarMonthResponse getMonth(int year, int month) { | ||||||||||||||||||
| log.info("Getting month for year {} and month {}", year, month); | ||||||||||||||||||
| if (month < 1 || month > 12) { | ||||||||||||||||||
|
Comment on lines
+38
to
41
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 키 SpEL에서 YearMonth.of 사용 시 유효성 검사 이전에 예외 발생 가능 + value 속성 사용법
안전한 키 문자열 포맷을 사용하세요. - @Cacheable(value = CACHE_NAME, key = "'month:' + T(java.time.YearMonth).of(#year, #month)")
+ @Cacheable(cacheNames = CACHE_NAME, key = "T(java.lang.String).format('month:%04d-%02d', #year, #month)")
- public CalendarMonthResponse getMonth(int year, int month) {
- log.info("Getting month for year {} and month {}", year, month);
+ public CalendarMonthResponse getMonth(int year, int month) {
+ log.info("Getting month for year {} and month {}", year, month);설명:
📝 Committable suggestion
Suggested change
|
||||||||||||||||||
| throw new CalendarException(CalendarErrorCode.DATE_FORMAT_ERROR); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -61,6 +79,12 @@ public CalendarMonthResponse getMonth(int year, int month) { | |||||||||||||||||
| .build(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * 켈린더 이벤트 생성 | ||||||||||||||||||
| * + 기존 엔티티의 시간을 기준으로 캐시를 무효화 | ||||||||||||||||||
| * @param request CalendarCreateRequest | ||||||||||||||||||
| * @return CalendarCreateResponse | ||||||||||||||||||
| */ | ||||||||||||||||||
| public CalendarCreateResponse create(CalendarCreateRequest request) { | ||||||||||||||||||
| if (request.getStartDate() == null || request.getEndDate() == null) { | ||||||||||||||||||
| throw new CalendarException(CalendarErrorCode.DATE_CANNOT_NULL); | ||||||||||||||||||
|
|
@@ -77,14 +101,46 @@ public CalendarCreateResponse create(CalendarCreateRequest request) { | |||||||||||||||||
| .build(); | ||||||||||||||||||
|
|
||||||||||||||||||
| CalendarEntity savedEntity = calendarRepository.save(entity); | ||||||||||||||||||
| evictMonthBetween(savedEntity.getStartDate(), savedEntity.getEndDate()); | ||||||||||||||||||
|
|
||||||||||||||||||
| return CalendarCreateResponse.of(savedEntity); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * 켈린더 이벤트 삭제(SoftDelete) | ||||||||||||||||||
| * + 기존 엔티티의 시간을 기준으로 캐시를 무효화 | ||||||||||||||||||
| * @param id | ||||||||||||||||||
| */ | ||||||||||||||||||
| public void delete(String id) { | ||||||||||||||||||
| ObjectId objectId = ObjectIdUtil.toObjectId(id); | ||||||||||||||||||
| CalendarEntity calendar = calendarRepository.findByIdAndNotDeleted(objectId) | ||||||||||||||||||
| .orElseThrow(() -> new CalendarException(CalendarErrorCode.CALENDAR_EVENT_NOT_FOUND)); | ||||||||||||||||||
|
|
||||||||||||||||||
| calendar.delete(); | ||||||||||||||||||
| calendarRepository.save(calendar); | ||||||||||||||||||
|
|
||||||||||||||||||
| LocalDate start = calendar.getStartDate(); | ||||||||||||||||||
| LocalDate end = calendar.getEndDate(); | ||||||||||||||||||
| evictMonthBetween(start, end); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * 캐시 무효화 | ||||||||||||||||||
| * @param startDate | ||||||||||||||||||
| * @param endDate | ||||||||||||||||||
| */ | ||||||||||||||||||
| private void evictMonthBetween(LocalDate startDate, LocalDate endDate) { | ||||||||||||||||||
| if (startDate == null || endDate == null) return; | ||||||||||||||||||
| Cache cache = cacheManager.getCache(CACHE_NAME); | ||||||||||||||||||
| if (cache == null) return; | ||||||||||||||||||
|
|
||||||||||||||||||
| YearMonth from = YearMonth.from(startDate); | ||||||||||||||||||
| YearMonth to = YearMonth.from(endDate); | ||||||||||||||||||
|
|
||||||||||||||||||
| for (YearMonth ym = from; !ym.isAfter(to); ym = ym.plusMonths(1)) { | ||||||||||||||||||
| String key = "month:" + ym; | ||||||||||||||||||
| log.info("Evicting month {} from {} to {}", ym, from, to); | ||||||||||||||||||
| cache.evictIfPresent(key); | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Cacheable에서 사용하는 캐시 이름은 컴파일 타임 상수여야 합니다
현재
private final String CACHE_NAME는 주석 위치에서 컴파일 타임 상수가 아니라서 어노테이션 인자로 사용할 수 없어 컴파일 오류가 납니다.static final로 승격하거나 문자열 리터럴을 직접 사용하세요.📝 Committable suggestion
🤖 Prompt for AI Agents