Springboot Cache 사용기[4] Redis 사용하기
Redis Cache를 사용하기 위해선 먼저 Redis를 사용할 줄 알아야 합니다. 그래서 하나씩 만들어보겠습니다.
Redis는 Nosql DB입니다. 영속성을 지원하며, In memory DB이므로 속도가 빠른 장점으로 Cache서버의 DB로서 많이 사용됩니다.
1. 디펜던시 추가
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
build.gradle에 위의 디펜더 시를 추가해줍니다.
2. RedisConfig
@Configuration @RequiredArgsConstructor @EnableRedisRepositories public class RedisConfig { private final RedisProperties redisProperties; @Bean public RedisConnectionFactory redisConnectionFactory(){ return new LettuceConnectionFactory(redisProperties.getHost(),redisProperties.getPort()); } @Bean public RedisTemplate<?,?> redisTemplate(){ RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; } }
2-1 RedisConnectionFactory
- Java에서 지원하는 RedisClient는 2가지가 있습니다. (Jedis, Lettuce)
- Jedis는 멀티쓰레드에서 불안정하고, Pool의 한계 등으로 현재 Springboot 2.0 이상에서는 Lettuce가 탑재되고, 사용된다.
- RedisProperties는 Redis의 기본 설정들을 가지고 있습니다.
- RedisProperties를 이용해서 Redis의 Host와 Port를 설정해줍니다.
LettuceConnectionFactory에 설정을 부여해서, 클러스터에 대한 설정도 할 수 있지만 여기서는 진행하지 않겠습니다.
2-2 RedisTemplate
Redis에 데이터를 저장하는 방법은 총 2가지 있습니다.
1. CrudRepository 상속받아 사용
- Redis를 사용할 Repository를 만들고 CrudRepository를 상속받아 사용합니다.
2. RedisTemplate 정의해서 사용
- Redis에 직렬화 설정을 부여해서 저장하는데, 자동으로 직렬화를 수행해줍니다.
- ConnectionFactory를 Luttece를 설정해줍니다.
- setKeySerializer(new StringRedisSerializer()): String으로 들어오는 key에 대한 직렬화를 수행합니다. 즉 key에 대한 직렬화 설정을 해주는 메서드입니다.
- setValueSerializer(new GenericJackson2JsonRedisSerializer)): 저장하려 하는 Object객체를 직렬화 하여 저장합니다. 즉 Value에 대한 직렬화 설정을 하주는 메서드입니다.
- RedisTemplate<String,?>: key값은 항상 String이므로 String, Value는 와일드카드를 사용해서, 다양한 객체를 저장할 수 있도록 해줍니다.
2022.04.29 추가
sprigboot 2.0 이상부터는 config 작성없이 application.properties나 yml로 작성이 가능합니다.
spring: redis: host: localhost port: 6379
3. CrudRepository 사용
CrudRepository를 상속받아서 Redis를 사용해보겠습니다.
3-1. RedisMember class
@Getter @NoArgsConstructor @RedisHash(value = "redisMember") public class RedisMember{ @Id private String id; private String name; private int age; public RedisMember(String name, int age) { this.id= name; this.name = name; this.age = age; } }
- @Id는 javaPersistent의 어노테이션입니다.
- @RedisHash: Redis에 저장될 때 사용되는 prefix입니다. ex) redisMember:key 사용됩니다.
- Id가 Key값으로 사용됩니다. 저희는 name을 key값으로 사용할 예정이므로 Id에도 key를 넣어줍니다. 만약 안 넣어줄 시 Redis에 입력될 때 랜덤 한 값으로 key값이 생성됩니다.

첫 번째는 id를 미지정, 두 번째는 id에 name으로 줬을 때입니다.
prefix로 redisMember가 들어간 것을 볼 수 있습니다.
3-2. MemberRedisRepository
public interface MemberRedisRepository extends CrudRepository<RedisMember,String> { }
- CrudRepository<>를 상속받습니다.
- Id 값을 이용하여 데이터를 찾습니다.
간단한 테스트 코드를 보겠습니다.
@Test @DisplayName("redisRepository 테스트") void repositoryTest(){ memberRedisRepository.save(new RedisMember("hi",10)); RedisMember redisMember = memberRedisRepository.findById("hi").get(); assertThat(redisMember.getName()).isEqualTo("hi"); }
- RedisMember id,name= hi이며, age=10인 객체를 저장합니다.

