Spring에서 Redis 사용하기 (RedisTemplate)

2025. 8. 17. 18:54·Spring Framework/Spring

Spring에서 Redis 서버에 연결하려면 무엇이 필요할까?

 

 

Spring에서 Redis에 연결하려면 클라이언트 드라이버(Lettuce, Jedis 등)가 필요합니다.
RedistTemplate은 RedisConnectionFactory를 통해 RedisConnection을 얻고 이 커넥션을 사용해 명령을 실행한 뒤 반납합니다.
RedisConnection은 Spring Data Redis가 제공하는 추상화 계층으로 내부적으로 Lettuce나 Jedis 같은 클라이언트 라이브러리를 사용해 실제 Redis 서버와 통신합니다.

Spring Data Redis는 클라이언트별로 전용 팩토리를 제공합니다. 즉, 어떤 클라이언트를 쓸지는 어떤 커넥션 팩토리를 생성하느냐로 결정됩니다.

  • LettuceConnectionFactory : Netty 기반, 동기/비동기/리액티브 지원(요즘 기본으로 가장 많이 사용)
  • JedisConnectionFactory : 전통적인 Jedis 클라이언트 기반(주로 동기식)

이제 설정 단계와 사용 단계를 구분해서 보겠습니다.

  • 설정 단계: Lettuce 또는 Jedis 중 하나를 선택해 RedisConnectionFactory를 빈으로 등록합니다.
  • 사용 단계: RedisTemplate 같은 컴포넌트가 이 팩토리를 통해 Redis 연결을 사용합니다.

참고: Spring Boot 기본값은 Lettuce이므로 별도 Bean 없이도 application.yml만으로 자동 구성할 수 있습니다. 커스터마이징이 필요할 때 Bean을 직접 정의합니다.

자동 구성 예시

# application.yml
spring:
  data:
    redis:
      host: localhost
      ort: 6379     

수동 구성 Bean 정의 예시

@Configuration
public class RedisConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        // localhost:6379 에 연결하는 Lettuce 기반 ConnectionFactory
        return new LettuceConnectionFactory("localhost", 6379);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 직렬화기 단순 설정 (키=문자열, 값=JSON)
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));

        return template;
    }
}

지금까지 Spring프로젝트에서 Redis를 사용하기 위한 설정을 정리하면 아래와 같습니다.

  1. 클라이언트 드라이버 선택(Lettuce)
  2. 그에 맞는 커넥션 팩토리 빈 등록(LettuceConnectionFactory)
  3. RestTemplate이 팩토리를 통해 Connection 사용
  4. 직렬화 설정

이젠 ConnectionFactory를 통해 Redis연결을 사용한 RedisTemplate 에 대해 알아보겠습니다.

RedisTemplate

Spring에서는 DB 작업할 때 JdbcTemplate, MongoDB에서는 MongoTemplate 같은 게 있죠.
마찬가지로 Redis와 상호작용할 때 쓰는 게 RedisTemplate 입니다.

RedisTemplate은 Redis와 연결을 맺고 Redis의 String, List, Set, Hash 등 다양한 자료구조 명령을 추상화하여 자바 코드로 손쉽게 저장·조회·삭제를 할 수 있게 도와주는 Spring Data Redis에서 제공하는 클래스입니다.

1. 역할

  • Redis에 데이터 넣기 (SET, HSET, LPUSH …)
  • Redis에서 데이터 가져오기 (GET, HGET, LRANGE …)
  • Redis에서 데이터 지우기 (DEL, EXPIRE …)

이런 Redis 명령어들을 자바 메서드 형태로 편하게 제공해줘요.

2. 구조

RedisTemplate<K, V> 형태로 선언합니다.

  • K: Redis key의 타입
  • V: Redis value의 타입

예:

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);

    // 직렬화기 단순 설정 (키=문자열, 값=JSON)
    template.setKeySerializer(new StringRedisSerializer());
    template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));

    return template;
}

3. 제공하는 Operation 인터페이스

RedisTemplate은 Redis의 여러 자료구조에 맞춘 opsForXXX() 메서드를 제공합니다

  • opsForValue() → String (Key-Value)
redisTemplate.opsForValue().set("key", "value");
String v = redisTemplate.opsForValue().get("key");
  • opsForList() → Redis List
redisTemplate.opsForList().rightPush("listKey", "item1");
//list의 처음부터 끝까지 탐색(오른쪽부터 -1,-2.. 로 인덱스를 셀 수 있다) 
List<String> list = redisTemplate.opsForList().range("listKey", 0, -1);
  • opsForSet() → Redis Set
