-
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 이용한 토큰관리를 포스트하겠다~
반응형'Spring Boot' 카테고리의 다른 글
스프링 Spring @Autowired? @Component? @Bean?차이 (0) 2023.07.16 스프링부트 3.0 javax/servlet/jsp/tagext/TagLibraryValidator 에러 해결하기 (0) 2023.06.10 스프링부트 배포시 ORA-01882: timezone region not found 오류 해결하기 (0) 2022.09.18 스프링부트 EC2 배포시 org.thymeleaf.exceptions.TemplateInputException 에러 (0) 2022.09.18 [몽고DB] SpringBoot 스프링부트 + 몽고DB 연동 및 CURD (0) 2022.08.07