1. 라이브러리 설정하기
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
2. RedisConfig 생성하기
@Configuration
@EnableCaching
public class RedisConfig {
@Bean // RedisTemplate<String, Object>를 빈으로 등록하여 애플리케이션에서 사용할 수 있게 함
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer()); // key 값을 String으로 저장
template.setValueSerializer(new GenericToStringSerializer<>(String.class)); // value 값을 String으로 저장
return template;
}
@Bean
public CacheManager productCacheManager(RedisConnectionFactory redisConnectionFactory) {
// 응답 타입 Object 직렬화
RedisCacheConfiguration objectCacheConfig = RedisCacheConfiguration
.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new Jackson2JsonRedisSerializer<>(Object.class)))
.entryTtl(Duration.ofMinutes(30L));
// 응답 타입 Response 직렬화
RedisCacheConfiguration responseCacheConfig = RedisCacheConfiguration
.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(
new Jackson2JsonRedisSerializer<>(Response.class)));
// 캐시 이름별로 직렬화 방식 지정
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put("rank", objectCacheConfig);
cacheConfigurations.put("search", responseCacheConfig);
return RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
}
3. yml로 포트 설정해주기
spring:
datasource:
url: jdbc:mysql://localhost:3306/freship
username: root
password:
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
show_sql: true
format_sql: true
use_sql_comments: true
dialect: org.hibernate.dialect.MySQLDialect
data:
redis:
host: localhost
port: 6379
4. redisTemplate을 사용하여 Redis에서 데이터 입출력하기
@Component
@RequiredArgsConstructor
public class ProductRedisUtils {
private final RedisTemplate<String, Object> redisTemplate;
// 사용자 검색 기록 저장 (동일한 검색어 중복 방지)
public void saveSearchHistory(Long userId, String searchKeyword) {
String searchHistoryKey = "user:" + userId + ":search:" + searchKeyword;
if (Boolean.FALSE.equals(redisTemplate.hasKey(searchHistoryKey))) {
redisTemplate.opsForValue().set(searchHistoryKey, "searched");
incrementSearchRank(searchKeyword);
}
}
// 검색어 순위 증가
private void incrementSearchRank(String searchKeyword) {
redisTemplate.opsForZSet().incrementScore("rank", searchKeyword, 1);
}
// 인기 검색어 조회 (상위 10개)
public List<String> getTopSearchKeywords() {
Set<String> popularSearch = redisTemplate.opsForZSet()
.reverseRange("rank", 0, 9)
.stream()
.map(Object::toString)
.collect(Collectors.toSet());
return popularSearch != null ? new ArrayList<>(popularSearch) : new ArrayList<>();
}
public void setReadCount(Long id){
redisTemplate.opsForZSet().add("product:readCount", "product:" + id, 1);
}
public Long getReadCount(Long productId){
return redisTemplate.opsForZSet().score("product:readCount", "product:" + productId).longValue();
}
public Long addReadCount(Long productId){
return redisTemplate.opsForZSet().incrementScore("product:readCount", "product:" + productId, 1).longValue();
}
public boolean notExistsReadCount(Long productId){
// 값이 존재하지 않는다면 null 반환
Double currentReadCount = redisTemplate.opsForZSet().score("product:readCount", "product:" + productId);
return currentReadCount == null;
}
// 조회수 상위 10개의 상품 id 리스트 조회
public List<Long> findProductIds() {
Set<Object> objectSet = redisTemplate.opsForZSet().reverseRange("product:readCount",0, 9);
if (objectSet == null) {
return Collections.emptyList();
}
//"product:7" -> ["product", "7"] -> ["7"]로 변환
List<Long> productIdList = new ArrayList<>();
for (Object o : objectSet) {
String value = (String) o;
String[] parts = value.split(":");
productIdList.add(Long.parseLong(parts[1]));
}
return productIdList;
}
public Boolean isNotViewed (Long productId, Long userId) {
Boolean isNotViewed = redisTemplate.opsForValue().setIfAbsent("product:" + productId + ":user:" + userId, "viewed");
return isNotViewed;
}
// 자정에 캐시 리셋
@Scheduled(cron = "0 0 0 * * ?")
public void clearCache() {
Set<String> cacheKeys = redisTemplate.keys("search*");
if (cacheKeys != null && !cacheKeys.isEmpty()) {
redisTemplate.delete(cacheKeys);
}
// 유저 검색 기록 삭제
Set<String> keysForDelete = redisTemplate.keys("user:*");
if (keysForDelete != null && !keysForDelete.isEmpty()) {
redisTemplate.delete(keysForDelete);
}
// 검색 조회수 삭제
if (Boolean.TRUE.equals(redisTemplate.hasKey("rank"))) {
redisTemplate.opsForZSet().removeRange("rank", 0, -1);
}
Set<String> visitorKeys = redisTemplate.keys("product*");
if (visitorKeys != null && !visitorKeys.isEmpty()) {
redisTemplate.delete(visitorKeys);
}
if (Boolean.TRUE.equals(redisTemplate.hasKey("product:readCount"))) {
redisTemplate.opsForZSet().removeRange("product:readCount", 0, -1);
}
}
}
5. 캐싱하고 싶은 데이터에 어노테이션 붙이기
@Cacheable(cacheNames = "rank", key = "'products:rank'", cacheManager = "productCacheManager")

'Redis' 카테고리의 다른 글
Redis 캐싱 전략과 조회 성능 비교 (0) | 2025.03.25 |
---|---|
Redis의 정의와 기본 명령어 (1) | 2025.03.25 |