redisTemplate.opsForSet().add("setKey", "a", "b", "c");
Set<String> members = redisTemplate.opsForSet().members("setKey");
  • opsForHash() → Redis Hash
redisTemplate.opsForHash().put("hashKey", "field1", "value1");
String fieldValue = (String) redisTemplate.opsForHash().get("hashKey", "field1");

 

[참고] RedisTemplate vs StringRedisTemplate

  • RedisTemplate<K, V>
    • 목적: 범용 템플릿 (문자열, 객체, 컬렉션 모두)
    • 키/값 타입: 제네릭(K,V) — 자유롭게 지정
    • 직렬화: 명시적으로 설정.
    • 사용처: 도메인 객체 캐싱, Hash/List/Set/ZSet 등 자료구조 활용
  • StringRedisTemplate
    • 목적: 문자열 전용 템플릿(= 미리 구성된 RedisTemplate<String, String>)
    • 키/값 타입: String 고정
    • 직렬화: key/value/hash 전부 StringRedisSerializer로 기본 세팅
    • 사용처: 간단한 문자열/카운터/락 키, 토큰/코드 저장, INCR/DECR/SETNX 등

 


RedisTemplat을 설정할 때 key와 value의 Serializer를 지정해줄 수 있는데요. Serializer에 대해 알아보겠습니다.

Serializer

Serializer가 필요한 이유 

Redis는 메모리 기반 Key-Value 저장소이기 때문에 내부적으로는 바이트 배열 형태로만 데이터를 다룹니다.
따라서 자바 객체를 Redis에 저장하거나 Redis에서 다시 객체로 꺼내오려면 직렬화(Serialize), 역직렬화(Deserialize) 과정이 필요합니다.

  • 저장 시: 자바 객체 → 직렬화 → 바이트 스트림 → Redis 저장
  • 조회 시: Redis byte[] 데이터 → 역직렬화 → 자바 객체 변환

Spring Data Redis에서 기본 제공하는 RedisTemplate도 이 과정을 위해 기본 직렬화기가 설정돼 있으며  JdkSerializationRedisSerializer가 기본값입니다.

실제 서비스에서는 StringRedisSerializer, Jackson2JsonRedisSerializer 등을 지정해 더 가독성 있고 호환성 좋은 방식으로 직렬화를 합니다.

 

[출처:  https://medium.com/@betul5634/redis-serialization-with-spring-redis-data-lettuce-codec-1a1d2bc73d26 ]

Spring Data Redis에서 제공하는 기본 Serializer들

Spring Data Redis는 RedisTemplate 설정 시 사용할 수 있는 다양한 Serializer 클래스를 기본 제공하며, 저장할 데이터의 유형에 따라 선택할 수 있습니다.

기본 제공 항목:

  • GenericJackson2JsonRedisSerializer
  • Jackson2JsonRedisSerializer
  • StringRedisSerializer : key 에 주로 사용
  • JdkSerializationRedisSerializer : 기본 값

 

Jackson2JsonRedisSerializer , GenericJackson2JsonRedisSerializer 차이

타입 메타데이터 포함 여부와 사용 용도에서 중요한 차이가 있습니다.

구분 Jackson2JsonRedisSerializer GenericJackson2JsonRedisSerializer
타입 메타데이터 없음 @class 필드 포함
직렬화 결과 JSON 깔끔, 표준 JSON 메타데이터가 붙어 다소 복잡
역직렬화 방식 명시적으로 타입 지정 필요 @class 정보로 자동 타입 추론 가능
장점 외부 시스템과 연동 쉬움
호환성 높음
다양한 타입 저장/조회 시 편리
자동 타입 매핑
단점 타입 지정 필수 패키지 포함 클래스 경로 노출(보안/유지보수 측면에서 부담)
JSON 가독성 낮음

예시 코드로 본 차이

User user = new User("kim", 30);

Jackson2JsonRedisSerializer 결과

{
  "name": "kim",
  "age": 30
}

GenericJackson2JsonRedisSerializer 결과

{
  "@class": "com.example.User",
  "name": "kim",
  "age": 30
}

 


그렇다면, 어떤 Serializer를 어떻게 프로젝트에 적용했나

StringRedisSerializer 사용

@Bean // 스프링 빈 등록: RedisTemplate<String, String>
public RedisTemplate<String, String> defaultRedisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
    RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); 
  
    // 공통 직렬화/커넥션 설정 적용 후 반환
    StringRedisSerializer s = new StringRedisSerializer(); // 문자열 직렬화기 생성(UTF-8 기반)
    redisTemplate.setKeySerializer(s); // 키 직렬화: String -> byte[]
    redisTemplate.setValueSerializer(s); // 값 직렬화: String -> byte[] (JSON 문자열 등을 저장할 때 적합)
    redisTemplate.setHashKeySerializer(s); // 해시 키 직렬화기
    redisTemplate.setHashValueSerializer(s); // 해시 값 직렬화기
    redisTemplate.setConnectionFactory(lettuceConnectionFactory); // 이 템플릿이 사용할 커넥션 팩토리 연결
    return redisTemplate;
}

