Spring Boot

Redis 레디스 + 스프링부트 SpringBoot 연동 RedisTemplate, @Cacheable 사용하기

dev.mk 2024. 10. 20. 18:12
반응형

레디스 사용방법을 간단하게 작성하겠다.

 

첫번째. RedisTemplate를 이용하여 CRUD 하기 

> 실무에서는 보통  jwt_token 관리에 사용.

 

두번째. @Cacheable 어노테이션을이용하여 데이터 캐싱하기

> 실무에서는 보통 통계 데이터나 대용량 데이터를 여러번 호출할때 사용.

 

 

그럼 스프링부트에 레디스를 설정해보자 

1. build.gradle에 redis 추가

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

2. application.yml 프로퍼티에 레디스 프로퍼티 추가

spring:
  application:
    name: SpringBoot3WithRedis
  redis:
    host: 185.224.12.50       #클라우드 아이피
    port: 6379                #레디스 포트
    password: redis4321       #레디스 AUTH 비밀번호 AUTH redis
  cache:
    type: redis               #스프링에서 캐시 사용을 레디스로 설정하겠다는 뜻이다.
    redis:
      time-to-live: 5         # 단위(초): @Cacheable의 캐싱시간

 

 

3. SpringBootApplication.java 어노테이션 정의

@SpringBootApplication
//Spring Boot에 캐싱 사용 알려주기
@EnableCaching
public class SpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBoot3WithRedisApplication.class, args);
    }

}

 

 

4. Redis 환경설정 java 작성

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
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.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig {

    //application.yml 정의한 IP (클라우드 IP 이거나 본인의 아이피)
    @Value("${spring.redis.host}")
    private String redisHost;

    //application.yml 정의한 포트번호
    @Value("${spring.redis.port}")
    private String redisPort;

    //application.yml 정의한 레디스 관리자 비밀번호(보안을 위해 필수)
    @Value("${spring.redis.password}")
    private String redisPassword;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {

        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(redisHost);
        redisStandaloneConfiguration.setPort(Integer.parseInt(redisPort));
        redisStandaloneConfiguration.setPassword(redisPassword);
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);

        return lettuceConnectionFactory;
    }
}

 

application.yml 파일에 정의한 상수들을 가져와 커넥션하는 메서드에 넣는다.

 

 

5. @Cacheable 설정 관련 java 작성

import java.time.Duration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisCacheConfig {

    @Value("${spring.cache.redis.time-to-live}")
    private long cacheTtlInSeconds;

    @Bean
    public CacheManager devmkCacheManager(RedisConnectionFactory cf) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(
                        RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()))
//                .entryTtl(Duration.ofMinutes(3L));    기존 3분 (분)
                .entryTtl(Duration.ofSeconds(cacheTtlInSeconds)); // 현재 (초)
        return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf)
                .cacheDefaults(redisCacheConfiguration).build();
    }
}

여기서 빈으로 등록한 메소드명 devmkCacheManager@Cacheable cacheManager = "devmkCacheManager" 으로 사용한다.  캐싱 만료시간 (TTL)도 application.yml에 정의한 시간 프로퍼티값을 가져와서 사용한다.

 

6. RedisTemplate 서비스 작성

