Redis
1. Redis
1) 일반적인 관계형 데이터베이스
- 서버의 형태로 가동됨.
- 주로 파일 시스템(디스크)에 데이터를 저장
- 데이터에 영속성을 부여하지만
- 읽고 쓰는데 걸리는 시간이 비교적 길다.
>>> 상황에 따라 공유되는 In-Memory 데이터베이스가 필요!
- 일시적인 세션 정보
- 빈번하게 조회되는 데이터
~> RAM에 데이터를 저장하고 공유할 수 있는 Middleware를 사용.
2) Redis (REmote DIctionary Server)
- Key-Value의 형태로 데이터를 저장할 수 있게 해주는 In-Memory Database (여러가지 자료구조 제공 - String, List, Set, Hash, Sorted Set)
- 여러 어플리케이션 인스턴스에서 세션 정보 공유
- 관계형 데이터베이스의 데이터를 임시 저장하는 캐시
-- 데이터 베이스 연결 👇
아래에서 데이터베이스 만들면 된다.
Why Redis Cloud | Redis
Redis Cloud: Build Fast Apps, Fast. Redis Cloud is the easiest way to build and manage fast, scalable apps–fast. It reduces total cost of ownership and helps organizations fight database sprawl. It also empowers architects and developers like no other cl
redis.com
+ Intellij로 Redis 연결하기
데이터베이스의 연결 정보를 찾고, 데이터베이스 연결을 해준다.
- Host에 public endpoint의 콜론(:) 이전 부분을 복사 붙여넣기 해준다.
- Port에 콜론(:) 이후 숫자를 써준다.
![](https://blog.kakaocdn.net/dn/b8UrUK/btsEOR9SAEI/FIROHCtTGZrolph9LrYev1/img.png)
3) Redis로 간단한 데이터 저장
- String : 가장 기본이 되는 자료형.
- SET key value : key에 value 문자열 데이터 저장. ""로 공백 등을 포함해서 저장 가능.
- GET key : key에 저장되어 있는 문자열 데이터 회수
- 정수형 문자열의 경우 증감이 가능. (INCR key / DECR key)
- 한번에 여러 데이터 할당/반환이 가능 (MSET key value key value key value.../ MGET key key key...)
- 이미지, 영상 등 파일을 저장하는데도 활용됨. 저장할 수 있는 최대 크기는 512MB.
-- Map<String, String> map = new HashMap<>();
-- map.put("greeting", "Hello Redis");
set greeting "Hello Redis"
-- map.get("greeting");
get greeting
--정수형 문자열
SET intstring 1
INCR intstring
GET intstring
DECR intstring
--다시 같은 key에 set이 가능하다
set intstring 300
--복수의 key value 설정/반환
MSET name hehesim age 29 married false
MGET name age married
- List : 여러 문자열 데이터를 Linked List의 형태로 저장, 스택 또는 큐로 활용
- LPUSH key value : key에 저장된 리스트 앞쪽에 value 데이터 저장
- RPUSH key value : key에 저장된 리스트 뒤쪽에 value 데이터 저장
- LPOP key : key의 앞쪽에서 데이터 가져오고 제거
- RPOP key : key의 뒤쪽에서 데이터 가져오고 제거
- LLEN key : key의 길이 반환
- LRANGE key start end : key의 start부터 end까지 반환 (start가 end보다 클 경우 빈 결과가, 음수가 주어질 경우 리스트의 뒤에서부터 위치를 세는 방식으로 동작)
-- list.push
LPUSH fruitlist apple
RPUSH fruitlist banana
LPUSH fruitlist coconut
RPUSH fruitlist melon
-- 결과는 coconut - apple - banana - melon
LPOP fruitlist
RPOP fruitlist
-- 몇 개의 데이터가 있는지
LLEN fruitlist
-- 0부터 -1은 전체 데이터 다 나옴
LRANGE fruitlist 0 2
LRANGE fruitlist 0 -1
- Set : 문자열 집합, 중복이 없고 순서가 없다. 집합 연산(교집합, 합집합 등) 지원
- SSAD key value : key에 저장된 집합에 value 데이터 추가
- SMEMBERS key : key에 저장된 모든 원소 반환
- SISMEMBER key value : key에 value가 있는지 확인 (존재여부)
- SREM key value : key에서 value 제거
- SCARD key : key의 value 갯수
-- set
-- 여러번 실행해도 한번만 들어간다(중복없음)
SADD students alex
SADD students brad
SADD students chad
-- 전체 조회
SMEMBERS students
-- 있는지 조회
SISMEMBER students dave
-- 제거
SREM students chad
-- 개수 확인
SCARD students
- Hash : Field와 Value로 이뤄진 자료구조. key 내부에 field를 정의하고 그 내부에 다시 value를 저장한다. (자바 컬렉션의 map 역할)
- HSET key field value : key의 Hash에 field-value 추가
- HGET key field : key의 field의 value를 반환
- HMGET key field field fiel... : key의 여러 field의 value를 반환
- HGETALL key : key의 모든 field-value를 반환
- HKEYS key : key의 모든 field를 반환
- HLEN key : key의 field-value의 개수
-- Hash
--Map<String, Map<String, String>>
HSET alexinfo name alex age 20 gender female
HGET alexinfo name
HMGET alexinfo name age gender
HGETALL alexinfo
HKEYS alexinfo
HLEN alexinfo
- Sorted Set : 정렬된 집합. 데이터와 score를 함께 전달. (리더보드 등을 구현하는데 주로 활용)
- ZADD key score member score member... : key에 score-member 추가
- ZRANK key member : key의 member의 순위 반환 (오름차순)
- ZRANGE key start stop : key의 member를 start에서 stop까지 반환
- ZINCRBY key inc member : key의 member의 점수를 inc만큼 증가
-- Sorted Set
ZADD grades 10 alex
zadd grades 9 brad 11 chad
ZADD grades 8 dave
-- 점수 올리기
ZINCRBY grades 1 dave
-- 0번째부터 1번째까지 보기
ZRANGE grades 0 1
-- 순위 보기
ZRANK grades dave
-- 내림차순으로 볼때 몇순위인지 확인
ZREVRANK grades dave
- 그 외
- EXPIRE : 만료 시간 정할 수 있음.
- DEL로 key의 데이터를 제거한다.
- FLUSHDB : 모든 Key 제거
-- expirelist
LPUSH expirelist "to be expired"
EXPIRE expirelist 10
-- delete key
SET somekey "to be deleted"
DEL somekey
get somekey
-- 전체 key 제거
flushdb
명령어 공식 문서 : https://redis.io/commands/
Commands
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker
redis.io
4) Spring Boot에서 Redis 사용하기
- 스프링 프로젝트 생성시 Spring Data Redis (Access+Driver) 의존성 추가하기
//build.gradle 로도 의존성 추가 가능
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
- application.yaml에서 설정.
// application.yaml
spring:
data:
redis:
host: <서버 주소>
port: <포트 번호>
username: <사용자 계정, 기본값 default>
password: <사용자 비밀번호>
- StringRedisTemplate
: 문자열 기준에서 작업을 편하게 해주도록 StringRedisTemplate이 Bean으로 등록되어 있음.
- ValueOperations<K, V> : Redis에 문자열을 저장하는 작업을 위한 객체. >> StringRedisTemplate.opsForValue()로 가져옴.
@RestController
@RequiredArgsConstructor
public class SimpleController {
//문자열 key와 문자열로 구성된 value(자바기준 문자열)를 다루기 위한 객체
private final StringRedisTemplate redisTemplate;
@PutMapping("string")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void setString(@RequestParam("key") String key,
@RequestParam("value") String value) {
// Redis에 String을 저장하고 싶다.
// ValueOperations : Redis 기준 문자열 작업(strings)을 위한 객체
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set(key, value);
//+ 다양하게 사용가능하다.
ListOperations<String, String> listOperations = redisTemplate.opsForList();
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();
}
}
--> Redis에 key와 value를 저장하고,
@GetMapping("string")
public String getString(
@RequestParam("key")
String key
) {
ValueOperations<String, String> operations = redisTemplate.opsForValue();
String value = operations.get(key); // null값이 반환될 수가 있다.
if (value == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
return value;
}
-->이를 불러들인다.
- 이번에는 SetOperations 사용
@PutMapping("string")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void setString(@RequestParam("key") String key,
@RequestParam("value") String value) {
SetOperations<String, String> setOperations = redisTemplate.opsForSet();
setOperations.add(key, value);
}
@GetMapping("string")
public Set<String> getString(
@RequestParam("key")
String key
) {
SetOperations<String, String> operations = redisTemplate.opsForSet();
return operations.members(key);
}
}
- RedisTemplate<K, V>
: 특정 자료형의 Java 객체를 Redis로 저장하고 싶다면, 해당 자료형을 사용하는 RedisTemplate 객체를 만들어줄 수 있다.
- PersonDto
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PersonDto {
private String name;
private Integer age;
private String major;
}
- RedisConfig 만들기 @Configuration
// RedisConfig
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, PersonDto> personRedisTemplate(
RedisConnectionFactory connectionFactory
) {
RedisTemplate<String, PersonDto> template = new RedisTemplate<>();
// 연결 팩토리 가져오기
template.setConnectionFactory(connectionFactory);
// 주어진 데이터의 직렬화 방식을 결정한다.
// Redis의 Value는 결국 문자열 형식이니까 주어진 데이터(DTO)를 어떻게 문자열로 바꿀 것인지 결정.
// template.setDefaultSerializer(RedisSerializer.json()); //이렇게 하면 key, value 둘다 이렇게 설정
template.setKeySerializer(RedisSerializer.string()); //이렇게 하면 key, value 따로 설정
template.setValueSerializer(RedisSerializer.json());
return template;
}
}
- Controller에서 실사용
// SimpleController
@RestController
@RequiredArgsConstructor
public class SimpleController {
private final RedisTemplate<String, PersonDto> persondRedisTemplate;
@PutMapping("person")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void setPerson(
@RequestParam("name") String name,
@RequestBody PersonDto dto
) {
ValueOperations<String, PersonDto> operations = persondRedisTemplate.opsForValue();
operations.set(name, dto);
}
@GetMapping("person")
public PersonDto getPerson(
@RequestParam("name") String name
) {
ValueOperations<String, PersonDto> operations = persondRedisTemplate.opsForValue();
PersonDto value = operations.get(name);
if (value == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
return value;
}
}
>> Body에 담아 보내면 JSON 타입으로 직렬화되어 나온다.
2. HttpSession과 Redis
ex) 장바구니 (로그인 안한 상태에서 장바구니 넣고 > 로그인해도 남아있도록)
1) HttpSession을 매개변수로 Handler Method에 추가
// SessionController
@Slf4j
@RestController
@RequestMapping("session")
public class SessionController {
@PutMapping
@ResponseStatus(HttpStatus.NO_CONTENT)
public void setSession(
@RequestParam("key")
String key,
@RequestParam("value")
String value,
HttpSession session
) {
session.setAttribute(key, value);
}
@GetMapping
public String getSession(
@RequestParam("key")
String key,
HttpSession session
) {
// value값은 Object형이기 때문에 그렇게 받아줌.
Object value = session.getAttribute(key);
if (value == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
if (!(value instanceof String)) {
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR);
}
return value.toString();
}
}
>> 하.지.만!!!! 두개 이상의 서버 인스턴스가 실행되면?
한쪽에서는 저장된 데이터를 반대쪽에서는 확인할 수 없다.
서버를 8080, 8081 두개에서 실행하였다.
서버포트 8080에서는 확인 가능하지만, 8081에서는 불가능하다.
== 서로 다른 Tomcat에서 실행되었기 때문에 둘은 Session을 공유하지 않는다...
==> 분산 환경에서 세션 정보를 공유할 수 있는 방법이 필요하다!
2) Redis에 저장하도록 설정하기
- 의존성 추가
// build.gradle
implementation 'org.springframework.session:spring-session-data-redis'
- 아까 위에서 했던 yaml 설정 정보 (그대로임)
// application.yaml
spring:
data:
redis:
host: <서버 주소>
port: <포트 번호>
username: <사용자 계정, 기본값 default>
password: <사용자 비밀번호>
- @Configuration 또는 @SpringBootApplication이 설정된 클래스에 @EnableRedisHttpSession을 추가
>> 이렇게 하면 바로 설정 끝! 다시 실행해본다.
포트 8080에서도, 8081에서도 같은 Session으로 저장되어 있어 탐색 가능하다.
Redis를 이용해 세션 정보를 기록하기 시작한다.
+ 세션 유지 기간도 설정 가능하다. (default 1800초)
maxInactiveIntervalInSeconds 설정.
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 10) //세션이 유지되는 기간.
public class RedisConfig {
@Bean
public RedisTemplate<String, PersonDto> personRedisTemplate(
RedisConnectionFactory connectionFactory
) {
//...
}
}
3. Redis Caching
1) CPU Cache
: CPU에는 자주 조회되는 데이터를 사용하기 위한 Cache 라는 임시 저장 장치가 있다.
- RAM보다도 읽기 쓰기 속도가 빠르다.
- 원본이 아닌 임시 데이터.
2) Caching
: 임시 저장소에 데이터를 저장하는 것.
- RESTful 제약사항 중에도 Cacheability(Cache 가능성에 대해 명시하기)가 있다.
- Cache Hit : 캐시에 접근했을 때 찾고 있는 데이터가 있는 경우
- Cache Miss : 캐시에 접근했을 때 찾고 있는 데이터가 없는 경우
- Eviction Policy : 캐시에 공간이 부족할 때 어떻게 공간을 확보할지
- Caching Strategy : 언제 캐시에 데이터를 저장하고, 언제 캐시를 확인하는지에 대한 전략.
3) Caching Strategy
- Cache Aside (Lazy Loading) : 조회 시 항상 캐시 우선 확인. 데이터가 있으면 캐시에서 사용 & 없으면 원본에서 조회 후 캐시에 저장.
- 최초 조회시에는 소요시간이 더 걸리고, 데이터의 최신성 보장 X
- 필요한 데이터만 캐시에 남아있음.
- Write Through : 데이터를 작성할 때 항상 캐시에 작성 & 이후 원본에 작성.
- 캐시가 항상 동기화되어 있음.
- 자주 사용하지 않는 데이터도 캐시됨. 작성이 항상 캐시를 거쳐 시간이 소요됨.
- Write Back : 데이터를 작성할 때 항상 캐시에 작성 & 일정 주기로 원본에 반영.
- 작성하는데 생기는 데이터베이스 부하가 줄어듦.
- 원본에 적용 전에 캐시에 문제가 생기면 데이터 소실됨.
4) Redis에 수동으로 캐시 작성하기
- 기본 구성
- Item Entity
@Getter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
private String name;
@Setter
private String description;
@Setter
private Integer price;
@Setter
private Integer stock;
}
- ItemDto class
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ItemDto {
private Long id;
private String name;
private String description;
private Integer price;
private Integer stock;
public static ItemDto fromEntity(Item entity) {
return ItemDto.builder()
.id(entity.getId())
.name(entity.getName())
.description(entity.getDescription())
.price(entity.getPrice())
.stock(entity.getStock())
.build();
}
}
- ItemRepository interface
@Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
}
- SlowDataQuery : 데이터 조회가 오랜 시간이 걸림을 가정하기 위해 구성. -> Thread.sleep()
// SlowDataQuery class
@Component
@RequiredArgsConstructor
public class SlowDataQuery {
private final ItemRepository itemRepository;
public List<Item> findAll() {
try {
Thread.sleep(1000); //1초 멈췄다가 조회함.
} catch (InterruptedException ignored) {
}
return itemRepository.findAll();
}
public Optional<Item> findById(Long id) {
try {
Thread.sleep(1000); //1초 멈췄다가 조회함.
} catch (InterruptedException ignored) {
}
return itemRepository.findById(id);
}
}
- 이를 사용하는 Service 작성
// ItemService class
@Slf4j
@Service
@RequiredArgsConstructor
public class ItemService {
private final SlowDataQuery repository;
public ItemDto readOne(Long id) {
return repository.findById(id)
.map(ItemDto::fromEntity)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
public List<ItemDto> readAll() {
return repository.findAll()
.stream()
.map(ItemDto::fromEntity)
.toList();
}
- 이를 사용하는 Controller 작성
// ItemController class
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("items")
public class ItemController {
private final ItemService itemService;
@GetMapping
public List<ItemDto> readAll() {
return itemService.readAll();
}
@GetMapping("{id}")
public ItemDto readOne(@PathVariable("id") Long id) {
return itemService.readOne(id);
}
}
- 아까와 같이 원하는 자료형을 사용하는 RedisTemplate 만들기
// RedisConfig configuration class
// key Long, value ItemDto
@Bean
public RedisTemplate<Long, ItemDto> cacheRedisTemplate(
RedisConnectionFactory connectionFactory
) {
RedisTemplate<Long, ItemDto> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.json());
return template;
}
- ItemService에 ValueOperations<Long, ItemDto> 추가
: @Resource를 활용하면 SpringBoot 내부에서 동일한 타입 파라미터를 가진 RedisTemplate을 기준으로 ValueOperations를 추출해 의존성을 주입해줄 수 있다!!
// ItemService class
@Slf4j
@Service
@RequiredArgsConstructor
public class ItemService {
private final SlowDataQuery repository;
@Resource(name = "cacheRedisTemplate")
private ValueOperations<Long, ItemDto> cacheOps;
public ItemDto readOne(Long id) {
//...
}
public List<ItemDto> readAll() {
//...
}
}
- 이제 Controller의 readOne() 메서드를 바꿔보자! (Cache Aside)
@Slf4j
@Service
@RequiredArgsConstructor
public class ItemService {
private final SlowDataQuery repository;
@Resource(name = "cacheRedisTemplate")
private ValueOperations<Long, ItemDto> cacheOps;
public ItemDto readOne(Long id) {
// Cahce Aside를 구현해보자
// 1. cacheOps에서 ItemDto를 찾아본다.
ItemDto found = cacheOps.get(id);
// 2. null일 경우 데이터베이스에서 조회한다.
if (found == null) {
// 2-1. 없으면 404
found = repository.findById(id)
.map(ItemDto::fromEntity)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
// 2-2. 있으면 캐시에 저장.
// 3번째 인자로 만료 시각 설정도 가능
cacheOps.set(id, found, Duration.ofSeconds(20));
}
// 3. 최종적으로 데이터를 반환한다.
return found;
}
}
>> 처음 GET 요청을 보냈을 시에는 지연이 있지만, 이후 다시 요청을 보냈을 때에는 캐시에 저장된 정보를 가져오기 때문에 훨씬 빠르게 가져온다. > 그 후 20초가 지나면 다시 지연이 생기고, 캐시에 다시 저장된다.
+ create,,, update 등등 여러 상황에 캐시를 활용할 수 있다.
5) @EnableCaching (Spring 내부 기능으로 간단하게 구현하기)
- @Configuration + @EnableCaching 추가.
- 이때 CacheManager의 구현체가 Bean으로 등록되어야 한다!
// ...
import static org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(
RedisConnectionFactory redisConnectionFactory
) {
RedisCacheConfiguration configuration = RedisCacheConfiguration
// 기본적인 설정
.defaultCacheConfig()
// null을 캐싱할 것인지
.disableCachingNullValues()
// Timetolive 만료시간 초
.entryTtl(Duration.ofSeconds(60))
// 캐시를 구분하기 위한 접두사 설정.
.computePrefixWith(CacheKeyPrefix.simple())
// 캐시 값을 어떻게 직렬화 할것인지
.serializeValuesWith(SerializationPair.fromSerializer(RedisSerializer.json()));
// 이 부분은 readAll할 때 역직렬화를 가능하도록 만들기 위한 map
Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>();
RedisCacheConfiguration itemAllConfig = RedisCacheConfiguration
.defaultCacheConfig()
.disableCachingNullValues()
.entryTtl(Duration.ofSeconds(10))
.serializeValuesWith(SerializationPair.fromSerializer(RedisSerializer.java()));
configurationMap.put("itemAllCache", itemAllConfig);
return RedisCacheManager
.builder(redisConnectionFactory)
.withInitialCacheConfigurations(configurationMap)
.cacheDefaults(configuration)
.build();
}
}
- @Cacheable 어노테이션을 추가.
(key에 작성하는 것은 SpEL 언어)
// ItemService class
// cacheNames: 적용할 캐시 규칙을 지정하기 위한 이름
// key: 캐시에서 데이터를 구분하기 위해 활용할 값
@Cacheable(cacheNames = "itemCache", key = "#root.args[0]")
public ItemDto readOne(Long id) {
return repository.findById(id)
.map(ItemDto::fromEntity)
.orElseThrow(()
-> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
@Cacheable(cacheNames = "itemAllCache", key = "#root.methodName")
public List<ItemDto> readAll() {
return repository.findAll()
.stream()
.map(ItemDto::fromEntity)
.toList();
}
>> findById 해보면 itemCache::이 붙고, id값이 붙어서 나온다~
>> findAll 도 가능!
6) @CachePut 어노테이션
: 작성과 갱신시에 데이터를 캐시에 저장하고 싶다면 @CachePut을 사용.
cf) @Cacheable : 캐시에 이미 데이터가 있으면 데이터를 실행하지 않음.
@CachePut : 무조건 메서드를 실행 후 캐시에 데이터를 저장. (Write Through 방식)
// ItemService class
// CachePut은 항상 메서드를 실행하고 해당 결과를 캐시에 적용한다.
@CachePut(cacheNames = "itemCache", key = "#result.id")
public ItemDto create(ItemDto dto) {
log.info("cacheput create");
return ItemDto.fromEntity(itemRepository.save(Item.builder()
.name(dto.getName())
.description(dto.getDescription())
.price(dto.getPrice())
.stock(dto.getStock())
.build()));
}
4. Sorted Set 활용
- LeaderBoard (ex. 쇼핑몰에서 가장 많이 구입된 순으로 물품 나열)
+ ItemOrder 추가
- ItemOrder Entity class
@Getter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ItemOrder {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
@ManyToOne(fetch = FetchType.LAZY)
private Item item;
}
- ItemOrderRepository
@Repository
public interface OrderRepository extends JpaRepository<ItemOrder, Long> {
}
1) RedisTemplate 만들기
// RedisConfig class
@Bean
public RedisTemplate<String, ItemDto> rankTemplate(
RedisConnectionFactory connectionFactory
) {
RedisTemplate<String, ItemDto> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.json());
return template;
}
2) ZSetOperations
- ZSetOperations를 주입
- 물품을 구매하는 purchase 메서드에서 구매 시마다 score가 올라가도록 설정.
// ItemService class
@Slf4j
@Service
@RequiredArgsConstructor
public class ItemService {
private final SlowDataQuery repository;
private final ItemRepository itemRepository;
@Resource(name = "cacheRedisTemplate")
private ValueOperations<Long, ItemDto> cacheOps;
@Resource(name = "rankTemplate")
private ZSetOperations<String, ItemDto> rankOps;
public void purchase(Long id) {
ItemDto item = ItemDto.fromEntity(repository.purchase(id));
}
//...
}
구매할 때마다 score가 점점 커진다.
- 그리고 score에 따라서 순위를 보여주는 결과를 반환한다. (reverseRange())
// ItemService
public List<ItemDto> getMostSold() {
Set<ItemDto> ranks = rankOps.reverseRange("soldRanks", 0, 9);
log.info(ranks.toString());
log.info(String.valueOf(ranks.getClass())); //LinkedHashSet
if (ranks == null) {
return Collections.emptyList();
}
return ranks.stream().toList();
}
// ItemController
@GetMapping("mostSold")
public List<ItemDto> getMostSold() {
return itemService.getMostSold();
}
>> 결과가 이렇게 나온다! 많이 구매한 순서로 표시가 된다!
'Programming > Database' 카테고리의 다른 글
눈물의 DB 연결 (2) | 2024.05.03 |
---|---|
Transaction (0) | 2024.02.02 |
JDBC (1) | 2023.12.20 |
정규화 Normalization와 조인 Join (1) | 2023.12.19 |
[연습문제 모음] (0) | 2023.12.18 |