Redis로 신상품 조회하기

sunny·2023년 9월 29일
0

배달의 민족 B마트라는 서비스를 클론하는 프로젝트에서 신상품을 조회하는 API를 만들었다.
처음에는 DB에서 createdAt이 2주 이내인 상품들만 조회하도록 하였다.

@Override
public List<Item> findNewItemsOrderBy(Long lastIdx, Long lastItemId, ItemSortType sortType,
    Pageable pageable) {
    OrderSpecifier orderSpecifier = createOrderSpecifier(sortType);
    Predicate predicate = item.createdAt.after(
        LocalDateTime.now().minus(NEW_PRODUCT_REFERENCE_TIME, ChronoUnit.WEEKS));

    return queryFactory
        .selectFrom(item)
        .leftJoin(item.reviews, review)
        .leftJoin(item.likeItems, likeItem)
        .leftJoin(item.orderItems, orderItem)
        .where(predicate)
        .groupBy(item)
        .having(
            getHavingCondition(lastIdx, lastItemId, sortType)
        )
        .orderBy(orderSpecifier, item.itemId.asc())
        .offset(pageable.getOffset())
        .limit(pageable.getPageSize())
        .fetch();
}

이후 2차 스프린트에서 Redis를 활용하여 신상품 조회를 하는 것으로 리팩토링하기로 했다!

Redis 설정하기

build.gradle

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

RedisConfig.java

@EnableCaching
@Configuration
public class RedisConfig {

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

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

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public RedisTemplate<String, ItemRedisDto> itemRedisDtoRedisTemplate() {
        RedisTemplate<String, ItemRedisDto> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(ItemRedisDto.class));
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}

내가 사용하고 싶은 타입을 설정하여 Bean으로 등록하면 된다!

Redis 사용하기!!!

Redis에 DTO로 올리고 싶어 만들어줬다.

public record ItemRedisDto(Long itemId, String name, int price, int discount,
                           @JsonSerialize(using = LocalDateTimeSerializer.class)
                           @JsonDeserialize(using = LocalDateTimeDeserializer.class)
                           LocalDateTime createdAt
) {

    public static ItemRedisDto from(final Item item) {
        return new ItemRedisDto(
            item.getItemId(), item.getName(), item.getPrice(), item.getDiscount(),
            item.getCreatedAt()
        );
    }
}
@Service
@RequiredArgsConstructor
public class ItemCacheService {

    private final RedisTemplate<String, ItemRedisDto> redisTemplate;
    private static final String NEW_PRODUCTS_KEY = "new_products";
	
    // 아이템을 등록할 때 해당 함수를 호출하여 Redis에도 생성한다!
    public void saveNewItem(final ItemRedisDto itemRedisDto) {
        redisTemplate.opsForList().rightPush(NEW_PRODUCTS_KEY, itemRedisDto);
    }
    
	// Redis에서 new_product라는 키로 등록된 데이터를 조회한다.
    public List<ItemRedisDto> getNewItems() {
        ListOperations<String, ItemRedisDto> listOperations = redisTemplate.opsForList();
        Long itemCount = listOperations.size(NEW_PRODUCTS_KEY);
        if (itemCount == null || itemCount == 0) {
            return null;
        }

        return listOperations.range(NEW_PRODUCTS_KEY, 0, -1);
    }

	// 자정마다 생성된지 2주가 지난 상품을 Redis에서 삭제한다.
    @Scheduled(cron = "0 0 * * * *")
    public void deleteOldProducts() {
        LocalDateTime twoWeeksAgo = LocalDateTime.now().minus(2, ChronoUnit.WEEKS);

        ListOperations<String, ItemRedisDto> items = redisTemplate.opsForList();
        Long itemCount = items.size(NEW_PRODUCTS_KEY);

        if (itemCount == null || itemCount == 0) {
            return;
        }

        for (int i = 0; i < itemCount; i++) {
            ItemRedisDto item = items.index(NEW_PRODUCTS_KEY, i);
            if (item != null && item.createdAt().isBefore(twoWeeksAgo)) {
                items.remove(NEW_PRODUCTS_KEY, 1, item);
                i--;
            }
        }
    }
}

0개의 댓글