import java.util.concurrent.TimeUnit;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class RedisUtils {

    private final RedisTemplate<String, Object> redisTemplate;

    public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    //TTL(Time To Live)이란? 
    //레디스에서 데이터를 캐싱하는 생명주기시간
    
    //데이터 저장
    public void setData(String key, String value, Long expiredTime) {
    	//위 코드에서는 opsForValue().set() 메서드를 사용해 값을 저장하고 있으며, 
    	//expiredTime과 TimeUnit을 전달함으로써 만료 시간(예: 밀리초 단위)을 설정한다.
    	//이 만료 시간(TTL)이 지나면 Redis에 저장된 해당 키가 자동으로 삭제된다.
    	
        redisTemplate.opsForValue().set(key, value, expiredTime, TimeUnit.MILLISECONDS);
    }
    
    //데이터저장 (TTL없이 무기한)
    public void setDataWithoutExpire(String key, String value) {
        redisTemplate.opsForValue().set(key, value); // 만료 시간 없이 저장
    }
    
    //TTL 업데이트와 만료시간 관리
    public void updateExpire(String key, Long newExpireTime) {
    	//이미 설정된 키에 대해 TTL을 업데이트하거나 연장하려면 Redis의 expire() 메서드를 사용할 수도 있다.
        redisTemplate.expire(key, newExpireTime, TimeUnit.MILLISECONDS);
    }

    //데이터 조회
    public String getData(String key) {
    	//키를 이용해 Redis에 저장된 값을 조회하는 메서드이다.
    	//반환된 데이터가 String이라고 가정하고 형 변환((String))하고 있다.
        return (String) redisTemplate.opsForValue().get(key);
    }

    //데이터삭제
    public void deleteData(String key) {
    	//특정 키에 대한 데이터를 삭제하는 간단한 메서드 이다.
    	//이 메서드는 키가 Redis에서 즉시 제거된다.
        redisTemplate.delete(key);
    }

}

RedisTemplate로 CRUD할때 사용하는 서비스다. 

 

 

7. RedisTemplate로 CRUD 테스트하기

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.bind.annotation.*;

import kr.co.test.login.vo.TokenRequest;
import kr.co.test.util.RedisUtils;

@RestController
@RequestMapping("/api/crud")
public class RedisTemplateCRUDController {

    private final RedisUtils redisUtils;
    private static final Long EXPIRE_TIME = 600_000L; // 10분(밀리초 단위)

    public RedisTemplateCRUDController(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }
	
    //저장
    @PostMapping("/store")
    public ResponseEntity<String> storeToken(@RequestBody TokenRequest tokenRequest) {
        redisUtils.setData(tokenRequest.getToken(), "valid", EXPIRE_TIME);
        return ResponseEntity.status(HttpStatus.CREATED).body("JWT 토큰이 Redis에 저장되었습니다.");
    }
    
    //조회
    @GetMapping("/validate")
    public ResponseEntity<String> validateToken(@RequestParam(name = "token") String token) {
        String tokenStatus = redisUtils.getData(token);
        if (tokenStatus == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("유효하지 않은 토큰입니다.");
        }
        return ResponseEntity.ok("유효한 토큰입니다.");
    }
    //삭제
    @DeleteMapping("/delete")
    public ResponseEntity<String> deleteToken(@RequestParam(name = "token") String token) {
        redisUtils.deleteData(token);
        return ResponseEntity.ok("JWT 토큰이 삭제되었습니다.");
    }
    //업데이트
    @PutMapping("/refresh")
    public ResponseEntity<String> refreshToken(@RequestParam(name = "token") String token) {
        String tokenStatus = redisUtils.getData(token);
        if (tokenStatus == null) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("토큰이 존재하지 않습니다.");
        }
        redisUtils.updateExpire(token, EXPIRE_TIME);
        return ResponseEntity.ok("JWT 토큰의 만료 시간이 갱신되었습니다.");
    }
}

 

컨트롤러를 만들어준다. jwt토큰을 사용한다고 가정하자.

 

postman으로 테스트하면서 서버의 Redis CLI로  데이터가 어떻게 저장되고 보여지는지 확인하자

docker exec -it redis redis-cli
127.0.0.1:6379> AUTH {비밀번호}
OK

↑ docker redis cli 로그인 과정일뿐.

 

레디스 데이터 저장

POST방식 /api/crud/store

{ "token" : "devmk_token" }

 

 레디스 데이터 조회

GET방식 /api/crud/validate?token=devmk_token

127.0.0.1:6379> keys *
1) "devmk_token"
127.0.0.1:6379> get devmk_token
"valid"

내가 임의로 정한 토큰이름으로 "valid" 라는 값이 저장됐다.

 

레디스 데이터 삭제

DELETE방식 /api/crud/delete?token=devmk_token

127.0.0.1:6379> keys *
(empty array)

삭제를 했기때문에 저장된 데이터가 없다.

 

