스프링 부트에서 레디스를 사용하려면 build.gradle 파일에 레디스 의존성을 추가해줘야 한다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
그리고 사용할 레디스의 호스트와 포트를 지정해준다. application.yml 파일에 지정하며, 로컬에서 레디스를 사용한다면 localhost, 다른 서버나 도커 등을 사용한다면 그에 맞는 호스트로 설정해준다. default port는 6379이다.
spring:
redis:
host: localhost
port: 6379
마지막으로 애플리케이션에서 레디스와 연동하기 위해 별도의 값을 세팅하고 빈에 등록해준다. 자바의 Redis Client 라이브러리는 Jedis와 Lettuce가 있는데, Lettuce가 성능이 더 좋기 때문에 Lettuce로 RedisConnectionFactory를 통해 Redis와 연결한다.
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
// 일반적인 key:value의 경우 시리얼라이저
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
// Hash를 사용할 경우 시리얼라이저
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
// 모든 경우
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
레디스를 사용하는 방법은 크게 2가지로 나뉜다.
Repository로 사용하면 Entity를 만들어서 쉽게 사용할 수 있다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@RedisHash(value = "refresh_token")
public class RefreshToken {
@Id
private String authId;
@Indexed
private String token;
private String role;
@TimeToLive
private long ttl;
public RefreshToken update(String token, long ttl) {
this.token = token;
this.ttl = ttl;
return this;
}
}
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {
Optional<RefreshToken> findByToken(String token);
Optional<RefreshToken> findByAuthId(String authId);
}
JpaRepository를 사용하는 것 처럼, CrudRepository 인터페이스를 상속받는다. @Id 또는 @Indexed 어노테이션을 적용한 프로퍼티들만 CrudRepository가 제공하는 findBy~ 구문을 사용할 수 있다.
@Service
public class RedisUtils {
private final RedisTemplate<String, Object> redisTemplate;
public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void setData(String key, String value,Long expiredTime){
redisTemplate.opsForValue().set(key, value, expiredTime, TimeUnit.MILLISECONDS);
}
public String getData(String key){
return (String) redisTemplate.opsForValue().get(key);
}
public void deleteData(String key){
redisTemplate.delete(key);
}
}
레디스 템플릿은 사용하는 자료구조마다 제공하는 메서드가 다르기 때문에 객체를 만들어서 레디스의 자료구조 타입에 맞는 메소드를 사용하면 된다.
| 메서드 명 | 레디스 타입 |
|---|---|
| opsForValue | String |
| opsForList | List |
| opsForSet | Set |
| opsForZSet | Sorted Set |
| opsForHash | Hash |
데이터를 저장할 때 만료 시간 지정할 시에는 해당 시간의 단위까지 지정해주면 된다. 위의 코드에서는 밀리 초(TimeUnit.MILLISECONDS)로 적용되어 있다.
레디스는 데이터베이스로도 사용되고, Message Broker로도 사용되지만 Cache Manager의 용도로도 많이 사용된다. 레디스가 제공하는 캐싱 기능을 사용하려면 캐싱 관련 어노테이션부터 알아야 한다.
| Element | Description | Type |
|---|---|---|
| cacheName | 캐시 이름 (설정 메서드 리턴값이 저장되는) | String[] |
| value | cacheName의 별칭 | String[] |
| key | 동적인 키 값을 사용하는 SpEL 표현식 동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값 | String[] |
| condition | SpEL 표현식이 참일 경우에만 캐싱 적용 - or, and 등 조건식, 논리연산 가능 | String[] |
| unless | 캐싱을 막기 위해 사용되는 SpEL 표현식 condition과 반대로 참일 경우에만 캐싱이 적용되지 않음 | String[] |
| cacheManager | 사용 할 CacheManager 지정 (EHCacheCacheManager, RedisCacheManager 등) | String[] |
| sync | 여러 스레드가 동일한 키에 대한 값을 로드하려고 할 경우, 기본 메서드의 호출을 동기화 즉, 캐시 구현체가 Thread safe 하지 않는 경우, 캐시에 동기화를 걸 수 있는 속성 | boolean[] |
| Element | Description | Type |
|---|---|---|
| cacheName | 입력할 캐시 이름 | String[] |
| value | cacheNamed의 별칭 | String[] |
| key | 동적인 키 값을 사용하는 SpEL 표현식 동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값 | String |
| cacheManager | 사용 할 CacheManager 지정 (EHCacheCacheManager, RedisCacheManager 등) | String |
| condition | SpEL 표현식이 참일 경우에만 캐싱 적용 - or, and 등 조건식, 논리연산 가능 | String |
| unless | 캐싱을 막기 위해 사용되는 SpEL 표현식 condition과 반대로 참일 경우에만 캐싱이 적용되지 않음 | String |
| Element | Description | Type |
|---|---|---|
| cacheName | 제거할 캐시 이름 | String[] |
| value | cacheName의 별칭 | String[] |
| key | 동적인 키 값을 사용하는 SpEL 표현식 동일한 cache name을 사용하지만 구분될 필요가 있을 경우 사용되는 값 | String |
| allEntries | 캐시 내의 모든 리소스를 삭제할지의 여부 | boolean |
| condition | SpEL 표현식이 참일 경우에만 삭제 진행 - or, and 등 조건식, 논리연산 가능 | String |
| cacheManager | 사용 할 CacheManager 지정 (EHCacheCacheManager, RedisCacheManager 등) | String |
| beforeInvocation | true - 메서드 수행 이전 캐시 리소스 삭제 false - 메서드 수행 후 캐시 리소스 삭제 | boolean |
@SpringBootApplication
@EnableCaching
public class SpringBootRedisSimpleStarterApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootRedisSimpleStarterApplication.class, args);
}
}
spring:
cache:
type: redis
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public CacheManager rcm(RedisConnectionFactory cf) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(3L));
return RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(cf)
.cacheDefaults(redisCacheConfiguration)
.build();
}
}
@RequestMapping("/log")
@RestController
@RequiredArgsConstructor
public class LogController {
private final LogService logService;
@GetMapping
public ResponseEntity<List<LogResponse>> getAll() {
List<LogResponse> logs = logService.searchAll();
return ResponseEntity.ok(logs);
}
@DeleteMapping
public void deleteAll() {
logService.removeAll();
}
}
@Cacheable(cacheNames = "searchAll", key = "#root.target + #root.methodName", sync = true, cacheManager = "rcm")
public List<LogResponse> searchAll() {
return logFacade.findAllOrderByDateAtDesc()
.stream()
.map(LogResponse::new)
.collect(Collectors.toList());
}
@CacheEvict(cacheNames = "searchAll", allEntries = true, beforeInvocation = true, cacheManager = "rcm")
@Transactional
public void removeAll() {
logFacade.removeAll();
}
public class Ref {
final String Spring boot Redis 사용하기 = "https://westmino.tistory.com/157";
final String [Spring Boot] Redis 사용하기 = "https://velog.io/@yoojkim/Spring-Boot-Redis-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0";
final String Jedis 보다 Lettuce 를 쓰자 = "https://jojoldu.tistory.com/418";
final String Spring Cache, 제대로 사용하기 = "https://gngsn.tistory.com/157";
final String Spring boot에서 Redis Cache 사용하기 = "https://deveric.tistory.com/98";
final String SpringBoot에서 Redis 캐시를 사용하기 = "https://www.wool-dev.com/backend-engineering/spring/springboot-redis-cache#%EC%82%AC%EC%9A%A9-%ED%95%A0-redis-%EA%B0%84%EB%8B%A8-%EC%84%A4%EB%AA%85";
final String [JAVA Spring Boot] Rest API + 레디스 캐시 (Redis Cache) 적용 및 샘플 예제 = "https://kim-oriental.tistory.com/28";
}