처리 방법 요약 

  • 현재 구조는 RedisTemplate<String, String>이므로 값은 무조건 String.
  • 그래서 put()에서는: 객체 → byte[] (Jackson) → String(ISO-8859-1) → Redis
  • get()에서는: Redis → String → byte[] (ISO-8859-1 역변환) → 객체(Jackson)
  • 타입별 Jackson2JsonRedisSerializer<T>을 호출부에서 사용
  • 타입별 Jackson2JsonRedisSerializer<T>를 직접 캐싱 (ConcurrentHashMap<Class<?>, Serializer>)해 GC 및 생성 비용 최소화

1. 기본 구조

적용한 프로젝트의 캐시는 RedisTemplate<String, String> 기반으로 동작합니다.
key와 value 모두 String으로 저장되고 StringRedisSerializer를 기본 시리얼라이저로 사용합니다.

RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());

2. 왜 직렬화/역직렬화가 필요할까?

문제는 여기서 시작됩니다. 우리가 캐시에 저장하는 값은 단순 문자열이 아니라, 보통 DTO 같은 객체입니다.
객체를 Redis에 넣으려면 JSON 같은 형태로 직렬화해야 합니다. Jackson을 쓰면 대체로 이렇게 변환됩니다.

byte[] jsonBytes = objectMapper.writeValueAsBytes(someObject);

Jackson은 객체 → JSON → UTF-8 인코딩된 byte[]를 반환합니다. 즉, 최종 결과는 바이트 배열입니다.
하지만 RedisTemplate<String, String>은 value를 String으로만 받습니다.  
따라서 JSON 직렬화 결과인 byte[]를 그대로 넣을 수 없고, String으로 한 번 감싸는 작업이 필요합니다.

3. byte[] ↔ String 변환 문제

여기서 중요한 점은 JSON 직렬화 결과는 임의의 바이트 시퀀스라는 것입니다. UTF-8로 인코딩된 문자열이라 하더라도, 직렬화기 내부 동작이나 타입에 따라 예측하기 어려운 바이트들이 포함될 수 있습니다.
그래서 단순히 new String(bytes, UTF_8) → getBytes(UTF_8) 방식으로 변환하면 손실 가능성이 있습니다.

이를 해결하기 위해 ISO-8859-1(라틴1) 브리지를 사용합니다:

  • ISO-8859-1은 0x00 ~ 0xFF까지 1:1 매핑되는 문자셋
  • 즉, 어떤 바이트 배열이 와도 동일한 코드 포인트로 변환 가능
  • 덕분에 byte[] ↔ String 변환을 손실 없이 보장할 수 있습니다.
// 저장할 때
String bridge = new String(bytes, StandardCharsets.ISO_8859_1);
redisTemplate.opsForValue().set(key, bridge);

// 읽어올 때
byte[] raw = stored.getBytes(StandardCharsets.ISO_8859_1);
T value = serializer.deserialize(raw);

 

4. put()/get() 직렬화 위치

이 때문에 객체의 (역)직렬화는 템플릿 내부가 아니라 호출부에서 처리합니다.

  • put():
    1. 객체 → Jackson 직렬화 → byte[]
    2. byte[] → ISO-8859-1 String 변환
    3. Redis에 저장
  • get():
    1. Redis에서 String 가져오기
    2. ISO-8859-1 String → byte[] 복원
    3. Jackson 역직렬화 → 객체
@Override
public <T> Optional<T> get(String cacheName, String key, Class<T> type) {
    String fullKey = fullKey(cacheName, key); // 완전 키 조립
    byte[] raw = readBytes(fullKey); // 레플리카-프리퍼드 읽기 (String -> bytes 맵핑)
    if (raw == null) return Optional.empty(); // 키 없음
    try {
        Jackson2JsonRedisSerializer<T> ser = serializer(type); // 타입별 Jackson Serializer 획득/캐싱
        T value = ser.deserialize(raw); // 바이트 -> 객체 역직렬화
        return Optional.ofNullable(value); // null 가능성 반영해 Optional 반환
    } catch (Exception e) { // 역직렬화 실패 시 해당 엔트리는 무시 (데이터 포맷 변경/깨짐 등)
        log.warn("Cache deserialize failed, ignore entry. key={}, cause={}", fullKey, e.toString());
        return Optional.empty();
    }
}

