diff --git a/com.sparta.cupeed.hub/build.gradle b/com.sparta.cupeed.hub/build.gradle index 78baefe..44ddef5 100644 --- a/com.sparta.cupeed.hub/build.gradle +++ b/com.sparta.cupeed.hub/build.gradle @@ -48,6 +48,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'com.auth0:java-jwt:4.4.0' + // redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + // LocalDate type 직렬화 + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/com.sparta.cupeed.hub/docker-compose.yml b/com.sparta.cupeed.hub/docker-compose.yml index bff2cbb..9384dc2 100644 --- a/com.sparta.cupeed.hub/docker-compose.yml +++ b/com.sparta.cupeed.hub/docker-compose.yml @@ -18,6 +18,18 @@ services: retries: 5 restart: unless-stopped + # Redis 캐시 + redis: + image: redis:alpine + container_name: cupeed-hub-redis + ports: + - "6379:6379" + volumes: + - redis_data:/data + restart: unless-stopped + volumes: postgres_data: driver: local + redis_data: + driver: local diff --git a/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/application/HubService.java b/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/application/HubService.java index fb9ab33..248aa9e 100644 --- a/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/application/HubService.java +++ b/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/application/HubService.java @@ -4,6 +4,10 @@ import java.util.UUID; import java.util.stream.Collectors; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.CachePut; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,9 +21,11 @@ import com.sparta.cupeed.hub.presentation.HubInternalResponseDtoV1; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; // @Transactional: 애플리케이션 서비스에서 트랜잭션을 관리합니다. // 이 서비스는 도메인 객체(Hub)를 로드하고, 도메인의 비즈니스 메서드를 호출하며, 결과를 저장소에 저장하는 '조율자' 역할을 합니다. +@Slf4j @Service @RequiredArgsConstructor public class HubService { @@ -29,6 +35,7 @@ public class HubService { // 1. 허브 생성 (POST /v1/hubs) @Transactional + @CacheEvict(value = "CacheHubs", allEntries = true) public HubResponseDto createHub(CreateHubCommand command) { // ⭐ 주소를 이용해 위도/경도 조회 ⭐ Coordinates coordinates = geocodingService.getCoordinatesFromAddress(command.getAddress()); @@ -50,6 +57,7 @@ public HubResponseDto createHub(CreateHubCommand command) { // 2. 허브 단건 조회 (GET /v1/hubs/{hubId}) @Transactional(readOnly = true) + @Cacheable(value = "CacheHub", key = "#hubId") public HubResponseDto getHubById(UUID hubId) { Hub hub = hubRepository.findById(hubId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않은 ID입니다." + hubId)); @@ -58,7 +66,9 @@ public HubResponseDto getHubById(UUID hubId) { // 3. 허브 목록 조회 (GET /v1/hubs) @Transactional(readOnly = true) + @Cacheable("CacheHubs") public List getAllHubs() { + log.info("허브 목록 조회"); return hubRepository.findAll().stream() .map(HubResponseDto::mapToResponse) .collect(Collectors.toList()); @@ -66,6 +76,11 @@ public List getAllHubs() { // 4. 허브 수정 (PUT /v1/hubs/{hubId}) @Transactional + @CachePut(value = "CacheHub", key = "#hubId") + @Caching(evict = { + @CacheEvict(value = "CacheHub", key = "#hubId"), // 단건 캐쉬 삭제 + @CacheEvict(value = "CacheHubs", allEntries = true) // 목록 캐쉬 삭제 + }) public HubResponseDto updateHub(UUID hubId, UpdateHubCommand command) { // 1. Repository를 통해 Hub 애그리거트 루트를 로드 Hub hub = hubRepository.findById(hubId) @@ -85,6 +100,10 @@ public HubResponseDto updateHub(UUID hubId, UpdateHubCommand command) { // 5. 허브 삭제 (DELETE /v1/hubs/{hubId}) @Transactional + @Caching(evict = { + @CacheEvict(value = "CacheHub", key = "#hubId"), + @CacheEvict(value = "CacheHubs", allEntries = true) + }) public void deleteHub(UUID hubId) { Hub hub = hubRepository.findById(hubId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않은 ID입니다." + hubId)); @@ -99,6 +118,7 @@ public HubInternalResponseDtoV1 getInternalHubByName(String hubName) { return HubInternalResponseDtoV1.of(hub); } + @Cacheable(value = "CacheHub", key = "#name") public HubResponseDto getHubByName(String name) { Hub hub = hubRepository.findByName(name) .orElseThrow(() -> new IllegalArgumentException("존재하지 않은 허브 이름입니다.")); diff --git a/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/application/dto/HubResponseDto.java b/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/application/dto/HubResponseDto.java index f8fe20d..3d3cef8 100644 --- a/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/application/dto/HubResponseDto.java +++ b/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/application/dto/HubResponseDto.java @@ -9,8 +9,10 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; @Getter +@Setter @Builder @NoArgsConstructor @AllArgsConstructor diff --git a/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/infrastructure/redis/CacheConfig.java b/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/infrastructure/redis/CacheConfig.java new file mode 100644 index 0000000..dcbdb41 --- /dev/null +++ b/com.sparta.cupeed.hub/src/main/java/com/sparta/cupeed/hub/infrastructure/redis/CacheConfig.java @@ -0,0 +1,66 @@ +package com.sparta.cupeed.hub.infrastructure.redis; + +// 나머지 import 문들 + +import java.time.Duration; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.cache.CacheKeyPrefix; +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 com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +@Configuration +@EnableCaching +public class CacheConfig { + + @Bean + // CacheManager로 진행해도 정상 동작 + public RedisCacheManager cacheManager( + RedisConnectionFactory redisConnectionFactory + ) { + + ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + objectMapper.activateDefaultTyping( + objectMapper.getPolymorphicTypeValidator(), + ObjectMapper.DefaultTyping.NON_FINAL + // JsonTypeInfo.As.PROPERTY + ); + + GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper); + + // 설정 구성을 먼저 진행한다. + // Redis를 이용해서 Spring Cache를 사용할 때 + // Redis 관련 설정을 모아두는 클래스 + RedisCacheConfiguration configuration = RedisCacheConfiguration + .defaultCacheConfig() + // null을 캐싱 할것인지 + .disableCachingNullValues() + // 기본 캐시 유지 시간 (Time To Live) + .entryTtl(Duration.ofSeconds(120)) + // 캐시를 구분하는 접두사 설정 + .computePrefixWith(CacheKeyPrefix.simple()) + // 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지 + // .serializeValuesWith(SerializationPair.fromSerializer(RedisSerializer.json())) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) + .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)); + ; + + return RedisCacheManager + .builder(redisConnectionFactory) + .cacheDefaults(configuration) + .build(); + } +} \ No newline at end of file diff --git a/com.sparta.cupeed.hub/src/main/resources/application.yaml b/com.sparta.cupeed.hub/src/main/resources/application.yaml index 6b2695c..df93374 100644 --- a/com.sparta.cupeed.hub/src/main/resources/application.yaml +++ b/com.sparta.cupeed.hub/src/main/resources/application.yaml @@ -25,6 +25,12 @@ spring: init: mode: always + # 3. Redis 설정 + data: + redis: + host: localhost + port: 6379 + server: port: 20040