Redis

2024. 2. 14. 11:33
728x90

1. Redis

1) 일반적인 관계형 데이터베이스

- 서버의 형태로 가동됨. 

- 주로 파일 시스템(디스크)에 데이터를 저장

- 데이터에 영속성을 부여하지만

- 읽고 쓰는데 걸리는 시간이 비교적 길다. 

 

>>> 상황에 따라 공유되는 In-Memory 데이터베이스가 필요! 

- 일시적인 세션 정보

- 빈번하게 조회되는 데이터 

~> RAM에 데이터를 저장하고 공유할 수 있는 Middleware를 사용.

 

2) Redis (REmote DIctionary Server)

- Key-Value의 형태로 데이터를 저장할 수 있게 해주는 In-Memory Database (여러가지 자료구조 제공 - String, List, Set, Hash, Sorted Set)

- 여러 어플리케이션 인스턴스에서 세션 정보 공유

- 관계형 데이터베이스의 데이터를 임시 저장하는 캐시

 

-- 데이터 베이스 연결 👇

더보기

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();
    }

 

 

>> 결과가 이렇게 나온다! 많이 구매한 순서로 표시가 된다! 

 

728x90

'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

BELATED ARTICLES

more