본문 바로가기
공부/Spring

[Spring][Redis] 스프링 부트에서 redis 연동 및 RedisTemplate 사용법

by 웅대 2023. 7. 14.
728x90
반응형

이번 포스팅에서는 스프링 부트에서 redis와 연동하고 값을 저장해보려고 한다.

 

먼저 스프링 부트에 redis 관련된 의존성을 추가해준다.

 

만약 기존 진행 중인 프로젝트에 적용한다면 build.gradle에 다음과 같이 의존성을 추가해준다.

build.gradle{
	.
    .
    .
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

start.spring.io에서 처음 프로젝트를 생성한다면 Spring Data Redis 의존성을 추가해준다.

그리고 application.properties에 다음 코드를 작성해준다.

spring.redis.host=localhost
spring.redis.port=6379

처음 레디스 컨테이너를 실행할 때 -p 옵션으로 호스트 포트 6379와 컨테이너 포트 6379를 매핑해줬기 때문에 6379번 포트를 설정해준다.

 

redis java client는 lettuce와 jedis가 있는데 lettuce는 별다른 설정 없이 바로 사용할 수 있어서 lettuce로 진행해보려고 한다.

 

RedisConnection, RedisConnectionFactory

RedisConnection은 redis 백엔드와의 통신을 다룬다.

 

또한 RedisConnection은 연결과 관련된 라이브러리 예외를 자동으로 스프링의 consistent exception hierarchy로 변환해준다.

 

이전 포스팅에서 스프링, 자바 예외 처리에 대한 내용을 다룬 적이 있는데 이를 참고하면 이해가 쉬울 듯 하다.

https://growth-coder.tistory.com/219

 

[Spring][인프런 스프링 DB] 스프링, 자바 예외 처리

본 포스팅은 김영한 강사님의 인프런 강의 "스프링 DB 1편"을 정리한 포스팅입니다. https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1/dashboard 스프링 DB 1편 - 데이터 접근 핵심 원리 - 인프런 | 강의

growth-coder.tistory.com

위 포스팅 내용을 간단하게 요약을 하자면 먼저 SQLException 같은 경우는 체크 예외이다.

 

체크 예외의 경우 일일이 throws를 작성하고 catch 해야 한다.

 

이러한 체크 예외는 의존성이 높아지기 때문에 이러한 예외를 런타임 예외로 바꾸어서 변환하는 방법을 소개했었다.

 

이것처럼 RedisConnection도 연결과 관련된 예외를 스프링의 exception hierarchy로 자동으로 변환해준다.

 

당연히 예외를 변환할 때 기존 예외를 포함시키기 때문에 에러 정보를 잃을 위험이 없고 의존성도 낮아지기 때문에 코드의 변경 없이 connector를 변경할 수 있다.

 

이러한 RedisConnection은 RedisConnectionFactory를 통해 생성되고 이 RedisConnectionFactory가 PersistenceExceptionTranslator 역할을 수행한다.

 

IOC 컨테이너를 통해 RedisConnectionFactory에 적절한 connector를 설정하고 이를 주입받아서 사용하면 된다.

 

Redis Connector에는 대표적으로 Lettuce와 Jedis가 있는데 이 중 Lettuce를 사용해보려고 한다.

 

LettuceConnectionFactory를 빈으로 등록하는 코드는 다음과 같다.

@Configuration
class RedisConfig {
  @Bean
  public RedisConnectionFactory redisConnectionFactory() {

    return new LettuceConnectionFactory(
    	new RedisStandaloneConfiguration("호스트", 6379)
    );
  }
}

 RedisStandaloneConfiguration의 첫 번째 인자엔 호스트, 두 번째 인자엔 포트 번호를 넣어주면 된다.

 

LettuceConnectionFactory로부터 생성된 Lettuce는 동일한 thread-safe connection을 공유한다.

 

RedisTemplate

RedisConnection의 경우 binary valuef를 주고 받는데 RedisTemplate의 경우 높은 수준의 추상화와 직렬화를 제공해주기 때문에 사용하기가 편하다.

 

RedisTemplate은 다양한 타입에 대해 다양한 연산을 제공해준다.

 

특정 키 값에 대해 value를 string, list, set ... 등등 많은 연산을 제공해준다.

 

key bound를 통해 다양한 타입의 key도 정의할 수 있다.

 

RedisConnectionFactory에 이어 RedisTemplate도 빈으로 등록해보자.

 

@Configuration
public class RedisConfiguration {
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private int redisPort;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisHost, redisPort);
    }
    @Bean
    public RedisTemplate<String, String> redisTemplate() {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

RedisTemplate 설정을 자세히 보면 key와 value에 대해 Serializer를 설정해주는 모습을 볼 수 있다.

 

나는 key와 value 모두 string 타입으로 지정할 것이기 때문에 StringRedisSerializer를 등록해준다.

 

만약 등록해주지 않는다면 "1"이란 key에 대해 value를 세팅해도 "1"이라는 key가 생성되지 않고 다음과 같은 key가 생성된다.

RestTemplate으로 레디스에 값 저장하기

RestTemplate을 통해 레디스에 값을 저장할 때 RedisTemplate으로부터 Operation을 가져와야 한다.

 

먼저 간단하게 단순 value로 저장을 하려면 ValueOperations를 가져와야 한다.

 

우선 redisTemplate의 opsForValue 메소드를 호출해서 ValueOperations를 가져올 수 있다.

 

나는 refresh token을 생성할 때 redis에 값을 저장하기 때문에 우선 JwtUtil 클래스에 해당 로직을 작성했다.

 

아래는 redis 값을 저장하는 로직만 남기고 나머지 코드는 생략한 코드이다.

@Component
@RequiredArgsConstructor
public class JwtUtil {
    private final RedisTemplate<String, String> redisTemplate;
    public String createRefreshToken(Long id) {
    .
    .
    .
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        valueOperations.set(id.toString(), token);
    .
    .
    .
    }

}

단순 value이기 때문에 이 key에는 단 하나의 value만 저장할 수 있다.

 

만약 하나의 key에 여러 개의 value를 list 형태로 보관하고 싶다면 어떻게 해야 할까?

 

opsForValue가 아닌 opsForList를 불러오면 된다.

@Component
@RequiredArgsConstructor
public class JwtUtil {
    private final RedisTemplate<String, String> redisTemplate;
    public String createRefreshToken(Long id) {
    .
    .
    .
        ListOperations<String, String> listOperations = redisTemplate.opsForList();
        listOperations.leftPush(id.toString(), token);
    .
    .
    }

}

규칙을 보면 opsFor[X]를 통해 [X]Operations를 가져올 수 있는 것을 확인할 수 있다.

 

list 말고도 Set, Hash 등등 다양한 타입들이 존재한다.

 

코드 개선

조금 더 다양한 기능들을 사용해보자.

 

지금까지의 과정을 한 번 요약해보자.

  1. RedisConnectionFactory에 호스트와 포트 번호를 설정하고 빈으로 등록한다.
  2. RedisTemplate에 RedisConnectionFactory와 key serializer, value serializer를 설정하고 빈으로 등록한다.
  3. RedisTemplate을 주입받아서 Operations를 가져오고 이를 통해 redis에 값을 저장한다.

이대로 사용해도 되지만 코드를 단축할 수 있는 여러 방법을 적용해보려고 한다.

StringRedisTemplate

우선 우리는 key와 value의 타입이 String이다.

 

그래서 key serializer와 value serializer에 StringRedisSerializer를 설정해줬다.

 

그런데 key와 value의 타입이 String인 경우는 많기 때문에 StringRedisTemplate이라는 편리한 기능을 제공해준다.

 

RedisTemplate 대신 StringRedisTemplate을 사용하면 key serializer와 value serializer를 설정해주지 않아도 자동으로 String이 된다.

 

StringRedisTemplate 코드는 다음과 같다.

public class StringRedisTemplate extends RedisTemplate<String, String> {

	/**
	 * Constructs a new <code>StringRedisTemplate</code> instance. {@link #setConnectionFactory(RedisConnectionFactory)}
	 * and {@link #afterPropertiesSet()} still need to be called.
	 */
	public StringRedisTemplate() {
		setKeySerializer(RedisSerializer.string());
		setValueSerializer(RedisSerializer.string());
		setHashKeySerializer(RedisSerializer.string());
		setHashValueSerializer(RedisSerializer.string());
	}

	/**
	 * Constructs a new <code>StringRedisTemplate</code> instance ready to be used.
	 *
	 * @param connectionFactory connection factory for creating new connections
	 */
	public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
		this();
		setConnectionFactory(connectionFactory);
		afterPropertiesSet();
	}

	protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
		return new DefaultStringRedisConnection(connection);
	}
}


