상세 컨텐츠

본문 제목

SpringBoot Redis 설정 및 Example

Spring/Redis

by Chan.94 2024. 9. 2. 16:19

본문

반응형

Intro

Docker에 Redis server를 구성한 환경이다.

 

Redis 및 Docker에 대한 개념은 아래 포스팅을 참고하기 바란다.

 

- Redis 개념 및 특징, 메모리 정책

- Docker 개념 정리

- Docker 설치 (Window 환경) 및 명령어 정리


Docker로 Redis 컨테이너 실행

1. Docker에 Redis 이미지 다운로드

docker pull redis

2. Redis 컨테이너 생성 및 실행

docker run --name redis_server -it -d -p 6379:6379 redis

 

-name : 컨테이너 이름을 redis_server로 한다.

-i : 표준입력(stdin) 활성화

-t : 키보드를 통해 표준 입력(stdin)을 전달할 수 있게 한다.

-d : 컨테이너를 백그라운드로 실행.

-p : 호스트와 컨테이너의 포트를 연결

 

생성한 컨테이너를 재실행

docker restart redis_server

 

3. 실행 확인

Docker Desktop이나 명령어로 실행을 확인한다.

Docker Desktop - redis 실행

docker logs -f redis_server


dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

application.yml

spring:
  redis:
    host: localhost
    port: 6379

RedisConfiguration.java

@Configuration
@RequiredArgsConstructor
public class RedisConfiguration {
    private final RedisProperties redisProperties;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        //redisTemplate.setEnableTransactionSupport(true);

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        return redisTemplate;
    }
}

 

RedisConnectionFactory

Redis 서버에 대한 연결을 생성하고 관리하는 역할을 하는 인터페이스로 애플리케이션이 Redis와 상호작용할 수 있게 한다.

RedisConnectionFactory 구현체

LettuceConnectionFactory

Lettuce 클라이언트를 사용하여 Redis 연결을 관리한다. 비동기 및 멀티스레드 환경에서 사용하기 좋다.

spring boot 2부터는 Lettuce가 기본설정이다.

 

JedisConnectionFactory
Jedis 클라이언트를 사용하여 Redis 연결을 관리한다. 단일 스레드 환경에서 주로 사용되며, 비교적 단순한 Redis 사용 사례에 적합.

 

RedisTemplate

RedisTemplate는 Redis에 명령어를 실행하는 기능을 제공하는 클래스이다.

RedisTemplate를 사용할 때 Spring <-> Redis 간 데이터 직렬화 설정에 따라 Redis에 저장되는 방식이 달라진다.

 

트랜잭션 지원 활성화

redisTemplate.setEnableTransactionSupport(true);
  1. RedisRepository 사용시 @Transactional 어노테이션을 통해 트랜잭션을 관리
  2. RedisTemplate 사용시 EnableTransactionSupport true 설정 

Redis 사용 전 필요 개념

RedisRepository

  • Spring Data Commons에서 제공하는 일반적인 인터페이스로, 기본적인 CRUD(Create, Read, Update, Delete) 작업을 처리하는 데 사용
  • JPA, Redis 등 여러 데이터 저장소에 대해 공통으로 사용되는 패턴
  • save(), findById(), findAll(), deleteById(), deleteAll() 등 기본적인 CRUD 메서드들이 제공
  • Spring Data가 인터페이스의 구현체를 자동으로 생성해 주기 때문에, 인터페이스 정의만으로도 구현체를 사용할 수 있다
  • 일반적인 엔터티와 매핑된 간단한 Redis 사용 사례에 적합하다

RedisTemplate

  • Redis와의 보다 세밀한 상호작용을 제공하는 Spring의 템플릿 클래스로 Redis의 다양한 데이터 구조(String, Hash, List, Set, ZSet 등)와 작업할 때 사용
  • Redis의 저수준 명령을 직접 다룰 수 있어, 복잡한 작업이나 특정한 Redis 기능을 사용할 때 유연성이 높다
  • CRUD 작업을 직접 구현해야 하므로 개발 속도가 느릴 수 있다
  • Redis의 저수준 명령을 세밀하게 다룰 수 있어, 복잡한 작업이나 최적화가 필요한 경우에 더 적합하다

Redis  cli

  • Redis와 소통을 위한 command line interface
  • Key 정보 조회 명령어
    • keys : 특정 패턴과 일치하는 모든 Key를 조회
                 ex) keys *
    • ttl : 키의 남은 만료 시간을 초 단위로 반환
            ex) ttl <key>
  • 데이터 타입별 조회 명령어
    • String
      get <key>
    • List
      lrange <key> <start> <end>
    • Set
      smembers <key>
    • Hash
      hget <key> : 특정 필드의 값을 검색
      hgetall <key> : 모든 필드와 값 조회

RedisRepository Example

1) Redis에 저장할 자료구조 객체 

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
@RedisHash(value = "refreshToken", timeToLive = 60)
public class RedisRefreshToken {

    @Id
    private Long userId;

    private String token;
    
}

