본문 바로가기
Study/DB

Redis - Spring Boot Cache 적용하기

by 코드콩 2024. 9. 12.
728x90
반응형

📍 Spring Boot 프로젝트에서 Spring Data Redis를 설정하고 사용하는 방법에 대해 알아보겠습니다.

Spring Data Redis 란?

Spring Data Redis는 Redis와 스프링 애플리케이션을 통합하여 Redis 데이터 저장소와 쉽게 상호작용할 수 있도록 지원하는 스프링 프로젝트입니다.

 

1.  의존성 추가

Spring Boot 프로젝트에서 Spring Data Redis를 사용하려면 spring-boot-starter-data-redis 의존성을 추가해야 합니다.

 

Redis 클라이언트인 LettuceJedis를 사용할 수 있는데 Spring Boot에서 Redis 클라이언트로 기본적으로 사용되는 라이브러리는 Lettuce입니다. Lettuce는 비동기 및 동시성을 지원하고, 리액티브 프로그래밍에도 적합하기 때문에 Spring의 기본 선택입니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

2. Redis 연결 설정

Spring Boot는 기본적으로 application.properties 또는 application.yml 파일을 통해 Redis 설정을 할 수 있습니다.

spring:
  redis:
    host: 127.0.0.1
    port: 6379

 

3. Redis Configuration

Redis 연결을 위한 Configuration 클래스를 작성합니다

import com.fasterxml.jackson.databind.DeserializationFeature;
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;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
public class RedisConfig {

    @Bean
    RedisTemplate<String, Object> objectRedisTemplate(RedisConnectionFactory connectionFactory) {
        // PolymorphicTypeValidator는 Jackson이 다형성 타입을 안전하게 직렬화/역직렬화할 수 있도록 합니다.
        // allowIfSubType(Object.class)는 Object 클래스의 모든 하위 타입을 허용합니다.
        PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
                .builder()
                .allowIfSubType(Object.class)
                .build();
                
        // ObjectMapper 구성
        var objectMapper = new ObjectMapper()
        	// JSON에 알 수 없는 속성이 있어도 역직렬화 실패를 방지합니다.
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                // Java 8 날짜/시간 타입(예: LocalDate, LocalDateTime)을 지원합니다.
                .registerModule(new JavaTimeModule())
                // 다형성 타입 처리를 활성화합니다. 이는 객체의 실제 타입 정보를 JSON에 포함시킵니다.
                .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
                // 날짜를 타임스탬프 대신 ISO-8601 형식의 문자열로 직렬화합니다.
                .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
		
        // RedisTemplate을 구성
        var template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(connectionFactory); // 주입된 Redis 연결 팩토리를 설정합니다.
        template.setKeySerializer(new StringRedisSerializer()); // 키를 문자열로 직렬화합니다.
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)); // 값을 JSON으로 직렬화합니다. 커스텀 ObjectMapper를 사용하여 위에서 구성한 모든 설정을 적용합니다.
        return template;
    }
    
}

 

 

4. RedisRepository 사용

RedisRepository 인터페이스를 사용하여 더 간단하게 CRUD 작업을 수행할 수 있습니다. 이를 위해서는 저장할 객체에 반드시 @RedisHash 어노테이션을 추가해야 합니다.

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

import java.time.LocalDateTime;


@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@RedisHash("Person", timeToLive = 30L)
public class RedisPerson {
    
    @Id
    private String id;
    private String name;
    private int age;
    @Indexed
    private String email;
    private LocalDateTime createdDt;
    private LocalDateTime updatedDt;
}

 

이제 CrudRepository 인터페이스를 확장한 PersonRepository를 통해 데이터를 저장하고 조회할 수 있습니다.

import org.springframework.data.repository.CrudRepository;

public interface RedisPersonRepository extends CrudRepository<Person, String> {
}

 

5. 캐싱 적용하기

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PersonService {

	private final PersonRepository personRepository;
	private final RedisPersonRepository redisPersonRepository;
    
    	// 1. 캐싱 방식: redisPersonRepository Redis 전용 저장소를 통해 직접 캐시된 데이터를 확인하고 없을 경우 데이터베이스에서 조회한 후 캐시에 저장하는 방식을 사용합니다.
	// 2. 캐시 조회: 먼저 Redis에서 findById(id)로 캐시된 데이터를 조회합니다.
	// 3. 캐시 미스 시 데이터베이스 조회: 캐시에 데이터가 없으면 데이터베이스(personRepository)에서 해당 사용자를 조회한 후, 그 결과를 Redis에 저장합니다.

        public RedisPerson getPerson(final Long id) {
            var cachedPerson = redisPersonRepository(id).orElseGet(() -> {
                Person person = personRepository(id).orElseThrow();
                return redisPersonRepository.save(RedisPerson.builder()
                        .id(person.getId())
                        .name(person.getName())
                        .email(person.getEmail())
                        .age(person.getAge())
                        .createdDt(person.getCreatedDt())
                        .updatedDt(person.getUpdatedDt())
                        .build());
            });
            return cachedPerson;
	}
}

 

Spring @Cacheable 사용하면 간단하게 코드를 적용할 수 있습니다.

import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UserService {

	private final PersonRepository personRepository;
		
        @Cacheable(cacheNames = CACHE1, key = "'person:' + #id")
        public User getPerson(final Long id) {
            return personRepository.findById(id).orElseThrow();
        }
}
import com.fasterxml.jackson.databind.DeserializationFeature;
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;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
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.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.List;

@EnableCaching
@Configuration
public class CacheConfig {
    public static final String CACHE1 = "cache1";
    public static final String CACHE2 = "cache2";

    @AllArgsConstructor
    @Getter
    public static class CacheProperty {
        private String name;
        private Integer ttl;
    }

    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
                .builder()
                .allowIfSubType(Object.class)
                .build();

        var objectMapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                .registerModule(new JavaTimeModule())
                .activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL)
                .disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
		
        // 캐시 속성 정의: properties 리스트는 두 개의 캐시 속성을 정의합니다. 
        // CACHE1은 300초 TTL을 가지며, CACHE2는 30초 TTL을 가집니다.
        List<CacheProperty> properties = List.of(
                new CacheProperty(CACHE1, 300),
                new CacheProperty(CACHE2, 30)
        );

        return (builder -> {
            properties.forEach(i -> {
                builder.withCacheConfiguration(i.getName(), RedisCacheConfiguration
                        .defaultCacheConfig()
                        .disableCachingNullValues()
                        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)))
                        .entryTtl(Duration.ofSeconds(i.getTtl())));
            });
        });
    }
}

 

간단하게 Spring Boot Cache 적용하는 방법을 정리했습니다.

Spring Data Redis는 Spring 애플리케이션에서 Redis를 쉽고 효율적으로 사용할 수 있게 해줍니다. 캐싱, 데이터 저장, 메시징 등 다양한 용도로 활용할 수 있어, 애플리케이션의 성능과 확장성을 크게 향상시킬 수 있습니다. 

728x90
반응형