@Override
public void put(String cacheName, String key, Object value, Class<?> type, Duration ttl) {
    Objects.requireNonNull(value, "value must not be null"); // null 입력 방지 (널은 별도 마커 경로)
    String fullKey = fullKey(cacheName, key); // 완전 키 구성
    try {
        @SuppressWarnings("unchecked") // 캐시에서 가져온 Serializer를 안전 캐스트
        Jackson2JsonRedisSerializer<Object> ser =
                (Jackson2JsonRedisSerializer<Object>) serializer(type); // 타입 고정 Serializer 재사용
        byte[] bytes = ser.serialize(value); // 객체 -> 바이트 직렬화
        writeBytes(fullKey, bytes, ttl); // 마스터에 쓰기 + TTL 설정
    } catch (Exception e) { // 직렬화/쓰기 실패는 무시 (소프트 실패)
        log.warn("Cache put failed (ignored). key={}, cause={}", fullKey, e.toString());
    }
}

5. Jackson을 왜 RedisTemplate에 직접 붙이지 않았나?

사실, RedisTemplate의 value serializer를 Jackson2JsonRedisSerializer로 지정하면 더 깔끔해집니다:

redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));

이렇게 하면 put()/get()에서 수작업으로 직렬화할 필요가 없습니다. 문제는 타입 정보입니다.
GenericJackson 기반 Redis serializer는 타입을 보존하려고 JSON에 "@class": ... 같은 메타데이터를 추가합니다.
또한, Jackson2JsonSerializer는 지정 타입만 사용할 수 있어 RedistTemplate에 기본으로 등록해서 사용하려면 타입 갯수별로 RedistTemplat을 만들어야 합니다. 


관련 코드 링크

위 내용은 github 프로젝트 코드에서 확인할 수 있습니다. 

  • 스프링 프로젝트에 Redis 설정 
  • 프로젝트에 SpringRedisSerializer 사용한 방법

 

저작자표시 비영리 변경금지 (새창열림)

'Spring Framework > Spring' 카테고리의 다른 글

Spring @Transactional 동작, 로그로 확인하기  (0) 2025.08.24
@Transactional 에 대한 것  (2) 2025.08.08
스프링이 자바 빈 등록하는 방법  (1) 2025.06.07
Spring Event 를 활용한 예약 푸쉬 발송  (0) 2023.04.05
Spring Security 디버깅 방법  (0) 2023.03.30
'Spring Framework/Spring' 카테고리의 다른 글
  • Spring @Transactional 동작, 로그로 확인하기
  • @Transactional 에 대한 것
  • 스프링이 자바 빈 등록하는 방법
  • Spring Event 를 활용한 예약 푸쉬 발송
devstep
devstep
웹 백엔드 개발자
  • devstep
    개발 여정
    devstep
  • 전체
    오늘
    어제
    • 분류 전체보기 (91) N
      • Java (24)
      • Spring Framework (17) N
        • Spring (14) N
        • JPA (3)
      • Database (8)
        • RDBMS공통 (1)
        • MySQL (6)
        • Redis (0)
        • Oracle (1)
      • Concept (13)
        • 테스트코드 (4)
        • 클린코드 (2)
        • 성능테스트 (4)
        • 설계 (1)
        • 인증 (1)
        • REST API (1)
      • git (2)
      • Intellij (4)
      • Computer Science (3)
        • 네트워크 (1)
        • 자료구조 (1)
        • 보안 (1)
      • Essay (18) N
        • Learning Essay (10)
        • WIL (8) N
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    ClusteredIndex
    JVM
    성능테스트
    테스트코드
    component
    aggregate
    bean
    인텔리제이실행에러
    대칭암호화
    클린코드
    nginx
    JMeter
    단위테스트
    블록암호화
    springsecurity
    nofile
    storageEngine
    자바메모리모델
    부하테스트도구
    seed
    tdd
    보안
    applicationcontext
    비대칭암호화
    linux
    DDD
    innodb
    JavaMemoryModel
    부하테스트
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
devstep
Spring에서 Redis 사용하기 (RedisTemplate)
상단으로

티스토리툴바