IT recording...

[Redis] Spring + Redis 간단한 API 작성 - (3) 본문

Spring

[Redis] Spring + Redis 간단한 API 작성 - (3)

I-one 2022. 4. 1. 15:58

[더 편하게 보고 싶다면 -> 원본 링크]

https://adorable-aspen-d23.notion.site/Spring-Spring-Boot-Redis-6ecda14328a34ec1a0e3dc2990a51277

 

[Spring] Spring Boot + Redis 연결하기

1. Redis란?

adorable-aspen-d23.notion.site

 

4. Redis 설치

4-1. macOS

  • 나는 macOS를 사용하고 있어서 Brew를 사용했다.
brew install redis  //시간이 좀 많이 걸린다.(기다리기)

//redis 시작
brew services start redis

//redis-cli 켜기,사용
redis-cli

//redis 중지
brew services stop redis

//redis 재시작
brew services restart redis

[Redis] Redis 설치 및 간단한 사용 방법 (Mac)

4-2. AWS EC2 (...작성중...)

5. Redis를 사용한 API 작성

[Spring Boot] Redis (Lettuce)를 이용한 간단한 API 제작

**<위 글을 보고 따라한 예제입니다.>**

  • 프로젝트 깃허브 링크

Spring_Jiwon/src/main/java/young/board/redis at master · YoungDeveloperSS/Spring_Jiwon

(java/young/board 안의 redis 폴더 + gradle 정도만 참고하면 된다)

(나머지는 다른 프로젝트 파일임. 초기설정하기 귀찮아서 기존 프로젝트에 진행했다)

1. 프로젝트 구성

일단 전체적인 Redis 사용 예제 프로젝트는 다음과 같이 구성을 가진다.

(일반적인 MVC 구조와 동일하다고 생각하면 된다)

- config : Redis host,port 등을 설정한다.
- controller : API요청을 받아들여 service를 수행한다.
    - dto : request와, response dto
- domain : Entity 역할을 하는 친구들
    - repository : 데이터가 저장되는 저장소
- service : controller에서 넘어와서 비즈니스로직을 수행한다.(save, get 등)

2. 프로젝트 설정

2-1. gradle 추가

//redis
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.6.3'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2'

2-2. properties 파일 작성

spring.cache.type=redis
spring.cache.redis.time-to-live=60000
spring.cache.redis.cache-null-values=true
spring.redis.host=127.0.0.1
spring.redis.port=6379

3. Config 설정파일

3-1. RedisProperties

  • properties/yml에 작성해 둔 host, post를 받아와 객체 클래스로 제작한다.
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Getter
@Setter
@ConfigurationProperties(prefix = "spring.redis")
@Component
public class RedisProperties {
    private String host;
    private int port;
}

