Spring에서 Redis 사용해보기 - Spring Data Redis

StrayCat·2026년 3월 16일

본 포스팅은 Spring Boot 3.x 환경을 기준으로 작성되었다. Spring Boot 2.x와 일부 설정 방식이 다르므로 주의가 필요하다.


들어가며

Redis를 설치하고 CLI에서 직접 명령어를 사용해보는 것까지는 실행해봤다.
그런데 정작 중요한, "Spring Boot 프로젝트에서 Redis를 어떻게 사용하는가?"라는 질문이 남는다.

Spring Boot는 Redis를 연동하기 위한 두 가지 주요 방법을 제공한다.

  1. Spring Data Repository 방식 — JPA처럼 CrudRepository를 상속받아 쓰는 방식
  2. RedisTemplate 방식 — 직접 Key/Value를 다루며 세밀하게 제어하는 방식

각 방식이 어떤 상황에 적합한지, 실제로 어떻게 동작하는지를 코드와 함께 정리해 보려 한다.


1. 의존성 추가 및 기본 설정

pom.xml

<!-- Spring Data Redis + Lettuce 클라이언트를 함께 포함하는 스타터 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

Spring Boot 스타터(starter)란, 관련된 의존성들을 묶어서 편리하게 추가할 수 있도록 만든 패키지다. spring-boot-starter-data-redis를 추가하면 Lettuce 클라이언트와 Spring Data Redis가 함께 포함된다.

Spring Boot 3.x부터는 Redis 연결 설정 prefix가 spring.redis.*에서 spring.data.redis.* 로 변경되었다.
레거시(Spring Boot 2.x) 프로젝트에서는 여전히 spring.redis.*를 사용하는 경우가 많으니, 팀 내 환경을 먼저 확인하는 것이 좋다.

application.yaml

spring:
  data:
    redis:
      host: <서버 주소>       # Redis 서버 호스트 (로컬이면 localhost)
      port: <포트 번호>       # 기본값 6379
      username: default       # Redis ACL 사용 시 지정 (7.x 이상)
      password: <비밀번호>    # 설정한 경우에만 입력

host와 port를 생략하면 localhost:6379로 자동 연결을 시도한다.
로컬에 Redis를 설치했다면 별도 설정 없이도 바로 연결된다.


2. Spring Data Repository 방식

JPA를 사용해봤다면 이 방식이 굉장히 익숙하게 느껴질 것이다.
@RedisHash 어노테이션을 붙인 도메인 클래스를 만들고, CrudRepository를 상속받는 것이 전부다.

도메인 클래스 — Item.java

// package, import 생략

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@RedisHash("item") // Redis에 "item" 이라는 해시(Hash) 자료형으로 저장됨을 선언
public class Item implements Serializable { // Redis 직렬화를 위해 Serializable 구현

    @Id // Redis Key의 식별자로 사용될 필드
    private Long id;

    private String name;
    private String description;
    private Integer price;
}

JPA의 @Entity 대신 @RedisHash 를 사용한다.
이 어노테이션의 값("item")은 Redis Key의 prefix로 사용되며, 저장 시 item:1, item:2 형태의 Key가 만들어진다.

Repository 인터페이스 — ItemRepository.java

// CrudRepository<도메인_클래스, ID_타입> 를 상속받는 것만으로
// save(), findById(), deleteById() 등의 기본 CRUD 메서드가 자동으로 제공된다.
public interface ItemRepository extends CrudRepository<Item, Long> {}

테스트 코드

@SpringBootTest
public class RedisRepositoryTests {

    @Autowired
    private ItemRepository itemRepository;

    // CREATE — 데이터 저장
    @Test
    public void createTest() {
        Item item = Item.builder()
                .id(1L)
                .name("keyboard")
                .description("Mechanical Keyboard Expensive")
                .build();
        itemRepository.save(item); // Redis Hash 자료형으로 저장
    }

    // READ — ID로 단건 조회
    @Test
    public void readOneTest() {
        Item item = itemRepository.findById(1L)
                .orElseThrow(); // 없으면 예외 발생
        System.out.println(item.getDescription());
    }

    // UPDATE — 조회 후 변경 사항 저장
    @Test
    public void updateTest() {
        Item item = itemRepository.findById(1L)
                .orElseThrow();
        item.setDescription("On Sale!!!"); // 값 변경
        itemRepository.save(item);         // 다시 save()하면 덮어쓰기(upsert)된다

        item = itemRepository.findById(1L).orElseThrow();
        System.out.println(item.getDescription()); // "On Sale!!!" 출력
    }

    // DELETE — 삭제
    @Test
    public void deleteTest() {
        itemRepository.deleteById(1L);
    }
}

Redis에 실제로 저장되는 구조

createTest()를 실행하면 Redis에 두 개의 Key가 생성된다.

