ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Redis 레디스 + 스프링부트 SpringBoot 연동 RedisTemplate, @Cacheable 사용하기
    Spring Boot 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 이용한 토큰관리를 포스트하겠다~

    반응형

    댓글

Designed by Tistory.