3-2. RedisRepositoryConfig

  • Redis의 연결을 정의하는 클래스
  • Lettuce를 사용한다.
    • LettuceConnectionFactory에서 host, port를 연결
  • RedisConnetionFactory를 사용해 내장 혹은 외부의 Redis를 연결한다.
  • RedisTemplate을 통해 RedisConnection에서 넘겨준 byte 값을 객체 직렬화한다.
    • 왜 직렬화하나? → Spring - Redis 간 데이터 통신시, 우리가 알아볼 수 있게 변환해주기 위해서
    • 안해도 동작에는 문제없지만 redis-cli를 통해 직접 데이터를 보려고 할 때 알아볼 수 없는 형태로 출력되기 때문에 사용한다.
    Spring Boot Redis 두 가지 사용 방법 RedisTemplate, RedisRepository
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories
public class RedisRepositoryConfig {
    private final RedisProperties redisProperties;

    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
    }

    @Bean
    public RedisTemplate<?,?> redisTemplate(){
        RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

3-3. EmbeddedRedisConfig

  • Redis 외부 서버가 존재하는 경우는 필요 없다.
  • But 내장 서버로 환경을 구성할 경우, EmbeddedRedisConfig 설정을 추가로 해줘야 한다.
implementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2'
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import redis.embedded.RedisServer;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@RequiredArgsConstructor
@Profile("local")
@Configuration
public class EmbeddedRedisConfig {
    private final RedisProperties redisProperties;
    private RedisServer redisServer;

    @PostConstruct
    public void redisServer(){
        redisServer = new RedisServer(redisProperties.getPort());
        redisServer.start();
    }

    @PreDestroy
    public void stopRedis(){
        if(redisServer != null){
            redisServer.stop();
        }
    }
}

4. domain 작성

우선 우리는 RedisRepository 방식과 RedisTemplate을 사용하는 방식 둘 모두를 사용해볼 것이다.

RedisRepository 방식을 사용하는 경우는 entity자체를 저장할 수 있는데, RedisTemplate 중 zSet사용시에는 String만 저장할 수 있는 것 같다. (아닐수도.. 더 알아봐야 한다..)

⇒ 일단 RedisRepository에서 저장할 도메인만 작성해보자 (예제에서 template은 걍 string 저장할꺼임)

4-1. RedisCrudByRepository

  • Redis Repository를 사용할 때 사용하는 domain
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.apache.tomcat.jni.Local;
import org.springframework.data.redis.core.RedisHash;

import javax.persistence.Id;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Getter
@ToString
@RedisHash("redisCrud")
public class RedisCrudByRepository {
    @Id
    private Long id;
    private String description;
    private LocalDateTime updatedAt;

    @Builder
    public RedisCrudByRepository(Long id, String description, LocalDateTime updatedAt) {
        this.id = id;
        this.description = description;
        this.updatedAt = updatedAt;
    }
}

4-2. RedisCrudRepository

  • 저장소를 만들자
  • JPARepository를 사용하는 것처럼 interface에 CrudRepository만 상속받으면 기본적인 save, findById등을 그냥 사용할 수 있다.
public interface RedisCrudRepository extends CrudRepository<RedisCrudByRepository, Long> {
}

5. Controller 작성

5-1. dto 작성

  • request, response 데이터 형식을 정해주자
  • Request
    • RedisCrudSaveRequestDto
    • import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import young.board.redis.domain.RedisCrudByRepository; import java.time.LocalDateTime; @Getter @NoArgsConstructor public class RedisCrudSaveRequestDto { private Long id; private String description; private LocalDateTime updatedAt; @Builder private RedisCrudSaveRequestDto(Long id, String description, LocalDateTime updatedAt) { this.id = id; this.description = description; this.updatedAt = updatedAt; } public RedisCrudByRepository toRedisHash(){ return RedisCrudByRepository.builder() .id(id) .description(description) .updatedAt(LocalDateTime.now()) .build(); } }
  • Response
    • RedisByRepositoryResponseDto
    import lombok.Getter;
    import ~~young.board~~.redis.domain.RedisCrudByRepository;
    
    import java.time.LocalDateTime;
    
    @Getter
    public class RedisByRepositoryResponseDto {
        private Long id;
        private String description;
        private LocalDateTime updatedAt;
    
        public RedisByRepositoryResponseDto(RedisCrudByRepository redisHash) {
            this.id = redisHash.getId();
            this.description = redisHash.getDescription();
            this.updatedAt = redisHash.getUpdatedAt();
        }
    }
    
    • RedisByTemplateResponseDto
    import lombok.Builder;
    import lombok.Getter;
    
    @Getter
    public class RedisByTemplateResponseDto {
        private int rank;
        private String name;
    
        @Builder
        private RedisByTemplateResponseDto(int rank, String name) {
            this.rank = rank;
            this.name = name;
        }
    
        public static RedisByTemplateResponseDto of(int rank, String name){
            return RedisByTemplateResponseDto.builder()
                    .rank(rank)
                    .name(name)
                    .build();
        }
    }
    

5-2. RedisController

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.*;
import young.board.redis.controller.dto.RedisByTemplateResponseDto;
import young.board.redis.controller.dto.RedisByRepositoryResponseDto;
import young.board.redis.controller.dto.RedisCrudSaveRequestDto;
import young.board.redis.service.RedisCrudService;

import java.util.Arrays;
import java.util.Set;

@RequiredArgsConstructor
@RestController
public class RedisController {
    private final RedisCrudService redisCrudService;
    private final StringRedisTemplate redisTemplate;

    @PostMapping("/save/repository")
    public Long saveByRedisRepository(@RequestBody RedisCrudSaveRequestDto requestDto){
        return redisCrudService.saveByRedisRepository(requestDto);
    }

    @GetMapping("/get/repository/{id}")
    public RedisByRepositoryResponseDto getByRedisRepository(@PathVariable Long id){
        return redisCrudService.getByRedisRepository(id);
    }

		//java로 치면 key-value에서 name-count
    @PostMapping("/save/template")
    public Boolean saveByRedisTemplate(@RequestParam String name, @RequestParam double count){
        return redisCrudService.saveByRedisTemplate(name,count);
    }

		//1위부터 9위까지 get
    @GetMapping("/get1To9/template")
    public List<RedisByTemplateResponseDto> get1To9ByRedisTemplate(){
        return redisCrudService.get1To9ByRedisTemplate();
    }

}

6. Service 작성

6-1. RedisCrudService

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import young.board.redis.controller.dto.RedisByTemplateResponseDto;
import young.board.redis.controller.dto.RedisCrudSaveRequestDto;
import young.board.redis.controller.dto.RedisByRepositoryResponseDto;
import young.board.redis.domain.RedisCrudByRepository;
import young.board.redis.domain.repository.RedisCrudRepository;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

@RequiredArgsConstructor
@Service
public class RedisCrudService {
    //방법1.Redis Repository 사용
    private final RedisCrudRepository redisCrudRepository;

    //방법2.Redis Template 사용
    private final StringRedisTemplate redisTemplate;
    private ZSetOperations<String, String> zSetOps;

		//방법1.======
    @Transactional
    public Long saveByRedisRepository(RedisCrudSaveRequestDto requestDto){
        return redisCrudRepository.save(requestDto.toRedisHash()).getId();
    }

    public RedisByRepositoryResponseDto getByRedisRepository(Long id){
        RedisCrudByRepository redisCrud = redisCrudRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Nothing saved. id=" + id));
        return new RedisByRepositoryResponseDto(redisCrud);
    }

		//방법2.======
    @PostConstruct
    public void init(){
        zSetOps = redisTemplate.opsForZSet();
    }

    @Transactional
    public Boolean saveByRedisTemplate(String name, double count){
        Boolean aBoolean = zSetOps.add("redisCrudTemplate", name, count);
        return aBoolean;
    }

    public List<RedisByTemplateResponseDto> get1To9ByRedisTemplate(){
        Set<String> redisCrudTemplate = zSetOps.reverseRange("redisCrudTemplate", 0, 8);
        List<RedisByTemplateResponseDto> redisByTemplateResponseDtoSet = new ArrayList<>(redisCrudTemplate.size());
        Iterator<String> iterator = redisCrudTemplate.iterator();

        int i=1;
        while(iterator.hasNext()){
            redisByTemplateResponseDtoSet.add(RedisByTemplateResponseDto.of(i++,iterator.next()));
        }

        return redisByTemplateResponseDtoSet;
    }

}

6-2. Redis Repository를 사용하는 법

  • RedisCrudRepository 만들어놓았던 저장소를 주입받아서 사용할 것이다
  • 그래서 Redis에서 KEY로 사용하는 애는 RedisCrudByRepository(엔티티)에 써져있다.
    • 이 예제에서는 redisCrud를 키로 사용할 것임
    @RedisHash("redisCrud")
    public class RedisCrudByRepository {
    
  • save는 간편하게 save 불러오면 끝
  • get도 간편하게 findById사용 끝
  • 다 찾아오려면 기본으로 제공해주는 findAll 뭐 이런거 사용하면 될듯
//방법1.Redis Repository 사용
private final RedisCrudRepository redisCrudRepository;

//======
@Transactional
public Long saveByRedisRepository(RedisCrudSaveRequestDto requestDto){
    return redisCrudRepository.save(requestDto.toRedisHash()).getId();
}

public RedisByRepositoryResponseDto getByRedisRepository(Long id){
    RedisCrudByRepository redisCrud = redisCrudRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Nothing saved. id=" + id));
    return new RedisByRepositoryResponseDto(redisCrud);
}

6-3. Redis Template을 사용하는 법

  • 자주 쓰이는 StringRedisTemplate을 주입받는다.
  • 이번에는 넣으면 알아서 정렬해주는 ZSet 자료구조를 사용해볼것이다.
    • 얘는 <String,String> 만 되는 것 같은데 다른거는 못넣는건가..흠 더 알아봐야할듯
  • 그래서 ZSetOperations 쓰고, init에서 초기화 해줌
  • save할때는 add(”KEY”, java의 key, java의 value역할(=score))
    • save하면 알아서 score로 정렬된다. (굿굿)
    • redis-cli에서 직접 본 정렬되는 모습 (save api 수행시마다 요로코롬 알아서 정렬된다.)
      • one - 1 , seven - 7, three - 3, five - 5 순으로 넣음
  • 1위부터 9위를 리턴받을 것인데 그래서
    • reverseRange(”KEY”, min, max) //0부터 8까지 9개 가져와라라는 것임
    • Set<String>에 담고, API response형태로 이터레이터 슈슉 써서 변환시켜줌
    • 리턴하면 끗.그러면 요런식으로 리턴된다.
//방법2.Redis Template 사용
private final StringRedisTemplate redisTemplate;
private ZSetOperations<String, String> zSetOps;

//======
@PostConstruct
public void init(){
    zSetOps = redisTemplate.opsForZSet();
}

@Transactional
public Boolean saveByRedisTemplate(String name, double count){
    Boolean aBoolean = zSetOps.add("redisCrudTemplate", name, count);
    return aBoolean;
}

public List<RedisByTemplateResponseDto> get1To9ByRedisTemplate(){
    Set<String> redisCrudTemplate = zSetOps.reverseRange("redisCrudTemplate", 0, 8);
    List<RedisByTemplateResponseDto> redisByTemplateResponseDtoSet = new ArrayList<>(redisCrudTemplate.size());
    Iterator<String> iterator = redisCrudTemplate.iterator();

    int i=1;
    while(iterator.hasNext()){
        redisByTemplateResponseDtoSet.add(RedisByTemplateResponseDto.of(i++,iterator.next()));
    }

    return redisByTemplateResponseDtoSet;
}

'Spring' 카테고리의 다른 글

[Redis] Redis의 자료구조, 명령어 - (2)  (0) 2022.04.01
[Redis] Redis란? - (1)  (0) 2022.04.01
[스프링 JPA1] 3. 컨트롤러  (0) 2022.02.15
[스프링 JPA1] 2. 도메인 개발  (0) 2022.02.15
[Spring] 개발 중 마주한 오류들 - 1  (0) 2022.02.15
Comments