diff --git a/build.gradle b/build.gradle index a3d6214..b943a5f 100644 --- a/build.gradle +++ b/build.gradle @@ -73,6 +73,9 @@ dependencies { // firebase implementation 'com.google.firebase:firebase-admin:9.1.0' + + // jackson LocalDateTime 직렬화 + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' } tasks.named('test') { diff --git a/src/main/java/org/withtime/be/withtimebe/WithTimeBeApplication.java b/src/main/java/org/withtime/be/withtimebe/WithTimeBeApplication.java index 76f82d1..6efb8e9 100644 --- a/src/main/java/org/withtime/be/withtimebe/WithTimeBeApplication.java +++ b/src/main/java/org/withtime/be/withtimebe/WithTimeBeApplication.java @@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.FilterType; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @@ -13,6 +14,7 @@ import jakarta.annotation.PostConstruct; +@EnableCaching @EnableScheduling @EnableJpaAuditing @EnableJpaRepositories( diff --git a/src/main/java/org/withtime/be/withtimebe/domain/log/placecategorylog/scheduler/PlaceCategoryLogScheduler.java b/src/main/java/org/withtime/be/withtimebe/domain/log/placecategorylog/scheduler/PlaceCategoryLogScheduler.java index 2c63abe..641cd7c 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/log/placecategorylog/scheduler/PlaceCategoryLogScheduler.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/log/placecategorylog/scheduler/PlaceCategoryLogScheduler.java @@ -12,6 +12,7 @@ import java.util.stream.Collectors; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ZSetOperations; import org.springframework.scheduling.annotation.Scheduled; @@ -36,8 +37,16 @@ public class PlaceCategoryLogScheduler { private final PlaceCategoryRepository placeCategoryRepository; @Scheduled(cron = "${scheduler.logs.place-category.sync-cron}") // 매 5분마다 + @CacheEvict( + value = "place-category-log", + key = "'weekly:' + T(java.time.LocalDate).now().getYear() + '-' + T(java.time.temporal.WeekFields).ISO.weekOfYear().getFrom(T(java.time.LocalDate).now())", + cacheManager = "redisCacheManager", + beforeInvocation = false + ) public void syncPlaceCategoryLogsToDB() { + log.info("[PlaceCategoryLogScheduler] 동기화 스케쥴러 동작"); + // 현재 날짜 및 레디스 키 생성 LocalDate now = LocalDate.from(LocalDateTime.now().minusMinutes(1)); String formattedDate = now.format(DateTimeFormatter.ofPattern("yyyyMMdd")); diff --git a/src/main/java/org/withtime/be/withtimebe/domain/log/placecategorylog/service/query/PlaceCategoryLogQueryServiceImpl.java b/src/main/java/org/withtime/be/withtimebe/domain/log/placecategorylog/service/query/PlaceCategoryLogQueryServiceImpl.java index 88a4f4b..e596b68 100644 --- a/src/main/java/org/withtime/be/withtimebe/domain/log/placecategorylog/service/query/PlaceCategoryLogQueryServiceImpl.java +++ b/src/main/java/org/withtime/be/withtimebe/domain/log/placecategorylog/service/query/PlaceCategoryLogQueryServiceImpl.java @@ -4,6 +4,7 @@ import java.time.LocalDate; import java.util.List; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.withtime.be.withtimebe.domain.log.placecategorylog.model.PlaceCategoryLog; @@ -19,6 +20,11 @@ public class PlaceCategoryLogQueryServiceImpl implements PlaceCategoryLogQuerySe private final PlaceCategoryLogRepository placeCategoryLogRepository; @Override + @Cacheable( + value = "place-category-log", + key = "'weekly:' + T(java.time.LocalDate).now().getYear() + '-' + T(java.time.temporal.WeekFields).ISO.weekOfYear().getFrom(T(java.time.LocalDate).now())", + cacheManager = "redisCacheManager" + ) public List findWeeklyPlaceCategoryLogList() { LocalDate now = LocalDate.now(); diff --git a/src/main/java/org/withtime/be/withtimebe/global/config/MapperConfig.java b/src/main/java/org/withtime/be/withtimebe/global/config/MapperConfig.java new file mode 100644 index 0000000..e892e83 --- /dev/null +++ b/src/main/java/org/withtime/be/withtimebe/global/config/MapperConfig.java @@ -0,0 +1,28 @@ +package org.withtime.be.withtimebe.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +@Configuration +public class MapperConfig { + + @Bean + public ObjectMapper objectMapper() { + + PolymorphicTypeValidator typeValidator = BasicPolymorphicTypeValidator.builder() + .allowIfSubType(Object.class) + .build(); + + return new ObjectMapper() + .enable(SerializationFeature.INDENT_OUTPUT) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .registerModule(new JavaTimeModule()) // LocalDateTime 지원 모듈 추가 + .activateDefaultTyping(typeValidator, ObjectMapper.DefaultTyping.NON_FINAL); // 클래스 정보를 포함하여 직렬/역직렬화 하도록 설정 + } +} diff --git a/src/main/java/org/withtime/be/withtimebe/global/config/RedisConfig.java b/src/main/java/org/withtime/be/withtimebe/global/config/RedisConfig.java index 4f8c734..14cb7fa 100644 --- a/src/main/java/org/withtime/be/withtimebe/global/config/RedisConfig.java +++ b/src/main/java/org/withtime/be/withtimebe/global/config/RedisConfig.java @@ -1,19 +1,35 @@ package org.withtime.be.withtimebe.global.config; +import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair.*; + +import java.time.Duration; + import lombok.RequiredArgsConstructor; 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.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; import org.withtime.be.withtimebe.global.data.RedisConfigData; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator; +import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + @Configuration @RequiredArgsConstructor public class RedisConfig { private final RedisConfigData redisConfigData; + private final ObjectMapper objectMapper; @Bean RedisConnectionFactory redisConnectionFactory() { @@ -28,4 +44,15 @@ RedisTemplate tokenRedisTemplate(RedisConnectionFactory redisCon redisTemplate.setValueSerializer(RedisSerializer.java()); return redisTemplate; } + + @Bean + public RedisCacheManager redisCacheManager() { + RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() + .serializeKeysWith(fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper))) + .entryTtl(Duration.ofDays(1L)); + + return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory()) + .cacheDefaults(redisCacheConfiguration).build(); + } }