Key자료형역할
itemSet저장된 ID 목록을 관리하는 인덱스 (자동 생성)
item:1Hash실제 Item 데이터 (id, name, description, _class 등)

HGETALL item:1로 조회하면 각 필드와 함께 _class 라는 키도 보이는데, 이는 역직렬화(deserialization) 시 어떤 Java 클래스로 복원할지를 판단하기 위해 Spring이 자동으로 저장하는 메타데이터다.


3. RedisTemplate 방식

Repository 방식은 편리하지만, Redis의 다양한 자료형(Sorted Set, List 등)을 세밀하게 다루거나 Key 네이밍을 직접 제어하고 싶을 때는 한계가 있다.
이 경우 RedisTemplate 을 직접 사용하는 방식이 훨씬 적합하다.

실제로 Salesforce 엔지니어링팀의 사례 분석에 따르면, CrudRepository보다 RedisTemplate을 직접 사용했을 때 GET 연산의 응답 속도가 밀리초(ms) → 나노초(ns) 수준으로 개선되었다는 보고도 있다.
물론 이는 인덱스 관리 오버헤드 제거에 따른 결과이며, 작은 규모에서는 차이가 미미할 수 있다.

3-1. StringRedisTemplate — 가장 간단한 시작점

Key와 Value 모두 Java String을 사용하는 경우라면, Spring Boot가 자동으로 빈(Bean) 등록해주는 StringRedisTemplate을 그냥 주입해서 쓰면 된다.

주의: "StringRedisTemplate = Redis String 자료형만 사용"이 아니다. Redis List, Set 등에도 Java의 String 타입으로 데이터를 주고받는다는 의미다.

@SpringBootTest
public class RedisTemplateTests {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // String 자료형 (opsForValue)
    @Test
    public void stringValueOpsTest() {
        // opsForValue() → Redis String 자료형에 대한 작업 객체 반환
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();

        ops.set("simplekey", "simplevalue");        // SET 명령어에 대응
        System.out.println(ops.get("simplekey"));   // GET 명령어에 대응

        ops.set("greeting", "hello redis!");
        System.out.println(ops.get("greeting"));
    }

    // Set 자료형 (opsForSet)
    @Test
    public void stringSetOpsTest() {
        // opsForSet() → Redis Set 자료형에 대한 작업 객체 반환
        SetOperations<String, String> setOps = stringRedisTemplate.opsForSet();

        setOps.add("hobbies", "games");   // SADD 명령어에 대응
        setOps.add("hobbies", "coding");
        setOps.add("hobbies", "alcohol");
        setOps.add("hobbies", "games");   // Set이므로 중복은 무시된다

        System.out.println(setOps.size("hobbies")); // SCARD → 3 출력
    }

    // TTL 설정 (공용 기능은 Template 자체에 정의되어 있음)
    @Test
    public void redisOpsTest() {
        // expire() → EXPIRE 명령어에 대응. 지정한 시간 후 자동 삭제된다.
        stringRedisTemplate.expire("simplekey", 5, TimeUnit.SECONDS);
        stringRedisTemplate.expire("greeting", 10, TimeUnit.SECONDS);
        stringRedisTemplate.expire("hobbies", 15, TimeUnit.SECONDS);
    }
}

자료형별 opsFor*() 메서드 정리:

메서드Redis 자료형반환 타입
opsForValue()StringValueOperations<K, V>
opsForList()ListListOperations<K, V>
opsForSet()SetSetOperations<K, V>
opsForZSet()Sorted SetZSetOperations<K, V>
opsForHash()HashHashOperations<K, HK, HV>

3-2. 커스텀 RedisTemplate — Java 객체를 JSON으로 저장하기

String이 아닌 Java 객체를 다루고 싶다면, @Configuration에서 RedisTemplate<K, V>를 직접 빈으로 등록하면 된다.

DTO 클래스 — ItemDto.java

@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ItemDto {
    private String name;
    private String description;
    private Integer price;
}

RedisConfig.java

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, ItemDto> itemRedisTemplate(
            RedisConnectionFactory connectionFactory // application.yaml 설정 기반으로 자동 생성된 빈
    ) {
        RedisTemplate<String, ItemDto> template = new RedisTemplate<>();

        // 1. Redis 연결 설정 주입
        template.setConnectionFactory(connectionFactory);

        // 2. Key 직렬화: 문자열 그대로 저장 (redis-cli에서 읽기 편함)
        template.setKeySerializer(RedisSerializer.string());

        // 3. Value 직렬화: JSON 형태로 저장 (사람이 읽을 수 있는 형식)
        template.setValueSerializer(RedisSerializer.json());

        return template;
    }
}

테스트 코드

@SpringBootTest
public class RedisTemplateTests {

    @Autowired
    private RedisTemplate<String, ItemDto> itemRedisTemplate;