@RedisHash

  • value
    Redis에 들어갈 객체의 Key의 prefix가 되며, 데이터를 넣으면 [prefix]:[entity id]와 같이 Key가 설정된다.
  • timeToLive
    객체가 만료되는 시간을 설정할 수 있다. 적용되는 시간은 '초' 단위이며, default 값은 -1L으로 이 경우 유효시간이 설정되지 않는다.

@Id

  • [prefix]:[entity id]로 데이터에 대한 Key를 저장하여 각 데이터를 구분할 수 있다.
  • JPA와 다르게 @Id가 org.springframework.data.annotaion.Id이다.

2) Repository생성

public interface RedisRefreshTokenRepository extends CrudRepository<RedisRefreshToken, Long>{
	
}
  • CurdRepository를 상속하는 Custom Repository를 생성
  • RedisRepository 방식은 CrudRepository를 상속받은 인터페이스가 사용되기 때문에 Spring Data JPA에서 JpaRepository를 사용하는 방식과 유사하다.

 

3) 테스트

@RequestMapping("/devlog/redis")
@RestController
@RequiredArgsConstructor
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public class RedisTestController {
    
    private final RedisRefreshTokenRepository redisRefreshTokenRepository;
    
    @GetMapping("/saveRefreshToken")
    public ResponseEntity<RedisRefreshToken> saveRefreshToken(Long userId) {
        
        RedisRefreshToken redisRefreshToken = new RedisRefreshToken();
        redisRefreshToken.setUserId(userId);
        redisRefreshToken.setToken(UUID.randomUUID().toString());
        
        redisRefreshTokenRepository.save(redisRefreshToken);
        
        redisRefreshToken = redisRefreshTokenRepository.findById(userId).get();
        
        return new ResponseEntity<>(redisRefreshToken, HttpStatus.OK);
    }
}

 

 

4) redis-cli로 데이터 확인

docker exec -it redis_server redis-cli

redis-cli로 데이터 확인

timeToLive를 60초로 설정하였기 때문에 60초가 지난 후 다시 key를 조회해 보면 조회되지 않는 것을 확인할 수 있다.


RedisTempalte Example

String

@GetMapping("/redisTemplate-String-Save")
public void opsForValueSave(String key, String value) throws Exception {
    ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    // 저장
    valueOperations.set(key, value, 60, TimeUnit.SECONDS);
}

@GetMapping("/redisTemplate-String-Search")
public ResponseEntity<String> opsForValueSearch(String key, String value) throws Exception {
    ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    // 조회
    String result = (String) valueOperations.get(key);
    return new ResponseEntity<>(result, HttpStatus.OK);
}

 

List

@GetMapping("/redisTemplate-List-Save")
public void opsForListSave(String key, Long value) throws Exception {
    ListOperations<String, Object> listOperations = redisTemplate.opsForList();
    
    //저장
    for(int saveValue = 1 ; saveValue <= value ; saveValue++) {
        if(saveValue % 2 == 0) {
            listOperations.leftPush(key, String.valueOf(saveValue));
        }else {
            listOperations.rightPush(key, String.valueOf(saveValue));
        }
    }
    //만료시간 설정
    listOperations.getOperations().expire(key, 60, TimeUnit.SECONDS);
}

@GetMapping("/redisTemplate-List-Pop")
public ResponseEntity<List<Object>> opsForListSearch(String key) throws Exception {
    ListOperations<String, Object> listOperations = redisTemplate.opsForList();
    
    //List Size
    long listsize = listOperations.size(key);
    //조회
    List<Object> list = listOperations.range(key, 0, listsize);
    for(int nIdx = 0; nIdx < listsize ; nIdx++) {
        //Pop
        listOperations.leftPop(key);
    }
    
    return new ResponseEntity<>(list, HttpStatus.OK);
}

 

Set

@GetMapping("/redisTemplate-Set-Save")
public void opsForSetSave(String key, String value) throws Exception {
    SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
    //저장
    setOperations.add(key, value);
    //만료시간 설정
    setOperations.getOperations().expire(key, 60, TimeUnit.SECONDS);
}

@GetMapping("/redisTemplate-Set-Search")
public ResponseEntity<Set<String>> opsForSetSearch(String key) throws Exception {
    SetOperations<String, Object> setOperations = redisTemplate.opsForSet();
    //조회
    Set<String> result = setOperations.members(key).stream().map(data -> (String) data).collect(Collectors.toSet());
    
    return new ResponseEntity<>(result, HttpStatus.OK);
}

 

4) Hash

@GetMapping("/redisTemplate-Hash-Save")
public void opsForHashSave(String key, String mapKey, String mapValue) throws Exception {
    HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
    //저장
    hashOperations.put(key, mapKey, mapValue);
    //만료시간 설정
    hashOperations.getOperations().expire(key, 60, TimeUnit.SECONDS);
}

@GetMapping("/redisTemplate-Hash-Search")
public ResponseEntity<Map<Object, Object>> opsForHashSearch(String key) throws Exception {
    HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
    //조회
    Map<Object, Object> resultMap = hashOperations.entries(key);
    
    return new ResponseEntity<>(resultMap, HttpStatus.OK);
}
반응형

관련글 더보기

댓글 영역

>