- redis가 만약 설치되어 있다면, redis-cli를 입력하고 위처럼 입력한다면 값이 저장된 것을 확인할 수 있습니다.
- Id를 가지고 데이터를 찾으므로 값을 변경하고 싶다면, 다시 저장하시면 됩니다.
@Test @DisplayName("redisRepository 데이터변경 테스트") void repositoryUpdateTest(){ memberRedisRepository.save(new RedisMember("hi",10)); memberRedisRepository.save(new RedisMember("hi",11)); RedisMember redisMember = memberRedisRepository.findById("hi").get(); assertThat(redisMember.getAge()).isEqualTo(11); }

- 삭제는 delete를 이용하면 됩니다.
전체를 반환하는 테스트를 해보겠습니다. JPA와 다르게 Iterable <> 형태로 반환됩니다.
@Test @DisplayName("redisRepository 전체 반환 테스트") void repositoryFindAllTest(){ memberRedisRepository.save(new RedisMember("hi1",10)); memberRedisRepository.save(new RedisMember("hi2",11)); Iterable<RedisMember> all = memberRedisRepository.findAll(); List<RedisMember> list = new ArrayList<>(); all.forEach(list::add); assertThat(list.size()).isEqualTo(2); }
- forEach의 람다 표현식으로 List에 담아서 사용합니다.
4. RedisTemplate 사용
위에서 선언한 RedisTemplate를 사용합니다.
먼저 사용가능한 자료구조와 사용하기 위한 객체를 살펴보겠습니다.
메소드명반환 오퍼레이션 Redis 자료구조
opsForValue() | ValueOperations | String |
opsForList() | ListOperations | List |
opsForSet() | SetOperations | Set |
opsForZSet() | ZSetOperations | Sorted Set |
opsForHash() | HashOperations | Hash |
Operations연산자를 얻어서 연산을 수행합니다.
4-1. ValueOperation
Value는 String에 대해서 연산을 진행합니다.
@Autowired RedisTemplate<String, RedisMember> redisMemberRedisTemplate; @Test @DisplayName("redisTemplate value 테스트") void redisTemplateTest(){ ValueOperations<String, RedisMember> stringRedisMemberValueOperations = redisMemberRedisTemplate.opsForValue(); stringRedisMemberValueOperations.set("hi", new RedisMember("hi", 10)); RedisMember redisMember = redisMemberRedisTemplate.opsForValue().get("hi"); assertThat(redisMember.getAge()).isEqualTo(10); }
- 연산자를 먼저 얻습니다. 그 후 set을 이용해서 객체를 저장합니다.
- 객체를 찾을 때 지정한 key를 이용해서 데이터를 찾습니다.
- 여기서 알아야할 부분은 원래 Value는 String에 대해서 연산을 진행하지만, RedisConfig에서 객체에 대한 직렬화 설정을 부여했기 때문에 자동적으로 직렬화와 역직렬 화가 수행돼, RedisMember를 저장하고 데이터를 찾을 수 있습니다.
- 마찬가지로 업데이트 연산은 set(key, 변경)을 이용하면 됩니다.
4-2 ListOperation
List에 대한 연산을 진행합니다.
@Test @DisplayName("redisTemplate list 테스트") void redisTemplateListTest(){ ListOperations<String, RedisMember> stringRedisMemberListOperations = redisMemberRedisTemplate.opsForList(); stringRedisMemberListOperations.rightPush("redisMemberList",new RedisMember("hi1",10)); stringRedisMemberListOperations.rightPush("redisMemberList",new RedisMember("hi2",11)); Long size = stringRedisMemberListOperations.size("redisMemberList"); RedisMember result = stringRedisMemberListOperations.index("redisMemberList", 1); List<RedisMember> redisMemberList = stringRedisMemberListOperations.range("redisMemberList", 0, 1); stringRedisMemberListOperations.set("redisMemberList",0,new RedisMember("m1",10)); }
- rightPush: List에 추가합니다.
- size: 크기를 알 수 있습니다.
- index: 단 건의 데이터를 조회할 수 있습니다.
- range: 다수의 데이터를 조회할 수 있습니다.
- set: 데이터의 값을 변경할 수 있습니다.
4-3 SetOperation
set에 대한 연산을 진행합니다.
@Test @DisplayName("redisTemplate set 테스트") void redisTemplateSetTest(){ SetOperations<String, RedisMember> stringRedisMemberSetOperations = redisMemberRedisTemplate.opsForSet(); stringRedisMemberSetOperations.add("memberSet",new RedisMember("h1",10)); stringRedisMemberSetOperations.add("memberSet",new RedisMember("h2",10)); RedisMember randomRedisMember = stringRedisMemberSetOperations.pop("memberSet"); List<RedisMember> randomMemberSet = stringRedisMemberSetOperations.pop("memberSet", 2); Set<RedisMember> memberSet = stringRedisMemberSetOperations.members("memberSet"); Boolean result = stringRedisMemberSetOperations.isMember("memberSet", new RedisMember("h1", 10)); Long re = stringRedisMemberSetOperations.remove("memberSet", new RedisMember("hi2", 10)); }
- add: set에 추가할 수 있습니다.
- pop(key): set에 저장된 데이터중에서 랜덤 하게 한 개의 데이터를 반환합니다.
- pop(key, count): set에 저장된 데이터중에서 랜덤 하게 count만큼의 데이터를 반환합니다.
- member(key): set에 저장된 모든 데이터를 반환합니다.
- isMember(key, 데이터): 데이터의 존재 유무를 판별합니다.
- remove(key,데이터): 데이터에 관한 삭제를 진행하며, 몇 건의 데이터가 삭제됐는지 반환됩니다.
4-4. ZSetOperation
위에서 본 set과는 다르게 값에 대해 가중치를 주어 정렬을 진행합니다.
@Test @DisplayName("redisTemplate zSet 테스트") void redisTemplateZSetTest(){ ZSetOperations<String, RedisMember> stringRedisMemberZSetOperations = redisMemberRedisTemplate.opsForZSet(); stringRedisMemberZSetOperations.add("memberZSet",new RedisMember("hi1",10),1); stringRedisMemberZSetOperations.add("memberZSet",new RedisMember("hi2",10),2); Long memberZSet = stringRedisMemberZSetOperations.count("memberZSet", 0, 1); RedisMember result = stringRedisMemberZSetOperations.popMin("memberZSet").getValue(); }
- add(key, value, 가중치): 가중치를 기준으로 value에 대한 정렬을 진행합니다.
- count(key, min, max): min~max까지의 개수를 반환합니다.
- popMin(key).getValue(): 가장 작은 가중치를 가진 데이터를 반환합니다.
4-5. HashOperation
hash를 이용하여 key과 hashKey값을 통해서 데이터를 저장, 조회합니다.
hash를 사용하기 위해선 config에 hash key값과 value값을 직렬화 해주는 과정이 필요합니다.
아래와 같이 추가해줍니다.
@Bean public RedisTemplate<String,?> redisTemplate(){ RedisTemplate<String, ?> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setKeySerializer(new StringRedisSerializer()); // 추가 redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); // 추가 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return redisTemplate; }
@Test @DisplayName("redisTemplate hash 테스트") void redisTemplateHashTest(){ HashOperations<String, Object, Object> stringObjectObjectHashOperations = redisMemberRedisTemplate.opsForHash(); stringObjectObjectHashOperations.put("memberHashOne","name",new RedisMember("hi3",10)); Map<String, RedisMember> map = new HashMap<>(); map.put("hi1",new RedisMember("hi1", 10)); map.put("hi2",new RedisMember("hi2", 10)); stringObjectObjectHashOperations.putAll("memberHashMap",map); RedisMember result = (RedisMember) stringObjectObjectHashOperations.get("memberHashMap", "hi1"); RedisMember redisMember = (RedisMember) stringObjectObjectHashOperations.get("memberHashOne", "name"); }
- put(key, hashKey, value): hashKey값을 이용해서 데이터를 저장합니다.
- putAll(key, Map): map의 key값이 hashKey값으로 들어가게 됩니다.
- get(key, hashKey): key값과 hashKey값을 이용해서 데이터를 찾습니다.
정리하기
Redis 디펜더시를 추가하여 사용할 수 있고, RedisConfig를 작성
- RedisConnectionFactory를 지정하고, properties를 주입해준다.
- RedisTemplate: 연산을 위한 Template
Redis에 연산 진행 2가지 방법
1. CrudRepository 상속
- @RedisHash는 prefix를 지정한다.
- Id가 key값으로 들어가게 된다. 미지정 시 랜덤 한 값으로
2. RedisTemplate 사용
- connectionFactory를 설정
- key와 value에 대한 직렬화 방식을 설정해줘 자동으로 직렬화와 역직렬화가 진행
- Value, List, Set, ZSet, Hash에 대한 연산 방법
다음은 Redis의 사용방법을 활용해서 Cache를 적용해보겠습니다. 읽어주셔서 감사합니다.
모든 코드는 아래 링크에서 확인 가능합니다.
https://github.com/rlaehdals/springbootCache
'SpringBoot > cache' 카테고리의 다른 글
Springboot Cache 사용기[5] Redis 이용한 Cache 사용 (0) | 2022.04.16 |
---|---|
Springboot Cache사용기[3] Local-Memory-Cache 동작 과정 (0) | 2022.04.02 |
Springboot Cache 사용기[2] Local-Memory-Cache (2) | 2022.03.30 |
Springboot Cache 사용기[1] (0) | 2022.03.30 |
댓글
이 글 공유하기
다른 글
-
Springboot Cache 사용기[5] Redis 이용한 Cache 사용
Springboot Cache 사용기[5] Redis 이용한 Cache 사용
2022.04.16 -
Springboot Cache사용기[3] Local-Memory-Cache 동작 과정
Springboot Cache사용기[3] Local-Memory-Cache 동작 과정
2022.04.02이전 포스팅에서 SimpleCacheManager를 이용해서 Cache가 동작하는 것을 구현해봤습니다. 그렇다면 이런 CacheManager가 동작되는데 어떠한 과정이 있는지 살펴보기 위해선 먼저 Proxy를 알아야 합니다. 1. Proxy란? Proxy란 대리자라는 의미를 가지고 있습니다. 예를 들어서 나는 운전면허가 있어서 운전을 할 수 있지만, 누군가에게 대신 운전을 시킬수도 있습니다. 여기서 대신 운전을 해주는 사람이 대리자이며, 영어로 Proxy라고 합니다. 개발자는 10개의 객체에 동일한 기능을 가진 프록시를 적용하려 한다면, 각각의 객체에 대한 부가 기능을 가진 프록시를 모두 정의했어야 했습니다 . 하지만 JDK 동적 프록시와 CGLIB를 이용하면 같은 부가 기능 로직을 한 번만 정의하면 모… -
Springboot Cache 사용기[2] Local-Memory-Cache
Springboot Cache 사용기[2] Local-Memory-Cache
2022.03.30앞 시간에서 Cache란 무엇이고, Springboot에서 어떠한 Cache가 있는지 알아봤습니다. 이번에는 Local-Memory-Cache를 사용하는 방법에 대해서 알아보겠습니다. Local-Memory-Cache 1. Local Memory에 Cache 데이터를 저장하고, 조회한다. 2. 서버가 꺼지면 Cache 데이터들은 삭제된다. 3. 기본 전략으로 @EnableCache만 붙여주거나, 간단하게 Custom CacheManager를 등록해서 사용 가능하다. 1. 프로젝트 세팅 Cache의 사용법을 알아보는 포스팅입니다. 밑에 진행하는 예제가 Cache로 사용하기 적합하지 않은 데이터일 수 있습니다. [디펜더 시 추가] dependencies { implementation 'org.springfr… -
Springboot Cache 사용기[1]
Springboot Cache 사용기[1]
2022.03.30Cache에 대해서 먼저 알아보자. Cache는 작은 저장공간을 가지지만 빠른 액세스 속도를 특징으로 가지고 있습니다. 따라서 자주 사용되는 데이터를 임시 저장소에 저장해 빠른 응답 속도를 위해서 사용됩니다. [그럼 어떠한 데이터에 Cache를 사용하면 좋을까?] 1. 반복적인 결과를 돌려주는 데이터(웹툰은 원고를 수정을 하지 않는 이상 같은 데이터를 반환한다.) 2. 조회하는데 많은 비용이 드는 데이터 [Local Cache와 Global Cache] Local Cache의 특징 1. 서버마다 Cache를 두는 것 2. 서버마다 작동하므로 동작이 빠르다. 3. 다른 서버와의 동기화에 어려움이 있을 수 있다. Global Cache의 특징 1. Cache 서버를 별도로 존재하고, 분산된 서버에서 데이터를…
댓글을 사용할 수 없습니다.