Redis란?
- Remote Dictionary Server의 약자.
- Key-Value 형태의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반 DBMS
- 데이터베이스, 캐시, 메시지 브로커 등으로 사용되며, 인메모리 데이터 구조를 가진 저장소이다.
- db-engines.com 에 따르면 23년 11월 기준 key-value 형태의 저장소 중 Redis가 가장 인기 있는 DBMS이다.
※ 인메모리 데이터 저장소 사용 이유
- 데이터베이스에 데이터를 저장하는 경우, 매번 디스크에 접근해야 하기 때문에 사용자가 많아질수록 부하가 많아져서 성능이 저하된다. 굳이 데이터베이스에 저장하지 않아도 되는 경우, 이러한 부하를 줄이기 위해 인메모리 데이터 저장소를 사용할 수 있다.
Redis의 장점 및 특징
- 데이터를 메모리에 저장하여 대기 시간이 낮고 데이터 처리량이 높다.
- String, List, Set, Hash, JSON 등 다양한 데이터 타입을 지원한다.
- 단순한 명령 구조로 데이터의 저장 및 조회가 가능하다.
- 데이터의 영속성을 보장하기 위해 데이터를 메모리가 아닌 디스크에도 저장 가능하다.
- 싱글 스레드 방식을 사용하기 때문에,
- 한 번에 하나의 명령어만 처리하고, 따라서 연산을 원자적으로 처리하여 Race Condition이 거의 발생하지 않는다.
- 시간 복잡도가 높은 연산 작업에 대해서는 주의해야 한다.
사용방법
- Spring에서는 Srping-data-redis 라이브러리를 이용하여 Redis에 접근할 수 있다.
- Spring Boot에서 RedisRepository와 RedisTemplate 두 가지 방법으로 Redis를 사용할 수 있다.
- JPA를 주로 사용한다면 RedisRepository 방식이 더 친숙할 것이다.
1. 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
2. build.gradle 세팅
- redis는 기본 포트가 6379이기 때문에 따로 설정 안 해줘도 됨.
spring:
redis:
host: localhost
port: 6379
3. main 함수가 위치한 Application 파일에 @EnableRedisRepositories 어노테이션을 추가해 준다.
@EnableRedisRepositories
public class SpringCodeBaseApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCodeBaseApplication.class, args);
}
}
4. Entity 생성
- **@RedisHash**는 해당 클래스가 Redis Hash 데이터 모델을 나타낸다는 것을 나타낸다.
- Redis Hash는 Redis에서 제공하는 데이터 타입 중 하나로, 필드와 값을 갖는 맵 형태의 데이터 구조를 나타낸다. @RedisHash 어노테이션을 사용하면 Spring Data Redis가 해당 클래스를 Redis Hash로 매핑하고, 객체의 필드를 Redis Hash의 필드로 저장한다.
- @RedisHash("authNum") 어노테이션은 해당 클래스를 Redis Hash로 사용하겠다고 선언한다.
- @Id 어노테이션은 객체의 주요 식별자를 나타내며, 여기서는 id 필드를 주요 식별자로 사용하고 있다. 이 필드는 Redis Hash의 키(key)로 사용된다.
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
@RedisHash("authNum") // options: timeToLive = 10
@Getter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class AuthNum {
@Id
private String phoneNum;
private String randomNum;
}
5. RedisRepository 생성
- CrudRepository에서 제공하는 save()와 findById()만 테스트 용으로 사용할 것이기 때문에 별도의 메서드를 추가하지 않았다.
public interface AuthNumRedisRepository extends CrudRepository<AuthNum, String> {
}
주의할 점
- 관계형 데이터베이스와는 다르게 NoSQL 데이터베이스인 Redis를 다룰 때는 JpaRepository를 상속받지 않는다. JpaRepository는 주로 관계형 데이터베이스와의 상호 작용을 지원하기 위해 만들어졌기 때문이다. 따라서 CrudRepository를 extends 해준다.
6. API 테스트를 위한 DTO 만들기
public class AuthDto {
@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class phoneNumDto {
String phoneNum;
}
@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class AuthRequestDto {
String phoneNum;
String authNum;
}
@Getter
@Builder
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public static class AuthResponseDto{
String randomNum;
}
}
7. RedisService 만들기
- 인증번호가 올바르지 않는 경우에 대해 restControllerAdvice로 예외처리를 해주었다.
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class AuthNumService {
private final AuthNumRedisRepository authNumRedisRepository;
// 입력받은 전화번호에 대해 랜덤 인증번호를 생성하여 AuthNum을 만들어 저장.
@Transactional(readOnly = false)
public String newAuthNum(AuthDto.phoneNumDto phoneNumDto) {
String randomNum = RandomStringUtils.randomNumeric(6);
AuthNum savedAuthNum = authNumRedisRepository.save(
AuthNum.builder()
.phoneNum(phoneNumDto.phoneNum)
.randomNum(randomNum)
.build()
);
log.info("저장된 phoneNum : " + savedAuthNum.getPhoneNum());
return randomNum;
}
// 전화번호와 인증번호를 입력받아 저장된 값과 비교
public boolean checkAuth(AuthDto.AuthRequestDto authRequestDto) {
AuthNum authNum = authNumRedisRepository.findById(authRequestDto.phoneNum).orElseThrow(() -> new MemberExceptionHandler(ErrorStatus._PHONE_AUTH_NOT_FOUND));
if (authRequestDto.getAuthNum().equals(authNum.getRandomNum())) {
return true;
} else{
throw new MemberExceptionHandler(ErrorStatus._PHONE_AUTH_INVALID);
}
}
}
8. 컨트롤러 만들기
@RestController
@RequiredArgsConstructor
@Slf4j
public class AuthNumRestController {
private final AuthNumService authNumService;
@PostMapping("/sms/request")
public ResponseDto<AuthDto.AuthResponseDto> newMessage(@RequestBody AuthDto.phoneNumDto phoneNumDto) {
String randomNum = authNumService.newAuthNum(phoneNumDto);
return ResponseDto.of(AuthDto.AuthResponseDto.builder()
.randomNum(randomNum)
.build());
}
@PostMapping("/sms/check")
public ResponseDto<Boolean> authCheck(@RequestBody AuthDto.AuthRequestDto authRequestDto) {
return ResponseDto.of(authNumService.checkAuth(authRequestDto));
}
}
테스트 결과
반응형
'Study > Spring' 카테고리의 다른 글
스웨거 GET API 응답 속도 저하 해결 (1) | 2024.03.29 |
---|---|
[Spring] XML Parsing (0) | 2024.03.22 |
[트러블슈팅] NCP SENS API 호출 시, 401 Unauthorized 에러 (0) | 2023.11.08 |
네이버 SENS API 사용 방법(feat. Feign Client) (1) | 2023.11.08 |
연관관계 매핑 기본 개념 (0) | 2022.11.17 |