From 7b0822a999e864c6b814ec9aac0d980240303aa7 Mon Sep 17 00:00:00 2001 From: kkang_h00n Date: Sat, 12 Jul 2025 12:08:20 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Feat:=20Redis=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EB=B0=8F=20=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + .../greening/common/config/RedisConfig.java | 57 +++++++++++++++++++ src/main/resources/application.yml | 9 +++ src/test/resources/application.yml | 5 ++ 4 files changed, 74 insertions(+) create mode 100644 src/main/java/swyp/team5/greening/common/config/RedisConfig.java 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..0a8d24f --- /dev/null +++ b/src/main/java/swyp/team5/greening/common/config/RedisConfig.java @@ -0,0 +1,57 @@ +package swyp.team5.greening.common.config; + +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.Jackson2JsonRedisSerializer; +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) { + RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration + .defaultCacheConfig() + // Redis에 Key를 저장할 때 String으로 직렬화(변환)해서 저장 + .serializeKeysWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new StringRedisSerializer())) + // Redis에 Value를 저장할 때 Json으로 직렬화(변환)해서 저장 + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + new Jackson2JsonRedisSerializer(Object.class) + ) + ) + // 데이터의 만료기간(TTL) 설정 + .entryTtl(Duration.ofMinutes(1L)); + + return RedisCacheManager + .RedisCacheManagerBuilder + .fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(redisCacheConfiguration) + .build(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 78a64a4..274353c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,9 @@ spring: + data: + redis: + host: localhost + 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/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 From dff7c6f3e3877d352dece5dda1c9cc5400f57b2f Mon Sep 17 00:00:00 2001 From: kkang_h00n Date: Sat, 12 Jul 2025 21:53:21 +0900 Subject: [PATCH 2/4] =?UTF-8?q?Feat:=20=ED=99=88=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=BA=90=EC=8B=9C?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../team5/greening/common/config/RedisConfig.java | 13 ++++++++++--- .../greening/post/service/PostQueryService.java | 2 ++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/swyp/team5/greening/common/config/RedisConfig.java b/src/main/java/swyp/team5/greening/common/config/RedisConfig.java index 0a8d24f..a76e9fa 100644 --- a/src/main/java/swyp/team5/greening/common/config/RedisConfig.java +++ b/src/main/java/swyp/team5/greening/common/config/RedisConfig.java @@ -1,5 +1,8 @@ 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; @@ -11,7 +14,7 @@ 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.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -33,6 +36,10 @@ public LettuceConnectionFactory redisConnectionFactory() { //캐시 설정 @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으로 직렬화(변환)해서 저장 @@ -42,11 +49,11 @@ public CacheManager postCacheManager(RedisConnectionFactory redisConnectionFacto // Redis에 Value를 저장할 때 Json으로 직렬화(변환)해서 저장 .serializeValuesWith( RedisSerializationContext.SerializationPair.fromSerializer( - new Jackson2JsonRedisSerializer(Object.class) + new GenericJackson2JsonRedisSerializer(objectMapper) ) ) // 데이터의 만료기간(TTL) 설정 - .entryTtl(Duration.ofMinutes(1L)); + .entryTtl(Duration.ofMinutes(15L)); return RedisCacheManager .RedisCacheManagerBuilder 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) From bb275d4249c6d5887cd69f362696724a3ce4fb0f Mon Sep 17 00:00:00 2001 From: kkang_h00n Date: Sat, 12 Jul 2025 22:41:26 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Test:=20=ED=99=88=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EB=AC=BC=20=EC=A1=B0=ED=9A=8C=20API=20=EC=8A=A4=ED=8E=99=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../greening/post/controller/PostControllerTest.java | 12 ------------ .../team5/greening/support/TestContainerSupport.java | 11 ++++++++++- 2 files changed, 10 insertions(+), 13 deletions(-) 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))); } } From 1fc15d43c11d1399ff277fd110cd4ba78fc9f54f Mon Sep 17 00:00:00 2001 From: kkang_h00n Date: Mon, 14 Jul 2025 15:55:55 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Chore:=20=EB=A0=88=EB=94=94=EC=8A=A4=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20secret=20=ED=82=A4=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 274353c..b56fe21 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,7 +1,7 @@ spring: data: redis: - host: localhost + host: ${REDIS_SERVER} port: 6379 datasource: