// Redis에 HTTP 세션 데이터를 저장하여 세션 클러스터링 및 영속화 지원
implementation 'org.springframework.session:spring-session-data-redis'
// Spring Data Redis를 통해 Redis 데이터베이스와 상호작용하는 기본 기능 제공
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
spring:
data:
redis:
host: redis_server
port: redis_port
password: # 비밀번호가 있다면 여기에 입력
timeout: 5000ms
lettuce:
pool:
max-active: 8 # 풀에서 유지할 최대 활성(사용 중이거나 사용 가능한) 연결 수
max-idle: 8 # 풀에서 유지할 최대 유휴(사용 가능) 연결 수
min-idle: 0 # 풀에서 유지할 최소 유휴(사용 가능) 연결 수
max-wait: -1ms # 풀에서 연결을 얻기 위해 대기할 최대 시간 (음수 값은 무한대 대기를 의미)
📦 main
┗ 📂 java
┗ 📂 com.demo.redis_spring
┣ 📂 config
┃ ┗ ⚙️ RedisConfig
┃ - Redis 연결 설정을 담당하는 클래스입니다. Redis 서버의 호스트, 포트, 비밀번호, 연결 풀 설정 등을 정의하며, RedisTemplate 빈을 생성합니다.
┣ 📂 controller
┃ ┗ ⚙️ RedisCommonController
┃ - 모든 Redis 자료구조(String, List, Set, ZSet, Hash)에 대한 공통적인 CRUD(생성, 조회, 삭제, 존재 확인) 작업을 처리하는 REST API 엔드포인트를 제공합니다.
┣ 📂 exception
┃ ┗ 🚫 GlobalExceptionHandler
┃ - 애플리케이션 전반에서 발생하는 예외를 중앙에서 처리하는 클래스입니다. 특히 Redis 관련 예외(WRONGTYPE 등)를 포함하여 클라이언트에게 친화적인 에러 응답을 제공합니다.
┣ 📂 model
┃ ┣ 🎛️ RedisDataType
┃ ┃ - Redis 데이터 타입(STRING, LIST, SET, ZSET, HASH)을 정의하는 Enum 클래스입니다. 각 타입에 해당하는 서비스 빈 이름을 포함하여 디스패처 서비스에서 활용됩니다.
┃ ┗ 🧩 RedisRequest
┃ - 클라이언트로부터 Redis 작업 요청을 받을 때 사용되는 DTO(Data Transfer Object)입니다. 키, 값, 만료 시간, 멤버, 스코어, 필드 등 다양한 Redis 작업에 필요한 파라미터를 캡슐화합니다.
┣ 📂 service
┃ ┣ 🗄️ RedisDispatcherService
┃ ┃ - 클라이언트 요청의 Redis 데이터 타입에 따라 적절한 RedisOperationService 구현체로 작업을 위임하는 핵심 디스패처 서비스입니다. 각 자료구조별 서비스의 고유 메서드에 대한 직접 접근도 제공합니다.
┃ ┗ 🗃️ RedisOperationService
┃ - Redis String, List, Set, ZSet, Hash 등 모든 Redis 자료구조 서비스 구현체들이 공통적으로 구현해야 할 기본 Redis 작업(setData, getData, deleteData, hasKey)을 정의하는 인터페이스입니다.
┗ 📂 type
┣ 📂 hash
┃ ┣ 📂 controller
┃ ┃ ┗ ⚙️ RedisHashController
┃ ┃ - Redis Hash 타입 데이터에 특화된 웹 요청을 처리하는 REST API 엔드포인트를 제공합니다. Hash의 특정 필드 저장/조회, 모든 필드 조회, 필드 삭제 등의 고유 작업을 담당합니다.
┃ ┗ 📂 service
┃ ┗ 🗄️ RedisHashServiceImpl
┃ - Redis Hash 자료구조에 대한 실제 데이터베이스 연산(필드 저장, 조회, 삭제 등)을 구현하는 서비스 클래스입니다. RedisOperationService 인터페이스를 구현합니다.
┣ 📂 list
┃ ┣ 📂 controller
┃ ┃ ┗ ⚙️ RedisListController
┃ ┃ - Redis List 타입 데이터에 특화된 웹 요청을 처리하는 REST API 엔드포인트를 제공합니다. 리스트의 양 끝에 요소 추가/제거, 범위 조회 등의 고유 작업을 담당합니다.
┃ ┗ 📂 service
┃ ┗ 🗄️ RedisListServiceImpl
┃ - Redis List 자료구조에 대한 실제 데이터베이스 연산(요소 추가, 조회, 제거 등)을 구현하는 서비스 클래스입니다. RedisOperationService 인터페이스를 구현합니다.
┣ 📂 set
┃ ┣ 📂 controller
┃ ┃ ┗ ⚙️ RedisSetController
┃ ┃ - Redis Set 타입 데이터에 특화된 웹 요청을 처리하는 REST API 엔드포인트를 제공합니다. 멤버 추가/제거, 모든 멤버 조회, 멤버 존재 여부 확인 등의 고유 작업을 담당합니다.
┃ ┗ 📂 service
┃ ┗ 🗄️ RedisSetServiceImpl
┃ - Redis Set 자료구조에 대한 실제 데이터베이스 연산(멤버 추가, 조회, 제거 등)을 구현하는 서비스 클래스입니다. RedisOperationService 인터페이스를 구현합니다.
┣ 📂 string
┃ ┣ 📂 controller
┃ ┃ ┗ ⚙️ RedisStringController
┃ ┃ - Redis String 타입 데이터에 특화된 웹 요청을 처리하는 REST API 엔드포인트를 제공합니다. (예: 값 증가/감소 등). 공통 CRUD는 RedisCommonController에서 처리합니다.
┃ ┗ 📂 service
┃ ┗ 🗄️ RedisStringServiceImpl
┃ - Redis String 자료구조에 대한 실제 데이터베이스 연산(값 설정, 조회 등)을 구현하는 서비스 클래스입니다. RedisOperationService 인터페이스를 구현합니다.
┗ 📂 zset
┣ 📂 controller
┃ ┗ ⚙️ RedisZSetController
┃ - Redis Sorted Set (ZSet) 타입 데이터에 특화된 웹 요청을 처리하는 REST API 엔드포인트를 제공합니다. 멤버와 스코어 추가/조회, 스코어 범위 조회 등의 고유 작업을 담당합니다.
┗ 📂 service
┗ 🗄️ RedisZSetServiceImpl
- Redis Sorted Set (ZSet) 자료구조에 대한 실제 데이터베이스 연산(멤버와 스코어 추가, 조회, 제거 등)을 구현하는 서비스 클래스입니다. RedisOperationService 인터페이스를 구현합니다.
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
// Key 직렬화 (String)
redisTemplate.setKeySerializer(new StringRedisSerializer());
// Value 직렬화 (JSON)
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// Hash Key 직렬화 (String)
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// Hash Value 직렬화 (JSON)
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet(); // 직렬화 설정을 적용
return redisTemplate;
}
}
/**
* Redis 작업 요청을 위한 DTO
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RedisRequest implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String type; // Redis 데이터 타입 (예: "STRING", "LIST", "SET", "ZSET", "HASH")
private String key; // Redis 키
private Object value; // 저장할 값 (String, JSON 객체 등, 단일 값 또는 HASH/ZSET의 다중 값 Map)
private int expirationSeconds; // 만료 시간 (초, 0 이하는 만료 없음)
private Object member; // SET/ZSET 작업 시 단일 멤버
private double score; // ZSET 작업 시 스코어
private double maxScore; // ZSET 범위 조회 시 최대 스코어
private double minScore; // ZSET 범위 조회 시 최소 스코어
private String field; // HASH 작업 시 필드
}
/**
* 모든 Redis 데이터 타입에 대한 공통 웹 요청(생성, 조회, 삭제, 존재 확인)을 처리하는 컨트롤러입니다.
* RedisRequest DTO의 'type' 필드를 사용하여 적절한 서비스로 작업을 위임합니다.
*/
@RestController
@RequestMapping("/redis") // 최상위 /redis 경로를 사용하여 범용 엔드포인트를 제공
public class RedisCommonController {
private final RedisDispatcherService redisDispatcherService;
public RedisCommonController(RedisDispatcherService redisDispatcherService) {
this.redisDispatcherService = redisDispatcherService;
}
/**
* 지정된 타입에 따라 Redis에 데이터를 저장하고 만료를 처리합니다.
* RedisRequest Body의 'type' 필드에 따라 STRING, LIST, SET, ZSET, HASH 서비스로 위임됩니다.
* - STRING: 단일 값 저장
* - LIST: 리스트의 오른쪽에 요소 추가 (RPUSH)
* - SET: Set에 멤버 추가
* - ZSET: Sorted Set에 멤버 (스코어 0.0) 또는 맵 형태로 여러 멤버/스코어 추가
* - HASH: Hash에 맵 형태로 여러 필드/값 추가 (HMSET)
*
* @param request 타입, 키, 값, 만료 시간을 포함하는 RedisRequest 객체
* @return 확인 메시지
* @throws IllegalArgumentException 지원되지 않는 타입이거나 필수 필드가 누락된 경우
*/
@PostMapping("/set")
public String setData(@RequestBody RedisRequest request) {
// 필수 필드 유효성 검사
if (request.getType() == null || request.getKey() == null || request.getValue() == null) {
throw new IllegalArgumentException("Error: Type, key, and value are required in the request body.");
}
Optional<RedisDataType> type = RedisDataType.fromString( request.getType() );
String key = request.getKey();
Object value = request.getValue();
int expirationSeconds = request.getExpirationSeconds();
if (expirationSeconds > 0) {
redisDispatcherService.dispatchSetDataWithExpiration(type.orElseThrow().getServiceBeanName(), key, value, expirationSeconds);
return "Data set with expiration (" + expirationSeconds + "s) successfully for type " + type.orElseThrow().name() + "!";
} else {
redisDispatcherService.dispatchSetData(type.orElseThrow().getServiceBeanName(), key, value);
return "Data set successfully for type " + type.orElseThrow().name() + "!";
}
}
/**
* 지정된 타입에 따라 Redis에서 데이터를 가져옵니다.
* URL 쿼리 파라미터로 타입과 키를 받습니다.
* @param typeString Redis 데이터 타입 문자열 (예: "STRING", "LIST")
* @param key Redis 키
* @return 가져온 데이터 (String, List, Set, Map 등)
* @throws IllegalArgumentException 지원되지 않는 타입인 경우
*/
@GetMapping("/get")
public Object getData(@RequestParam(value = "type") String typeString, @RequestParam(value = "key") String key) {
RedisDataType type = RedisDataType.fromString(typeString)
.orElseThrow(() -> new IllegalArgumentException("Invalid Redis type: " + typeString));
return redisDispatcherService.dispatchGetData(type.getServiceBeanName(), key);
}
/**
* 지정된 타입에 따라 Redis에서 데이터를 삭제합니다.
* URL 쿼리 파라미터로 타입과 키를 받습니다.
* @param typeString Redis 데이터 타입 문자열
* @param key Redis 키
* @return 확인 메시지
* @throws IllegalArgumentException 지원되지 않는 타입인 경우
*/
@DeleteMapping("/delete")
public String deleteData(@RequestParam(value = "type") String typeString, @RequestParam(value = "key") String key) {
RedisDataType type = RedisDataType.fromString(typeString)
.orElseThrow(() -> new IllegalArgumentException("Invalid Redis type: " + typeString));
if (redisDispatcherService.dispatchDeleteData(type.getServiceBeanName(), key)) {
return "Data deleted successfully for type " + type.name() + "!";
}
return "Failed to delete data or key not found for type " + type.name() + ".";
}
/**
* 지정된 타입에 따라 Redis에 키가 존재하는지 확인합니다.
* URL 쿼리 파라미터로 타입과 키를 받습니다.
* @param typeString Redis 데이터 타입 문자열
* @param key Redis 키
* @return 키 존재 여부
* @throws IllegalArgumentException 지원되지 않는 타입인 경우
*/
@GetMapping("/has")
public boolean hasKey(@RequestParam(value = "type") String typeString, @RequestParam(value = "key") String key) {
RedisDataType type = RedisDataType.fromString(typeString)
.orElseThrow(() -> new IllegalArgumentException("Invalid Redis type: " + typeString));
return redisDispatcherService.dispatchHasKey(type.getServiceBeanName(), key);
}
}
/**
* Redis 작업 공통 인터페이스. 각 구현체는 Redis 데이터 타입별(String, List 등) 작업을 처리합니다.
*/
public interface RedisOperationService {
/**
* Redis에 데이터를 저장합니다.
* @param key 저장할 키
* @param value 저장할 값
*/
void setData(String key, Object value);
/**
* Redis에 데이터를 저장하고 만료 시간을 설정합니다.
* @param key 저장할 키
* @param value 저장할 값
* @param timeoutSeconds 만료 시간 (초)
*/
void setDataWithExpiration(String key, Object value, long timeoutSeconds);
/**
* Redis에서 데이터를 가져옵니다.
* @param key 가져올 키
* @return 저장된 값 또는 요소들
*/
Object getData(String key);
/**
* Redis에서 데이터를 삭제합니다.
* @param key 삭제할 키
* @return 삭제 성공 여부
*/
Boolean deleteData(String key);
/**
* 특정 키의 존재 여부를 확인합니다.
* @param key 확인할 키
* @return 존재 여부
*/
Boolean hasKey(String key);
}
/**
* Redis Hash 타입 작업 서비스 구현체입니다. RedisOperationService 인터페이스를 구현합니다.
*/
@Service("hashRedisService") // 디스패처에 주입하기 위한 특정 빈 이름
@Slf4j
public class RedisHashServiceImpl implements RedisOperationService {
private final HashOperations<String, String, Object> hashOperations; // HashOperations 사용
public RedisHashServiceImpl(RedisTemplate<String, Object> redisTemplate) {
// HashOperations는 Hash Key를 String으로, Hash Value를 Object로 사용하도록 지정
this.hashOperations = redisTemplate.opsForHash();
}
// --- RedisOperationService 인터페이스 Hash 타입 메서드 구현 ---
/**
* Hash 타입의 'setData'는 Map<String, Object> 형태의 value를 받아 여러 필드-값 쌍을 Hash에 저장합니다. (HMSET/HSET)
* value가 Map이 아니면 지원하지 않습니다.
* @param key Hash의 키
* @param value 저장할 필드-값 쌍 (Map<String, Object> 형태)
*/
@Override
public void setData(String key, Object value) {
if (value instanceof Map) {
Map<String, Object> entries = (Map<String, Object>) value;
hashOperations.putAll(key, entries); // 여러 필드-값 쌍 저장
log.info("[HashService] Stored multiple entries in Hash via setData: {} -> {}", key, entries);
} else {
log.error("[HashService] setData for HASH type requires a Map<String, Object> value. Received: {}", value.getClass().getName());
throw new IllegalArgumentException("For HASH type, 'value' must be a Map<String, Object>.");
}
}
/**
* Hash 타입의 'setDataWithExpiration'은 Map<String, Object> 형태의 value를 받아 Hash에 저장하고, 해당 키(Hash 전체)에 만료 시간을 설정합니다.
* @param key Hash의 키
* @param value 저장할 필드-값 쌍
* @param timeoutSeconds 만료 시간 (초)
*/
@Override
public void setDataWithExpiration(String key, Object value, long timeoutSeconds) {
setData(key, value); // 필드-값 쌍 추가는 setData 로직 재사용
hashOperations.getOperations().expire(key, Duration.ofSeconds(timeoutSeconds)); // Hash 키에 TTL 설정
log.info("[HashService] Stored entries in Hash via setDataWithExpiration: {} -> {}. TTL set for key: {}s", key, value, timeoutSeconds);
}
/**
* Hash 타입의 'getData'는 Hash의 모든 필드-값 쌍을 가져오는 것으로 해석됩니다. (HGETALL)
* @param key Hash의 키
* @return Hash의 모든 필드-값 쌍 (Map<String, Object>)
*/
@Override
public Map<String, Object> getData(String key) {
Map<String, Object> entries = hashOperations.entries(key);
log.info("[HashService] Retrieved all Hash entries: {} -> {}", key, entries);
return entries != null ? entries : Collections.emptyMap();
}
@Override
public Boolean deleteData(String key) {
Boolean deleted = hashOperations.getOperations().delete(key);
log.info("[HashService] Delete data: {} -> {}", key, deleted);
return deleted;
}
@Override
public Boolean hasKey(String key) {
Boolean exists = hashOperations.getOperations().hasKey(key);
log.info("[HashService] Has key: {} -> {}", key, exists);
return exists;
}
...
}
postmen
redis server
📌
redis_spring
에 있습니다.📚 참고