2021. 6. 18. 17:53ใ๐ฑ Develop/Server
โ redis ๋ฅผ ์ ํํ ์ด์
๋ธ๋ก๊ทธ ํํ์ด์ง๋ฅผ ๋ง๋๋ ํ๋ก์ ํธ์์, "์ต๊ทผ ์ฝ์ ๊ธ" ์ ๊ฐ๋ฐํด์ผํ๋ ์ํฉ์ด ์์์ต๋๋ค. velog ๊ฐ์ ์๋น์ค์์๋ ๊ฝค ์ค๋ ๊ธฐ๊ฐ๋์ ๋ด๊ฐ ์ฝ์ ๊ธ์ ์ ์ฅํ๋ ๋ฏ ํ๋๋ฐ, ํ์ฌ ํ๋ก์ ํธ์ ๊ธฐํ์์๋ ์ผ์ฃผ์ผ์ด๋ผ๋ ๊ธฐ๊ฐ์ด ์ง์ ๋์ด์์์ต๋๋ค. ์ฒ์์๋ ํ
์ด๋ธ์ ํ๋ ๋ ์์ฑํด์ผํ๋ ๊ณ ๋ฏผ์ด ๋์๋๋ฐ, ์ฝ์ ๊ธ ํน์ฑ ์ ๋จ๊ธฐ๊ฐ์ ๋ง์ ๋ฐ์ดํฐ๊ฐ ์์ง๋๋ค๋ ์ ๊ณผ ๋ง๋ฃ ๊ธฐ๊ฐ์ ์ค์ ํ๋ ๊ฒ์ด ์ฑ๋ฅ ์์ผ๋ก ์ข์ง ์์ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ต๋๋ค. ๊ทธ๋ฌ๋ค ์ด์ ํ๋ก์ ํธ์์ refreshToken์ ์ ์ฅํ ๋ redis๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์๊ฐ๋์, redis๋ก ์กฐํํ ๊ธ ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ๊ณ , ์ผ์ ๊ธฐ๊ฐ์ด ์ง๋๋ฉด ์ญ์ ๋๋ ๊ธฐ๋ฅ์ ์ ์ฉํด๋ณด๊ณ ์ ํฉ๋๋ค.
๐ ๊ตฌํํด์ผํ๋ ๊ธฐ๋ฅ
- redis list ์๋ฃํ์ ์ฌ์ฉํ์ฌ postDto๋ก์ ์ฅ
- key ๊ฐ์ ํตํด postDto๋ฅผ ์ ์ฅํ๋ชฉ๋ก ์กฐํ
1๏ธโฃ springboot ์ redis ์ค์
build.gradle์ ์์กด์ฑ ์ถ๊ฐ
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.3.12.RELEASE'
application.yaml ์ spring redis ์ค์ ์ถ๊ฐ - local
spring:
redis:
host: localhost
port: 6379
๐ ๋ฐฐํฌ ์๋ฒ์ redis ์ค์
์ค์ ์๋ฒ์ ์๋ docker ์ด๋ฏธ์ง์ธ Redis์ ์ฐ๊ฒฐํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํด์ค๋๋ค. redis๋ docker-compose ๋ก ์คํํ ๋ ์ปจํ ์ด๋๋ช ์ ๋๋ค. redis ๋์ปค ์ด๋ฏธ์ง์ ์ฐ๊ฒฐํ๋ ค๋ ๊ฒ์ด ์๋๋ผ ์๋ฒ ๋ก์ปฌ redis์ ์ฐ๊ฒฐํ๋ ค๋ฉด ip ์ฃผ์๋ฅผ ์ ์ด์ฃผ์๋ฉด ๋ฉ๋๋ค.
spring:
redis:
host: redis(์ปจํ
์ด๋ ๋ช
)
port: 6379
EC2 ์๋ฒ์์ docker pull redis ๋ก ์ต์ redis ์ด๋ฏธ์ง๋ฅผ ๋ค์ด๋ฐ๊ณ docker-compose.yml ํ์ผ์ redis ์ค์ ์ถ๊ฐ๋ ํด์ค๋๋ค.
redis:
container_name: redis
hostname: redis
image: redis
command: ["redis-server", "--bind", "redis", "--port", "6379"]
ports:
- "6379:6379"
restart: always
networks: # ์๋ฒ์ ๊ฐ์ ๋คํธ์ํฌ, ์์ง ๋ง์ธ์!
- backend
server:
....
depends_on:
- redis
2๏ธโฃ RedisConfiguration ์ค์ ํ๊ธฐ
๐ config/redisRepositoryConfig
@Configuration
@EnableRedisRepositories(basePackages = "com.yapp18.retrospect.config")
public class RedisRepositoryConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
// jackson LocalDateTime mapper
@Bean public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // timestamp ํ์ ์๋ฐ๋ฅด๋๋ก ์ค์
mapper.registerModules(new JavaTimeModule(), new Jdk8Module()); // LocalDateTime ๋งคํ์ ์ํด ๋ชจ๋ ํ์ฑํ
return mapper;
}
// redisConnectionFactory ๋ฅผ ํตํด ์ธ๋ถ redis ๋ฅผ ์ฐ๊ฒฐํฉ๋๋ค.
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(redisHost, redisPort);
}
//RedisTemplate์ ํตํด RedisConnection์์ ๋๊ฒจ์ค byte ๊ฐ์ ๊ฐ์ฒด ์ง๋ ฌํํฉ๋๋ค.
@Bean
public RedisTemplate<?,?> redisTemplate(){
RedisTemplate<String, RecentLog> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper()));
return redisTemplate;
}
}
- ObjectMapper : redis์์ ๊ฐ์ ์ ์ฅํ๊ณ ๊ฐ์ ธ์ค๋ ๊ณผ์ ์์, LocalDateTime์ด๋ LocalDateTime ํ์์ ์ญ์ง๋ ฌํํ๋ ๊ณผ์ ์์ ์๋ฌ๊ฐ ๋ ์ ์์ต๋๋ค.
- ๋ ์ง ํ์ ์ฒ๋ฆฌํ๊ธฐspringboot์์๋ ObjectMapper ๊ฐ ๋น์ผ๋ก ์ฃผ์ ๋๊ธฐ ๋๋ฌธ์ application.yml ํ์ผ์์๋ ์ค์ ์ด ๊ฐ๋ฅํฉ๋๋ค.
spring: jackson: serialization: WRITE\_DATES\_AS\_TIMESTAMPS: false
- Object → Json(์ง๋ ฌํ) / Json → Object(์ญ์ง๋ ฌํ) / ObejctMapper ์ด ์ด๋ฐ ์ญํ ์ ๋ด๋นํ๊ณ ์๊ณ , Jackson์ ๊ธฐ๋ณธ์ ์ผ๋ก ObjectMapper์ ์ ๊ณตํ๊ณ ์์ต๋๋ค. Jackson์์ LocalDateTime์ ๋งคํํ๋ ค๋ฉด JavaTimeModule์ ํ์ฑํํด์ผํ๊ณ , WRITE_DATES_AS_TIMESTAMPS ๋ฅผ ์ ์ฉํ์ง ์๊ธฐ๋ก ์ค์ ํด์ฃผ์ด์ผํฉ๋๋ค.
- RedisConnectionFactory : application.yml ํ์ผ์์ ์ค์ ํ redis์ port ๋ฒํธ๋ฅผ ๊ฐ์ ธ์ ์ธ๋ถ redis์ ์ฐ๊ฒฐํฉ๋๋ค.
- RedisTemplate : RedisTemplate๋ฅผ ์ฌ์ฉํ์ฌ RedisConnection์์ ๋๊ฒจ์ค ๋ฐ์ดํธ ๊ฐ์ ์ง๋ ฌํํฉ๋๋ค. ์ด ๋, GenericJackson2JsonRedisSerializer์์ ์๊น ์ค์ ํด๋์๋ objectMapper() ์ค์ ์ ์ฌ์ฉํฉ๋๋ค.
3๏ธโฃ redis ์ํฐํฐ ์ค์
๐ domain/RecentLog
@RedisHash("userIdx")
@Getter @ToString @NoArgsConstructor @AllArgsConstructor @Builder
public class RecentLog {
@Id
private Long userIdx;
private PostDto.ListResponse postDto;
}
- RedisHash("value") : jpa์์ RecentLog๊ฐ Redis ์ํฐํฐ์์ ๋ช ์, ์ ๋ ธํ ์ด์ ์ ๋ค์ด๊ฐ ๊ฐ์ @Id ์ ๊ฒฐํฉํ์ฌ key๋ฅผ ์์ฑํ๋๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. Long , String ๊ฐ์ด ๋ค์ด๊ฐ ์ ์์ต๋๋ค.
- ์ ๋ ๊ธ์ ์์ฑํ ์ฌ์ฉ์์ ์ธ๋ฑ์ค ๊ฐ๊ณผ ํด๋น post๋ฅผ dto๋ก ๋ณํํ ๊ฐ์ ์ ์ฅํ๋๋ก ํด๋์์ต๋๋ค.
์ฃผ์ํ ์ ์ redis class์ Post์ ๊ฐ์ ๋ค๋ฅธ ์ํฐํฐ ์์ฒด๋ฅผ ์ ์ฅํ๋ฉด stackoverflowError๊ฐ ๋๋ค๋ ์ ์ ๋๋ค. ์ด ์๋ฌ๋ก ์ฝ์ง์ ๋ง์ด ํ๋๋ฐ, ๊ฐ์ ์ ์ฅํ ๋๋ ์ํฐํฐ๋ฅผ dto๋ก ๋ณํํด์ ์ ์ฅํ๋๋ก ํฉ์๋ค.
๐ Redis CRUD ๋ฐฉ์
redis์ ๊ฐ์ ์ ์ฅํ๊ณ ์ ๊ทผํ๋ ๋ฐฉ์์ 2๊ฐ๊ฐ ์์ต๋๋ค. ์ ๊ฐ ์ฌ์ฉํ redisTemplate์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ, CrudRepository๋ฅผ ์์๋ฐ์ redisRepository๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋๋ค. ์ต๊ทผ ์ฝ์ ๊ธ ๊ฐ์ ๊ฒฝ์ฐ๋ ์ ์ฅ๊ณผ ์กฐํ๋ง ๊ฐ๋จํ๊ฒ ํ์ํ๋ฏ๋ก, redisTemplate๋ฅผ ์ฌ์ฉํด๋ณด๊ฒ ์ต๋๋ค.
4๏ธโฃ Service ๋ง๋ค๊ธฐ
1) ์ต๊ทผ ์ฝ์ ๊ธ ์ ์ฅ
๋จผ์ postService์์ '์์ธ๋ณด๊ธฐ' ์กฐํ๋ฅผ ํ ๋, ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ ์ํ์ด๋ฉด ํด๋น ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ก ์ค์ ํด๋์์ต๋๋ค.
private final PostMapper postMapper;
private final PostRepository postRepository;
private final RedisTemplate<String, PostDto.ListResponse> redisTemplate;
// ์ต๊ทผ ์ฝ์ ๊ธ ์ ์ฅ
@Transactional
public void saveRecentReadPosts(Long userIdx, Long postIdx){
ListOperations<String, PostDto.ListResponse> listOperations = redisTemplate.opsForList();
Post post = postRepository.findById(postIdx)
.orElseThrow(() -> new EntityNullException(ErrorInfo.POST_NULL));
PostDto.ListResponse postDto = postMapper.postToListResponse(post, userIdx);
String key = "userIdx::"+ userIdx;
listOperations.leftPush(key, postDto);
redisTemplate.expireAt(key, Date.from(ZonedDateTime.now().plusDays(7).toInstant())); // ์ ํจ๊ธฐ๊ฐ TTL ์ผ์ฃผ์ผ ์ค์
}
- RedisTemplate ๋ฅผ Stirng(key ๊ฐ), PostDto ๋ก ๋ฐ๋๋ก ํฉ๋๋ค.
- post ์ธ๋ฑ์ค์ ํด๋นํ๋ Post ์ํฐํฐ๋ฅผ mapstruct๋ฅผ ํตํด dto๋ก ๋ณํํด์ฃผ์์ต๋๋ค. Entity → Dto ๋ณํ ๋ฐฉ์์ ํธํ ๋ฐฉ์๋๋ก ์ฌ์ฉํด์ฃผ์๋ฉด ๋ฉ๋๋ค.
- redisTemplate.opsForList() ๋ฅผ ์๋ก ๋ง๋ค์ด key๊ฐ๊ณผ postDto ๋ฅผ leftPush ๋ก ์ ์ฅํฉ๋๋ค.
- key๋ @RedisHash ์ ์ ๋ ฅํ value๊ฐ๊ณผ ์ฌ์ฉ์ ์ธ๋ฑ์ค ๊ฐ์ ํฉ์ณ์ string ํ์ ์ key๊ฐ์ผ๋ก ๋ง๋ค์์ต๋๋ค.
- ์ต๊ทผ ์ฝ์ ๊ธ์ ์ต๋ ์ผ์ฃผ์ผ๊น์ง๋ง ์ ์ฅํ ๊ฒ์ด๋ฏ๋ก, ํด๋น key์ redisTemplate์ ๋ง๋ฃ ๋ ์ง๋ฅผ ์ค๋๋ก๋ถํฐ ์ผ์ฃผ์ผ๋ก ์ค์ ํฉ๋๋ค.
2) ์ต๊ทผ ์ฝ์ ๊ธ ์กฐํ
์ฌ๊ธฐ์ ๊ฐ์ฅ ๋ง์ ์ฝ์ง์ ํ์ต๋๋ค. ๋ง์ฝ redisConfig์์ objectMapper() ์ค์ ์ ํ์ง ์์ผ๋ฉด, Could not read JSON: Cannot construct instance of java.time.LocalDateTime (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
์๋ฌ๊ฐ ๋ฉ๋๋ค. Redis์ ์ ์ฅํ ๋๋ Serialize ํ๊ณ , ์กฐํํ ๋๋ ๋ค์ Deserialize ํด์ ๊ฐ๊ณ ์จ๋ค๊ณ ์๊ฐํ๋ฉด ๋ฉ๋๋ค.
๊ฐ๋จํ๊ฒ dto์ @JsonDeserialize(using = LocalDateTimeDeserializer.class), @JsonSerialize(using = LocalDateTimeSerializer.class) ๋ฅผ ๋ถ์ฌ์ ์จ๋ ๋์ง๋ง, dto๋ง๋ค ๋ถ์ด๋ฉด ๋ฒ๊ฑฐ๋กญ๊ธฐ ๋๋ฌธ์ ์๊น์ฒ๋ผ custom ์ค์ ์ ํด์ฃผ๋ ๊ฒ์ด ํธํฉ๋๋ค.
// ์ต๊ทผ ์ฝ์ ๊ธ ์กฐํ
@Transactional
public List<PostDto.ListResponse> findRecentPosts(Long userIdx) {
ListOperations<String, PostDto.ListResponse> listOperations = redisTemplate.opsForList();
String key = "userIdx::" + userIdx;
long size = listOperations.size(key) == null ? 0 : listOperations.size(key); // NPE ์ฒดํฌํด์ผํจ.
return listOperations.range(key, 0, size);
}
- key ๊ฐ์ ํด๋นํ๋ ๋ชฉ๋ก ์ฌ์ด์ฆ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
- listOperations.range๋ก 0๋ถํฐ ๋ชฉ๋ก ์ฌ์ด์ฆ๊น์ง์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.
5๏ธโฃ Controller
์ต๊ทผ ์ฝ์ ๊ธ ์ ์ฅ์ ์์ธ๋ณด๊ธฐ์์ service๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ ๊ฒ์ด๊ณ , ์กฐํํ ๋ controller๋ง ๋ง๋ค์ด์ฃผ๋ฉด ๋ฉ๋๋ค.
@ApiOperation(value = "mypage", notes = "[๋ง์ดํ์ด์ง] ์ต๊ทผ ์ฝ์ ๊ธ ์กฐํ ")
@GetMapping("/recent")
public ResponseEntity<Object> findRecentPosts(HttpServletRequest request){
Long userIdx = tokenService.getUserIdx(tokenService.getTokenFromRequest(request));
return new ResponseEntity<>(ApiDefaultResponse.res(200, "",
listService.findRecentPosts(userIdx)), HttpStatus.OK);
}
- accessToken์์ userIdx ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์
- ์๊น ๋ง๋ค์ด ๋ service์ userIdx ๊ฐ์ ๋๊ฒจ์ฃผ๋ฉด ๋จ.
๐ ์ฐธ๊ณ ๋ธ๋ก๊ทธ
[spring] redis๋ก cachingํด์ dbms์ ๋ถํ ์ค์ด๊ธฐ - 1
[Java + Redis] Spring Data Redis๋ก Redis์ ์ฐ๋ํ๊ธฐ - RedisRepository ํธ
[Spring Boot] Redis (Lettuce)๋ฅผ ์ด์ฉํ ๊ฐ๋จํ API ์ ์
[ Redis ] Redis ํ๊ฒฝ ๊ฐ๋จํ๊ฒ ๊ตฌ์ถํ๊ธฐ
[spring] redis๋ก cachingํด์ dbms์ ๋ถํ ์ค์ด๊ธฐ - 2
RedisTemplate ๊ณผ Json Serializer ์ค์