레디스 만료시간 업데이트

PUT방식 /api/crud/refresh?token=devmk_token

 

 

8. 레디스 캐싱 이용하기

캐싱하는 서비스 작성

@Slf4j
@Service
@RequiredArgsConstructor
public class CacheService {

    @Cacheable(cacheNames = "getAll", key = "#root.target + #root.methodName", sync = true, cacheManager = "devmkCacheManager")
    public List<CacheVo> getAll() {

        log.info(" ::::: 캐싱대상 서비스 입니다.:::::");
        List<CacheVo> dummyList = new ArrayList<>();
        dummyList.add(new CacheVo("홍길동", "20", "남자"));
        dummyList.add(new CacheVo("임꺽정", "30", "여자"));
        dummyList.add(new CacheVo("김민호", "30", "남자"));

        return dummyList;

    }

}

 

임의로 더미데이터를 만드는 서비스를 작성한다.

 

컨트롤러 작성

import java.util.List;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import kr.co.test.cache.service.CacheService;
import kr.co.test.cache.vo.CacheVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequestMapping("/cache")
@RequiredArgsConstructor
public class CacheController {

    private final CacheService cacheService;

    @GetMapping("/getAll")
    public ResponseEntity<List<CacheVo>> getAll() {

        log.info(" :::: 레디스 캐싱 서비스를 호출을 시작 합니다. ::::");
        List<CacheVo> body = cacheService.getAll();
        log.info(" :::: 레디스 캐싱 서비스를 호출을 종료 합니다. ::::");

        return ResponseEntity.ok(body);
    }
}

 

레디스 데이터 캐싱하기

GET방식 /cache/getAll

[{"age": "홍길동","name": "20","sex": "남자"}
,{"age": "임꺽정","name": "30","sex": "여자"}
,{"age": "김민호","name": "30","sex": "남자"}]
형태의 데이터가 저장됐다.
 
127.0.0.1:6379> keys *
1) "getAll::kr.co.test.cache.service.CacheService@14f9a8c9getAll"
 

key명은 getAll::kr.co.test.cache.service.CacheService@14f9a8c9getAll 이다.

위의 키명으로 임의로 만든 배열이 저장됐다.

@Cacheable key는 아무거나 지정해도된다. 나는 단지 호출한서비스의 path를 key명으로 사용했을뿐이다.

127.0.0.1:6379> get getAll::kr.co.test.cache.service.CacheService@14f9a8c9getAll
"[\"java.util.ArrayList\",[{\"@class\":\"kr.co.test.cache.vo.CacheVo\"
,\"age\":\"\xed\x99\x8d\xea\xb8\xb8\xeb\x8f\x99\"
,\"name\":\"20\",\"sex\":\"\xeb\x82\xa8\xec\x9e\x90\"}
,{\"@class\":\"kr.co.test.cache.vo.CacheVo\"
,\"age\":\"\xec\x9e\x84\xea\xba\xbd\xec\xa0\x95\"
,\"name\":\"30\",\"sex\":\"\xec\x97\xac\xec\x9e\x90\"}
,{\"@class\":\"kr.co.test.cache.vo.CacheVo\"
,\"age\":\"\xea\xb9\x80\xeb\xaf\xbc\xed\x98\xb8\"
,\"name\":\"30\",\"sex\":\"\xeb\x82\xa8\xec\x9e\x90\"}]]"

 

redis cli에서 확인해보지 이런식으로 저장됐다. 서버에는 인코딩이 깨지지만 자바에서 불러올땐 제대로 나옴

 

127.0.0.1:6379> get getAll::kr.co.test.cache.service.CacheService@14f9a8c9getAll
(nil)

 

캐싱시간을 yml파일에 정의한 5초 이기때문에 5초후 다시 호출하면 값이 Null이다.

5초동안 레디스가 캐싱한 데이터를 대신 보여주고 CacheService.getAll() 서비스를 호출하지 않는 뜻이다.

 

대충 사용방법은 다 작성한것 같다.

 

다음은 jwt+redis 이용한 토큰관리를 포스트하겠다~

반응형