보다싶이 RedisTemplate<String, String>을 상속받아서 key serializer와 value serializer를 세팅해주고 있고 RedisConnectionFactory를 인자로 받아서 세팅해주고 있다.

 

이제 이 StringRedisTemplate을 빈으로 등록해보자.

@Configuration
public class RedisConfiguration {
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private int redisPort;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisHost, redisPort);
    }
    @Bean
    public RedisTemplate<String, String> redisTemplate() {
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}

이전 코드보다 조금 간결해졌다.

 

RedisTemplate을 거치지 않고 바로 Operations 주입 받기

이전에는 redisTemplate의 opsForValue와 같은 메소드를 통해 operations를 가져왔었다.

 

이렇게 redisTemplate을 통해 직접 가져와도 되지만 애초에 주입 받을 때 Operations만 가져올 수도 있다.

 

ValueOperations를 예시로 들어보자.

@Component
public class JwtUtil {
    @Resource(name="redisTemplate")
    private ValueOperations<String, String> valueOperations;
    .
    .
    .
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        valueOperations.set(id.toString(), token);
    .
    .
    .
}

@Resource는 빈 이름을 통해 주입을 받는 어노테이션인데 여기에 redisTemplate 빈 이름을 적어주고 원하는 Operations를 타입으로 지정해주면 RestTemplate을 거쳐서 가져올 필요 없이 바로 주입받을 수 있다.

 

이렇게 스프링 부트에서 redis를 연동하고 값을 저장하는 방법에 대해 알아보았다.

 

다음 포스팅에서는 refresh token을 redis에 저장하는 과정을 진행해보려고 한다.

 

참고

https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:string

 

Spring Data Redis

Some commands (such as SINTER and SUNION) can only be processed on the server side when all involved keys map to the same slot. Otherwise, computation has to be done on client side. Therefore, it is useful to pin keyspaces to a single slot, which lets make

docs.spring.io

https://docs.spring.io/spring-framework/reference/data-access/dao.html#dao-exceptions

 

DAO Support :: Spring Framework

The Data Access Object (DAO) support in Spring is aimed at making it easy to work with data access technologies (such as JDBC, Hibernate, or JPA) in a consistent way. This lets you switch between the aforementioned persistence technologies fairly easily, a

docs.spring.io

 

728x90
반응형

댓글