Redis
1. Redis
1) 일반적인 관계형 데이터베이스
- 서버의 형태로 가동됨.
- 주로 파일 시스템(디스크)에 데이터를 저장
- 데이터에 영속성을 부여하지만
- 읽고 쓰는데 걸리는 시간이 비교적 길다.
>>> 상황에 따라 공유되는 In-Memory 데이터베이스가 필요!
- 일시적인 세션 정보
- 빈번하게 조회되는 데이터
~> RAM에 데이터를 저장하고 공유할 수 있는 Middleware를 사용.
2) Redis (REmote DIctionary Server)
- Key-Value의 형태로 데이터를 저장할 수 있게 해주는 In-Memory Database (여러가지 자료구조 제공 - String, List, Set, Hash, Sorted Set)
- 여러 어플리케이션 인스턴스에서 세션 정보 공유
- 관계형 데이터베이스의 데이터를 임시 저장하는 캐시
-- 데이터 베이스 연결 👇
아래에서 데이터베이스 만들면 된다.
+ Intellij로 Redis 연결하기
데이터베이스의 연결 정보를 찾고, 데이터베이스 연결을 해준다.
- Host에 public endpoint의 콜론(:) 이전 부분을 복사 붙여넣기 해준다.
- Port에 콜론(:) 이후 숫자를 써준다.
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/
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 |