YS's develop story

Spring Boot + Redis 캐시 적용 가이드 (@Cacheable 활용) 본문

Spring

Spring Boot + Redis 캐시 적용 가이드 (@Cacheable 활용)

Yusang 2024. 5. 30. 15:24

 

@SpringBootApplication
@EnableJpaAuditing
@EnableCaching //캐싱 기능을 활성화 하겠다는 어노테이션 -> redis활용을 위해 추가
public class SpringJavaApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringJavaApplication.class, args);
    }

}

 

@EnableCaching 어노테이션 추가

캐싱 기능을 활성화하겠다는 어노테이션입니다.

 

 

 

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

 

관련 gradle 추가

 

 

//redis config 설정 클래스들
@Configuration
public class RedisConfig {

    @Bean
    LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory());
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .entryTtl(Duration.ofMinutes(10L)); // 캐시의 TTL 설정 (10분)

        return RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(factory)
                .cacheDefaults(cacheConfig)
                .build();
    }

    //Redis는 LocalDateTime과 같은 일부 타입을 직렬화할 수 없기 때문에
    //Redis의 직렬화 도구인 GenericJackson2JsonRedisSerializer를 구성하여 Java 8의 시간 관련 타입을 처리할 수 있도록 함.
    @Bean
    public RedisSerializer<Object> redisSerializer() {
        return new GenericJackson2JsonRedisSerializer(new ObjectMapper().registerModule(new JavaTimeModule()));
    }
}

 

Redis config 추가

 

 

 

@Cacheable(cacheNames = "allProducts", key = "#pageable")
//상품 전체 조회
public List<GetProductsResponseDTO> getAllProducts(Pageable pageable) {
    Page<Product> productList = productRepository.findAll(pageable);

    List<GetProductsResponseDTO> getProductsResponseDTOList = productList.getContent().stream()
            .map(product -> {
                List<String> imageList = product.getProductImages().stream()
                        .map(ProductImages::getImageUrl)
                        .collect(Collectors.toList());
                return GetProductsResponseDTO.fromEntity(product, imageList);
            })
            .collect(Collectors.toList());

    return getProductsResponseDTOList;
}

 

@Cacheable 어노테이션을 메서드에 추가하면 해당 메서드의 리턴값이 캐시에 저장되며,

이후 동일한 매개변수로 호출될 때 캐시된 값을 반환합니다.

 

Spring의 @Cacheable 어노테이션은 스프링 프레임워크가 제공하는 캐싱 추상화 기능을 이용합니다.

이 기능을 사용하면 메서드의 결과를 캐시에 저장하고, 동일한 매개변수로 메서드가 호출될 때 캐시된 결과를 반환할 수 있습니다.

 

어노테이션 하나로 이렇게 동작할 수 있는 이유는 Spring이 프록시를 사용하여 메서드 호출을 감싸고,

각 메서드 호출이 발생할 때 캐시 관련 로직을 수행하는 프록시를 생성하기 때문입니다.

 

이 프록시는 메서드 호출을 처리하기 전에 캐시에 저장된 결과를 확인하고,

저장된 결과가 있는 경우에는 캐시 된 결과를 반환합니다.

따라서 개발자는 어노테이션만으로 간편하게 캐싱 기능을 활성화할 수 있습니다.

 

이를 통해 반복적으로 동일한 요청이 들어올 때마다 매번 비싼 연산을 수행하지 않고,

캐시된 결과를 효율적으로 반환함으로써 성능을 향상할 수 있습니다.

 

 

 

@Cacheable 어노테이션을 사용하여 캐싱을 적용하면 Spring은 해당 메서드의 호출을 감싸는 프록시 객체를 생성합니다. 이 프록시 객체는 메서드가 호출될 때 캐시 관련 로직을 수행하고, 캐시에 저장된 결과를 반환하거나 메서드의 실행 결과를 캐시에 저장합니다.

프록시 객체는 다음과 같은 과정을 거쳐 생성됩니다:

  1. @Cacheable 어노테이션이 적용된 메서드가 호출됩니다.
  2. Spring AOP가 이를 감싸는 프록시 객체를 생성합니다.
  3. 프록시 객체는 메서드 호출 전에 캐시에 저장된 결과를 확인하고, 저장된 결과가 있는 경우 캐시된 결과를 반환합니다.
  4. 저장된 결과가 없는 경우 메서드를 실행하고, 실행 결과를 캐시에 저장한 후 반환합니다.

 

이러한 프록시 패턴을 통해 개발자는 @Cacheable 어노테이션만으로 간편하게 캐싱 기능을 활성화할 수 있습니다.

프록시 객체가 캐싱 관련 로직을 처리하므로 개발자는 비즈니스 로직에만 집중할 수 있습니다.

 

 

 

 

 

["java.util.ArrayList",[{"@class":"cohttp://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO","productId":12,"name":"도시락","price":3000,"description":"고기도시락","inventory":10,"productImages":["java.util.ArrayList",[]],"productType":"SNACK"},{"@class":"cohttp://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO","productId":11,"name":"주먹밥","price":2000,"description":"먹기편한 주먹밥","inventory":10,"productImages":["java.util.ArrayList",[]],"productType":"SNACK"},{"@class":"cohttp://m.practice.spring_java.domain.products.dto.response.GetProductsResponseDTO","productId":10,"name":"아메리카노","price":1500,"description":"시원한 아메리카노","inventory":3,"productImages":["java.util.ArrayList",[]],"productType":"BEVERAGE"},

.....

 

실제로 해당 상품 조회 api를 실행한 후, Redis를 확인해 본다면

그 결괏값들이 redis에 저장되어 있는 것을 확인할 수 있습니다.

 

 

@Cacheable

메서드에 붙여서 사용할 것을 권장하고, 캐시를 저장 또는 조회할 때 사용 합니다.

메서드에서 조회할 캐시 데이터가 캐시 서버에 있는지 확인하고, 있으면 반환하고 없으면 새로 저장한 후 반환

@CachePut

메서드에 붙여서 사용할 것을 권장하고, 캐시를 저장 또는 수정하는 목적으로만 사용합니다.

즉, 메서드에서 조회하고자 하는 데이터는 DB에서 직접 꺼내서 전달하고, 그 결과를 캐시 서버에 반영하기만 함

@CacheEvict

캐시 제거를 위해서 사용합니다.

역시 메서드에 붙여서 사용하는 것이 바람직하고, 삭제 기능을 갖고 있는 메서드에 붙여서 사용할 수 있습니다.

Comments