Redis 레디스 + 스프링부트 SpringBoot 연동 RedisTemplate, @Cacheable 사용하기
레디스 사용방법을 간단하게 작성하겠다.
첫번째. 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
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 이용한 토큰관리를 포스트하겠다~