오늘은 스프링 프로젝트에 Redis를 설정하고 어떻게 적용했는지에 대해 포스팅하겠습니다.
Redis 사용 준비
먼저 Redis 설치는 로컬에 하는 방법과, 도커를 이용하는 방법이 있습니다. 저는 Docker를 이용해서 설치해보도록 하겠습니다.
처음 시작하시는 분들이 있을 수 있기에 기본적인 Dockerfile도 같이 업로드하겠습니다.
혹시 도커가 설치 안 되신 분들은 설치하시고 진행하시면 됩니다.
Docker 세팅하기
Dockerfile
FROM openjdk:11
ARG JAR_FILE=*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
저는 Java11 버전으로 프로젝트를 만들었습니다.
docker-compose.yml
version: '3.8'
services:
mysqldb:
image: mysql
restart: always
environment:
MYSQL_DATABASE: capstone
MYSQL_ROOT_PASSWORD: "root"
ports:
- 3306:3306
redis:
image: redis
ports:
- 6379:6379
backend:
build: .
restart: always
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysqldb:3306/capstone
SPRING_DATASOURCE_USERNAME: "root"
SPRING_DATASOURCE_PASSWORD: "root"
ports:
- 8080:8080
depends_on:
- mysqldb
- redis
build.gradle과 application.yml 설정하기
build.gradle
build.gradl에 redis 디펜던시 설치를 위해 dependencies 안에 추가해주세요.
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/capstone?useSSL=false&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: redis
port: 6379
jpa:
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
use-new-id-generator-mappings: false
database: mysql
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
properties:
hibernate:
show_sql: true
format_sql: true
use_sql_comments: true
application.yml도 본인에게 맞는 걸로 설정하면 됩니다. 다만 redis를 추가하기 위해서는 위에 redis 부분은 꼭 설정해주셔야 합니다.
마지막으로 스프링 프로젝트에서 값을 숨기기 위해서 secure.properties를 다음과 같이 설정해주었습니다.
redis.host=localhost
redis.port=6379
스프링 프로젝트에 Redis 적용하기
RedisConfig
package com.example.capstone.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
@PropertySource("classpath:secure.properties")
public class RedisConfig {
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<?, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
RedisConfig를 만들어서 위에와 같은 코드를 입력해줍니다. 이는 Redis를 사용하기 위한 기본 설정이라고 보시면 됩니다.
이제 바로 적용해보겠습니다.
Redis 적용 상황 분석
먼저 저는 프로젝트에서 같이 먹으면 안 되는 약을 조회하는 API를 만들고 있었습니다.
약A, 약B를 쿼리 파라미터로 받아서 데이터베이스에서 이걸 이용해서 병용금기 엔터티를 조회하고, 금기 이유를 리턴해주는 API입니다.
문제는 병용금기 내역의 데이터가 너무 많기 때문에 조회 시 너무 헤비 하다는 단점이 있었습니다.
이를 보완하기 위해서 생각한 방법이 유저들이 자주 검색하는 병용금기 약에 대해 view_count 즉 조회수를 두고, 일정 조회수를 넘어가면 이 병용금기 내역을 redis 데이터베이스에 추가하는 방법으로 진행하고자 했습니다.
package com.example.capstone.service.contraindicate;
import com.example.capstone.entity.contraindicate.Contraindicate;
import com.example.capstone.exception.ContraindicateNotFoundException;
import com.example.capstone.repository.contraindicate.ContraindicateRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
public class ContraindicateService {
private final RedisTemplate<String, String> redisTemplate;
private final ContraindicateRepository contraindicateRepository;
@Transactional
public Object findContraindicate(String pillA, String pillB) {
if (getRedis(pillA, pillB) == null) {
// 레디스에서 조회 했는데 없는 경우
// 1. 디비 조회
Contraindicate contraindicate = contraindicateRepository.findByPillAAndPillB(pillA, pillB)
.orElseThrow(ContraindicateNotFoundException::new);
// 2. viewCount 증가 (if >= 3 추가)
contraindicate.addViewCount();
if (contraindicate.getViewCount() >= 3) {
setRedis(pillA, pillB, contraindicate.getSymptom());
}
return contraindicate.getSymptom();
}
// 레디스에서 조회 했는데 있는 경우
// 출력
return getRedis(pillA, pillB);
}
public void setRedis(String key, String pillVal, String symptom) {
HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
hashOperations.put(key, pillVal, symptom);
}
public Object getRedis(String key, String pillVal) {
HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
Object symptom = hashOperations.get(key, pillVal);
return symptom;
}
}
약A에 대해 약B, 약C 등등 여러 가지의 병용금기 약이 있고 그에 따른 symptom도 있기 때문에 저는 위와 같이 리스트로 구현했습니다.
HashMap으로 해도 상관은 없을 듯합니다.
RedisTemplate에는 여러 가지 타입을 넣을 수 있으니 다들 상황에 맞게 잘 구현하시면 될 것 같습니다 :)
++ 병용 금기 내용은 변하지 않으므로, MySQL과 Redis 사이에서 데이터의 정합성의 문제로부터 벗어날 수 있다고 판단하고, 영속성과 동시성 문제는 추후에 각각 캐시파일로 만들고, 코드를 리팩토링하면서 해결해야할 것 같습니다.
'Develop > Spring Framework' 카테고리의 다른 글
스프링 서버를 Docker, Docker-compose로 멋들어지게 띄워보기 (0) | 2023.01.09 |
---|---|
스프링에서 properties 파일을 통해 민감 정보 감추는 방법 (2) | 2022.11.07 |