    @Test
    public void itemRedisTemplateTest() {
        ValueOperations<String, ItemDto> ops = itemRedisTemplate.opsForValue();

        // "my:keyboard" 라는 Key로 ItemDto 객체를 JSON 형태로 저장
        ops.set("my:keyboard", ItemDto.builder()
                .name("Mechanical Keyboard")
                .price(300000)
                .description("Expensive")
                .build());

        // 저장된 데이터 조회 — JSON에서 ItemDto로 자동 역직렬화
        System.out.println(ops.get("my:keyboard"));

        ops.set("my:mouse", ItemDto.builder()
                .name("mouse mice")
                .price(100000)
                .description("Expensive")
                .build());

        System.out.println(ops.get("my:mouse"));
    }
}

Redis에 실제로 저장된 데이터를 MGET my:keyboard my:mouse로 조회하면,
JSON 형태로 저장되어 있는 것을 확인할 수 있다.


4. 두 방식 비교 정리

구분Spring Data RepositoryRedisTemplate
구현 난이도낮음 (JPA와 유사한 경험)다소 높음 (직접 설정 필요)
Key 제어자동 생성 (해시명:id)직접 설정 가능
자료형 선택Hash 고정String, List, Set, ZSet, Hash 모두 가능
인덱스 자동 생성있음 (SET으로 ID 관리)없음
성능인덱스 오버헤드 존재더 낮은 레이턴시 가능
적합한 상황단순 CRUD, 소규모 캐싱세밀한 제어, 복잡한 자료형 활용

5. 기타

직렬화(Serializer) 선택 주의

예제에서 RedisSerializer.json()을 사용했는데, 이는 내부적으로 GenericJackson2JsonRedisSerializer를 사용한다.
이 직렬화기는 편리하지만 JSON에 타입 정보(@class)를 함께 저장하기 때문에, 클래스 패키지 경로가 바뀌면 역직렬화가 실패하는 문제가 발생할 수 있다.

반면 Jackson2JsonRedisSerializer는 특정 타입에 특화되어 더 간결하게 저장되지만, Spring Data Redis 4.0에서 Deprecated 처리되었으니 주의가 필요하다.

현재 Spring Data Redis 4.x 공식 문서 기준으로 추천되는 접근은 GenericJackson2JsonRedisSerializer커스텀 ObjectMapper를 주입하는 방식이다.

// 커스텀 ObjectMapper를 활용한 안정적인 JSON 직렬화 설정 예시
@Bean
public RedisTemplate<String, ItemDto> itemRedisTemplate(
        RedisConnectionFactory connectionFactory,
        ObjectMapper objectMapper // Spring이 관리하는 ObjectMapper 주입
) {
    RedisTemplate<String, ItemDto> template = new RedisTemplate<>();
    template.setConnectionFactory(connectionFactory);
    template.setKeySerializer(new StringRedisSerializer());

    // 직접 objectMapper를 주입하여 애플리케이션 전체 Jackson 설정과 통일
    GenericJackson2JsonRedisSerializer serializer =
            new GenericJackson2JsonRedisSerializer(objectMapper);
    template.setValueSerializer(serializer);

    return template;
}

Lettuce vs Jedis

Spring Boot 2.x 이후 기본 Redis 클라이언트는 Lettuce로 변경되었다.
Lettuce는 Netty 기반의 비동기 클라이언트로, 멀티스레드 환경에서 커넥션 하나를 공유하며 효율적으로 동작한다.

다만 레거시 시스템에서는 여전히 Jedis를 사용하는 경우도 적지 않다. Jedis는 동기 방식으로 직관적이지만, 스레드당 커넥션이 필요하기 때문에 커넥션 풀(connection pool) 관리가 중요하다. 둘 다 spring-boot-starter-data-redis 안에서 선택 가능하다.

Reactive Redis

Spring WebFlux + Redis 조합을 사용하는 경우, RedisTemplate 대신 ReactiveRedisTemplate 을 사용하는 것이 권장된다. 비동기 논블로킹(non-blocking) 방식으로 Redis와 통신할 수 있어 고성능 서비스 구축에 유리하다. 이 부분은 별도로 학습해볼 만하다.


마치며

직접 RedisTemplate을 설정하며 직렬화/역직렬화, Key 네이밍 전략 등 생각보다 고려할 게 많다는 걸 실감했다.

정리하면:

  • 빠르게 시작하고 싶다면 → Spring Data Repository
  • 성능과 자료형 제어가 중요하다면 → RedisTemplate

그리고 실제 개발 환경에서는 두 가지를 적절히 혼용하는 경우도 많다. 중요한 건 어떤 방식을 선택했는지보다, 선택의 근거를 이해하고 있는 것이다.


참고 자료

profile
알면 좋은 것보단 잊어버리기 싫은 것들을 기록합니다.

0개의 댓글