NoSQL 스토리지 시스템은, 수평적 확장성과 속도를 위해 기존 RDBMS에 대한 대안을 제공.
그 중 Redis는 Spring Data 가 지원하는 Key-Value 저장소 중 하나이다. 그리고 다른 NoSQL 들이 disk 나 SSD 에 저장하는 것과 달리, Redis 는 in-memory 에 저장한다.
in-memory 저장 덕분에, Redis 는 데이터 접근 속도가 매우 매우 빠르다 (ms 단위라고 함).
Memory SnapShot
메모리에 있는 데이터를 스냅샷을 찍어 통째로 디스크에 옮겨준다. 그리고 필요할 때 다시 메모리에 올려준다.
Redis 설치 완료된 상태에서
(Redis Window 설치 - https://aljjabaegi.tistory.com/616)
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Redis와 Spring을 사용할 때 첫 번째 작업은, IoC 컨테이너를 통해 저장소를 연결한다.
RedisConnection 은 RedisConnectionFactory 를 통해 생성된다.
RedisConnectionFactory 는 크게 2가지 방식으로 구현된다. 하나는 Jedis , 나머지 하나는 Lettuce Redice Clients 이다. 이 중 Jedis 는 Redis 공식 문서에 Java 추천 방법으로 나와있다.
하지만 Lettuce 가 SPring Data Redis 를 구현하고, thread-safe , asynchronous 하기 때문에 reactive applications 에서 쓰기 좋다.
다음은 Lettuce 연결 팩토리를 생성하는 방법이다
@Configuration
class AppConfig {
private String redisHost = ...;
private int redisPort = ...;
// Creating Connection with Redis
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisHost, redisPort));
}
// Creating RedisTemplate for Entity 'Employee'
@Bean
public RedisTemplate<String, Employee> redisTemplate() {
RedisTemplate<String, Employee> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
Employee 를 Entity 로 사용할 것이므로 다음과 같이 RedisTemplate 를 명시해주자. (Employee 대신 Object 로 할 경우도 상관 X)
redis 를 이용해 CRUD 할 Entity 를 먼저 만들자
@Data
@NoArgsConstructor
@AllArgsConstructor
// No @Entity concept here
public class Employee implements Serializable {
private static final long serialVersionUID = -7817224776021728682L;
private Integer empId;
private String empName;
private Double empSalary;
}
Redis 를 사용하기 때문에 @Entity 를 사용하지 않는다. 하지만 Redis 의 엔티티로 사용하려면 Serializable 인터페이스를 꼭 구현해야한다. (Employee 데이터를 하나의 Employee 테이블 같이 묶기 위해서 필요하다.)
Serializable 을 안 쓴다면 Key 값에 Employee 들을 구분하기 위해 Employee:id 이렇게 넣어야한다.
public interface EmployeeRepository {
void saveEmployee(Employee emp);
Employee getOneEmployee(Integer id);
void updateEmployee(Employee emp);
Map<Integer, Employee> getAllEmployees();
void deleteEmployee(Integer id);
void saveAllEmployees(Map<Integer, Employee> map);
}
Employee 에서 사용할 DB 기능들을 정리해 놓은 인터페이스
@Repository
public class EmployeeRepositoryImpl implements EmployeeRepository{
private final String hashReference = "Employee";
@Resource(name = "redisTemplate") // 빨간 줄 무시
private HashOperations<String, Integer, Employee> hashOperations;
@Override
public void saveEmployee(Employee emp) {
hashOperations.putIfAbsent(hashReference, emp.getEmpId(), emp);
}
@Override
public void saveAllEmployees(Map<Integer, Employee> map) {
hashOperations.putAll(hashReference, map);
}
@Override
public Employee getOneEmployee(Integer id) {
return hashOperations.get(hashReference, id);
}
@Override
public void updateEmployee(Employee emp) {
hashOperations.put(hashReference, emp.getEmpId(), emp);
}
@Override
public Map<Integer, Employee> getAllEmployees() {
return hashOperations.entries(hashReference);
}
@Override
public void deleteEmployee(Integer id) {
hashOperations.delete(hashReference, id);
}
}
@Resource 를 이용해서 생성한 redisTemplate 빈을 이름으로 찾는다. (@Autowired 와의 차이점) 그리고 HashOperations 에 주입시켜준다. (원래는 redisTemplate.opsForHash()
로 생성 가능)
@Component
public class RedisOperationsRunner implements CommandLineRunner {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public void run(String... args) throws Exception {
//saving one employee
employeeRepository.saveEmployee(new Employee(500, "Emp0", 2150.0));
//saving multiple employees
employeeRepository.saveAllEmployees(
Map.of( 501, new Employee(501, "Emp1", 2396.0),
502, new Employee(502, "Emp2", 2499.5),
503, new Employee(503, "Emp4", 2324.75)
)
);
//modifying employee with empId 503
employeeRepository.updateEmployee(new Employee(503, "Emp3", 2325.25));
//deleting employee with empID 500
employeeRepository.deleteEmployee(500);
//retrieving all employees
employeeRepository.getAllEmployees().forEach((k,v)-> System.out.println(k +" : "+v));
//retrieving employee with empID 501
System.out.println("Emp details for 501 : "+employeeRepository.getOneEmployee(501));
}
}
CommandLineRunner 를 구현하면 Spring Application 에 포함되는 순간 자동 실해될 수 있도록 해준다.
순서는 @Ordered 어노테이션을 이용해 지정할 수 있다.
🥳실행 결과
지금까지 Redis 를 스프링 부트에서 사용하는 방법에 대해 알아봤다.
이를 이용해 JWT 토큰 관리를 Redis 로 하도록 바꿔보는 토이 프로젝트를 진행해보자
더 다양한 예제 - https://blog.kingbbode.com/25
출처 - https://freeblogger.tistory.com/10
공식 문서 - https://redis.io/docs/ui/cli/
Spring Boot 는 프로젝트 시작 시 연결할 수 있는 DB 가 있나 체크한다. 그러므로 아무 DB라도 연결해놓아야 한다
Spring Security 를 포함해서 빌드했는데 Spring Boot 를 실행할 때 자동으로 실행돼서 요청 URL 에 대해 인증이 없던 상태라 발생한 오류였다.
참고 :
https://javatechonline.com/spring-boot-redis-crud-example/#Jedis_Connector_JedisConnectionFactory
https://www.woolog.dev/backend/spring-boot/spring-boot-redis-dessert-simple/
Redis 공식 - https://redis.io/docs/clients/java/