diff --git a/build.gradle b/build.gradle index cc45f60..6034a69 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,9 @@ dependencies { //AWS S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + //Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' } tasks.named('test') { diff --git a/src/main/java/swyp/team5/greening/common/config/RedisConfig.java b/src/main/java/swyp/team5/greening/common/config/RedisConfig.java new file mode 100644 index 0000000..a76e9fa --- /dev/null +++ b/src/main/java/swyp/team5/greening/common/config/RedisConfig.java @@ -0,0 +1,64 @@ +package swyp.team5.greening.common.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.time.Duration; +import org.springframework.beans.factory.annotation.Value; +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.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@EnableCaching +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public LettuceConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port)); + } + + //캐시 설정 + @Bean + public CacheManager postCacheManager(RedisConnectionFactory redisConnectionFactory) { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration + .defaultCacheConfig() + // Redis에 Key를 저장할 때 String으로 직렬화(변환)해서 저장 + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer())) + // Redis에 Value를 저장할 때 Json으로 직렬화(변환)해서 저장 + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new GenericJackson2JsonRedisSerializer(objectMapper) + ) + ) + // 데이터의 만료기간(TTL) 설정 + .entryTtl(Duration.ofMinutes(15L)); + + return RedisCacheManager + .RedisCacheManagerBuilder + .fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(redisCacheConfiguration) + .build(); + } +} diff --git a/src/main/java/swyp/team5/greening/post/service/PostQueryService.java b/src/main/java/swyp/team5/greening/post/service/PostQueryService.java index b702763..be43ad7 100644 --- a/src/main/java/swyp/team5/greening/post/service/PostQueryService.java +++ b/src/main/java/swyp/team5/greening/post/service/PostQueryService.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @@ -43,6 +44,7 @@ public FindPostResponseDto findPost(Long postId, Long userId) { } // 홈 화면 게시글 + @Cacheable(cacheNames = "getHomePosts", key = "'homePost'", cacheManager = "postCacheManager") @Transactional(readOnly = true) public List findLatestPostByCategory() { return Stream.of(1L, 2L, 3L) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 78a64a4..b56fe21 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,9 @@ spring: + data: + redis: + host: ${REDIS_SERVER} + port: 6379 + datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: ${DB_URL} @@ -22,6 +27,10 @@ spring: # 전체 요청의 최대 크기 max-request-size: 20MB +logging: + level: + org.springframework.cache: trace # Redis 사용에 대한 로그가 조회되도록 설정 + jwt: access-expiry-time: 1440 client-secret: ${JWT_SECRET} diff --git a/src/test/java/swyp/team5/greening/post/controller/PostControllerTest.java b/src/test/java/swyp/team5/greening/post/controller/PostControllerTest.java index 4f2cd9e..6585c2e 100644 --- a/src/test/java/swyp/team5/greening/post/controller/PostControllerTest.java +++ b/src/test/java/swyp/team5/greening/post/controller/PostControllerTest.java @@ -251,18 +251,6 @@ void getLatestPosts() throws Exception { .andExpect(jsonPath("$.data[1].userId").value(anotherUser.getId())); } - @Test - @DisplayName("로그인 한 유저에 따라 좋아요 여부가 올바르게 나타난다.") - void getLatestPosts2() throws Exception { - mockMvc.perform(get("/api/posts/home") - .header(HttpHeaders.AUTHORIZATION, accessToken)) - .andExpect(jsonPath("$.data.size()").value(2)) - .andExpect(jsonPath("$.data[0].postId").value(post.getId())) - .andExpect(jsonPath("$.data[0].userId").value(loginUser.getId())) - .andExpect(jsonPath("$.data[1].postId").value(anotherPost.getId())) - .andExpect(jsonPath("$.data[1].userId").value(anotherUser.getId())); - } - @Test @DisplayName("1번 카테고리 글이 잘 나타난다.") void getPostsByCategory1() throws Exception { diff --git a/src/test/java/swyp/team5/greening/support/TestContainerSupport.java b/src/test/java/swyp/team5/greening/support/TestContainerSupport.java index a7bef8f..2375f29 100644 --- a/src/test/java/swyp/team5/greening/support/TestContainerSupport.java +++ b/src/test/java/swyp/team5/greening/support/TestContainerSupport.java @@ -2,6 +2,7 @@ import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.JdbcDatabaseContainer; import org.testcontainers.containers.MySQLContainer; import org.testcontainers.utility.DockerImageName; @@ -9,18 +10,24 @@ public abstract class TestContainerSupport { private static final String MYSQL_IMAGE = "mysql:8.0"; + private static final String REDIS_IMAGE = "redis:latest"; + private static final int REDIS_PORT = 6379; private static final int MYSQL_PORT = 3306; - + private static final GenericContainer REDIS; private static final JdbcDatabaseContainer MYSQL; static { + REDIS = new GenericContainer(DockerImageName.parse(REDIS_IMAGE)) + .withExposedPorts(REDIS_PORT) + .withReuse(true); MYSQL = new MySQLContainer<>(DockerImageName.parse(MYSQL_IMAGE)) .withExposedPorts(MYSQL_PORT) .withReuse(true); + REDIS.start(); MYSQL.start(); } @@ -31,6 +38,8 @@ public static void overrideProps(DynamicPropertyRegistry registry) { registry.add("spring.datasource.username", MYSQL::getUsername); registry.add("spring.datasource.password", MYSQL::getPassword); + registry.add("spring.data.redis.host", REDIS::getHost); + registry.add("spring.data.redis.port", () -> String.valueOf(REDIS.getMappedPort(REDIS_PORT))); } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index b75f182..2110853 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -1,4 +1,9 @@ spring: + data: + redis: + host: localhost + port: 6379 + jpa: hibernate: ddl-auto: create