JAVA/프로젝트

Spring Boot 에 Redis 설정, 캐시 적용, 조회

whyHbr 2024. 11. 17. 16:56
728x90
반응형

build.gradle에 추가

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

application.properties에 추가

spring.cache.type=redis
spring.data.redis.host=
spring.data.redis.port=
spring.data.redis.password=
spring.data.redis.repositories.enabled=false
# spring.cache.redis.time-to-live=600000
expire.defaultTime=36288000

 

 

mainApplication 에 추가

@EnableCaching

 

 

RedisConfig class 를 만들어준다. 

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.beans.factory.annotation.Value;
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;

import java.time.Duration;

@Configuration
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String redisHost;

    @Value("${spring.data.redis.port}")
    private int redisPort;

    @Value("${spring.data.redis.password}")
    private String redisPwd;

    @Value("${expire.defaultTime}")
    private long defaultExpireSecond;

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        //TimeStamp 를 설정하지 못하게 disable 설정
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        //날짜 인식 가능하게
        mapper.registerModules(new JavaTimeModule(), new Jdk8Module());
        return mapper;
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        //redis의 실제 연결 정보
        RedisStandaloneConfiguration redisStandaloneConfiguration
                = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setPort(redisPort);
        redisStandaloneConfiguration.setHostName(redisHost);
        redisStandaloneConfiguration.setPassword(redisPwd);
        //redisConnectionFactory 의 구현체. lettuce 가 비동기 동작으로 더 빠르다.
        LettuceConnectionFactory lettuceConnectionFactory =
                new LettuceConnectionFactory(redisStandaloneConfiguration);
        return lettuceConnectionFactory;
    }

    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory,
            //서버간 통신을 위한 직렬화. 역직렬화를 해주는 ObjectMapper
            ObjectMapper objectMapper) {
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                .disableCachingNullValues()
                .entryTtl(Duration.ofSeconds(defaultExpireSecond))
                //직렬화를 통해 통신하게 설정
                .serializeKeysWith(RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));

        return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(
                        redisConnectionFactory)
                .cacheDefaults(configuration).build();
    }
}

 

 

ServiceImpl 에 적용

 @Async
    @Cacheable(value = "getPosts",
            key = "'getPosts' + #request.getName() + #request.getCategoryId()",
            unless = "#result == null")
    @Override
    public List<PostDTO> getPosts(PostSearchRequest request) {
        List<PostDTO> dtos = null;
        try {
            dtos = postSearchMapper.selectPosts(request);
        } catch (RuntimeException e) {
            log.error("select 실패 {}", e
                    .getMessage());
            log.error(e);
        }
        return dtos;
    }
}

 

unless = "#result == null" 이 없다면 검색 단어가 없을 때 NPE 가 발생한다.

 

 @Async? :

비동기적 처리를 도와주는 어노테이션. 캐시가 존재할 때는 즉시 응답, 없을 때는 비동기적으로 데이터 조회한다. 

클라이언트의 요청 -> 캐시 확인 -> 캐시가 없다면 DB 조회해 비동기적으로 데이터 가져오고, 캐시에 저장.

메인 스레드는 데이터의 조회를 기다리지 않고 즉시 응답하고, 실제 데이터 조회 작업은 백그라운드에서 수행한다.

복잡한 로직이 있었다면 사용하기를 주저했겠지만, 간단한 검색 기능이라 속도 향상을 위해 사용해보았다. 

 

현재 게시글:

검색:

{
    "name": "update",
    "contents":"",
    "categoryId":2,
    "sortStatus":"NEWEST"
}

 

결과:

{
    "dtos": [
        {
            "id": 3,
            "name": "update post",
            "contents": "dsfd content",
            "views": 0,
            "categoryId": 2,
            "userId": 0,
            "sortStatus": null
        }
    ]
}

 

 

 

CMD:

docker exec -it redis redis-cli

docker exec -it [이름] redis-cli

KEYS *

결과 확인.

 

728x90