2024.07.03 - [Spring/대용량 트래픽] - R2DBC 알아보기!!
R2DBC 알아보기!!
2024.07.03 - [Spring/대용량 트래픽] - Spring Webflux에서 WebClient 사용하기 Spring Webflux에서 WebClient 사용하기2024.05.29 - [Spring/대용량 트래픽] - Spring Webflux 사용해보기 Spring Webflux 사용해보기2024.05.22 - [Spri
lsdiary.tistory.com
이전 포스팅에서는 R2DBC가 무엇인지 개념에 대해서 알아봤다. 이제 실제로 해볼 시간이다.
** 준비물
- MySQL8 (Docker container)
- Gradle
- spring-boot-starter-data-r2dbc
- io.asyncer:r2dbc-mysql
- Repository
- ReactiveCrudRepository
도커에 mysql이 설치되어 있지 않으면 다음글에서 설치하고 실행하는 법을 찾아보길 바란다.
https://lsdiary.tistory.com/82
Redis Cache로 실습하기
2024.05.07 - [Spring/대용량 트래픽] - Redis Cache 이론 Redis Cache 이론2024.05.07 - [Spring/대용량 트래픽] - Redis Key, Scan 명령어 Redis Key, Scan 명령어2024.05.07 - [Spring/대용량 트래픽] - Redis Transactions Redis Transact
lsdiary.tistory.com
도커로 mysql을 실행한 뒤 DB를 하나 만들고 users 테이블을 생성한다.
create table users (
id bigint auto_increment primary key,
name varchar(128),
email varchar(255),
created_at datetime not null default CURRENT_TIMESTAMP,
updated_at datetime not null default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
);
추가적으로 post에 대한 테이블도 생성한다.
create table posts
(
id bigint auto_increment primary key,
user_id bigint,
title varchar(30),
content varchar(200),
created_at datetime not null default CURRENT_TIMESTAMP,
updated_at datetime not null default CURRENT_TIMESTAMP
);
두 개의 테이블을 생성했다면 다음으로는 인덱스를 추가해준다.
Index란?
DB에서 빠른 탐색을 도와주는 도구이다. 정해진 키(속성)에 대해서 탐색할수 있도록 설정할수 있다.
내부적으로 B+Tree 형태로 되어있고, 미디어 파일같은 경우에는 R-Tree로 구성되어있다.
트리 형태가 balance해서 이진트리에 비해 엄청난 성능을 보여준다. 우리는 속도가 매우중요한데, B+Tree를 통한 디스크 I/O(Page 참조횟수)는 B+Tree 의 Depth가 결정하므로 적은 Page참조를 하면서 DB를 탐색할수 있다.
create index idx_user_id on posts (user_id);
다음으로는 Gradle 의존성을 추가한다. 구현체로는 asyncer를 사용한다(버전은 스프링부트3에 호환되는 버전이다).
implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
implementation 'io.asyncer:r2dbc-mysql:1.0.2'
r2dbc에 연결해줬으므로 위에 만들어둔 DB에 연결하기 위해 설정정보를 세팅해준다.
spring:
r2dbc:
url: r2dbc:mysql://localhost:3306/fastsns
username: root
password: ${DB_PASSWORD}
**아직 연결된거 아님!!(설정이 제대로 맞는건지 검증 부분이 필요함)
R2dbcConfig.java
@Component
@Slf4j
@RequiredArgsConstructor
public class implements ApplicationListener<ApplicationReadyEvent> {
private final DatabaseClient databaseClient;
// 어플리케이션이 실행될때
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
//간단한 쿼리
databaseClient.sql("SELECT 1").fetch().one()
//리액터에 있는 subscribe -> 즉 쿼리문을 받았는지 검증
.subscribe(
//핸들러
success ->{
log.info("Initialize r2dbc database connection");
},
error -> {
log.error("Failed to initialize r2dbc database connection");
}
);
}
}
이렇게 설정해주면 비밀번호가 틀렸을때 바로 연결이 종료되는것을 확인할 수 있다.
더 강력하게 검증하고 싶으면 아래와 같은 코드를 error 핸들러에 설정해줄수 있다. 이렇게 설정해주면 연결이 실패할 경우 자동으로 어플리케이션을 종료해준다.
SpringApplication.exit(event.getApplicationContext(), () -> -110);
이전 글에서 @EnableR2dbcRepositories 어노테이션이 필요하다고 했었다. Config 파일에 추가하자.
그리고 미리 만들어놨던 entity 클래스에 만든 테이블을 연결시켜준다.
User.java
@Data
@Builder
@AllArgsConstructor
@Table("users")
public class User {
@Id
private Long id;
private String name;
private String email;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
@CreatedDate와 @LastModifiedDate를 사용하기 위해서는 기존 jdbc에서는 @EntityListeners(AuditingEntityListener.class) 다음과 같은 어노테이션을 두어서 사용했다. r2dbc에서는 @EnableR2dbcAuditing 어노테이션으로 Config파일에 설정해줘야 변경되는 시간을 리스너로 받을수 있다.
UserR2dbcRepository.java
public interface UserR2dbcRepository extends ReactiveCrudRepository<User, Long> {
}
ReactiveCrudRepository를 상속받으면 바로 R2DBC를 사용할수 있다. JPA처럼 정말 간단하다.
UserService.java
@Service
@RequiredArgsConstructor
public class UserService {
// private final userRepository userRepository;
private final UserR2dbcRepository userR2dbcRepository;
public Mono<User> create(String name, String email){
return userR2dbcRepository.save(User.builder().name(name).email(email).build());
}
public Flux<User> findAll(){
return userR2dbcRepository.findAll();
}
public Mono<User> findById(Long id){
return userR2dbcRepository.findById(id);
}
public Mono<Void> deleteById(Long id){
return userR2dbcRepository.deleteById(id);
}
public Mono<User> update(Long id, String name, String email){
return userR2dbcRepository.findById(id)
.flatMap(u -> {
u.setName(name);
u.setEmail(email);
return userR2dbcRepository.save(u);
});
}
}
MVC로 코드를 짜본 사람은 위에 코드가 정말 익숙한 형태라는것을 느꼈을 것이다.
- MVC : String, ModelAndView, ResponseEntity, 도메인 객체 등을 반환
- Webflux : Mono<T>, Flux<T> 등의 객체를 반환
반환타입을 제외하고는 딱히 다른점이 없다!
UserController.java
@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {
private final UserService userService;
@PostMapping("")
public Mono<UserResponse> createUser(@RequestBody UserCreateRequest userCreateRequest){
return userService.create(userCreateRequest.getName(), userCreateRequest.getEmail())
.map(UserResponse::of);
}
@GetMapping("")
public Flux<UserResponse> findAllUsers(){
return userService.findAll()
.map(UserResponse::of);
}
@GetMapping("/{id}")
public Mono<ResponseEntity<UserResponse>> findUser(@PathVariable Long id){
return userService.findById(id)
.map(u -> ResponseEntity.ok(UserResponse.of(u)))
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
}
@DeleteMapping("/{id}")
public Mono<ResponseEntity<?>> deleteUser(@PathVariable Long id){
//정상응답 205가 아니라 204 (no content) 응답으로 주고 싶다.
return userService.deleteById(id).then(
Mono.just(ResponseEntity.noContent().build())
);
}
@PutMapping("/{id}")
public Mono<ResponseEntity<UserResponse>> updateUser(@PathVariable Long id, @RequestBody UserUpdateRequest request){
return userService.update(id, request.getName(), request.getEmail())
.map(u -> ResponseEntity.ok(UserResponse.of(u)))
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
}
}
테스트
나머지 url도 전부 잘 작동하는것을 확인할 수 있다.
이전에는 ConcurrentHashMap을 통해서 실제 DB에 저장하지 않았지만, 지금은 R2DBC로 실제 MySQL에 저장할것이므로 DB에 잘 저장되어 있는지 CLI를 통해 확인해보자.
다음으로는 posts테이블에 대한 예시코드를 작성하자.
Post.java
@Data
@NoArgsConstructor
@Table(name = "posts")
public class Post {
@Id
private Long id;
@Column("user_id")
private Long userId;
private String title;
private String content;
@Column("created_at")
@CreatedDate
private LocalDateTime createdAt;
@Column("updated_at")
@LastModifiedDate
private LocalDateTime updatedAt;
}
PostServiceV2.java
@Service
@RequiredArgsConstructor
public class PostServiceV2 {
private final PostR2dbcRepository postR2dbcRepository;
//create
public Mono<Post> create(Long userId, String title, String content) {
return postR2dbcRepository.save(Post.builder()
.userId(userId)
.title(title)
.content(content)
.build());
}
//read
public Flux<Post> findAll(){
return postR2dbcRepository.findAll();
}
public Mono<Post> findById(Long id) {
return postR2dbcRepository.findById(id);
}
//delete
public Mono<Void> deleteById(Long id) {
return postR2dbcRepository.deleteById(id);
}
}
PostControllerV2.java
@RestController
@RequiredArgsConstructor
@RequestMapping("/v2/posts")
public class PostControllerV2 {
private final PostServiceV2 postServiceV2;
@PostMapping("")
public Mono<PostResponseV2> createPost(@RequestBody PostCreateRequest request) {
return postServiceV2.create(request.getUserId(), request.getTitle(), request.getContent())
.map(PostResponseV2::of);
}
@GetMapping("")
public Flux<PostResponseV2> findAllPosts() {
return postServiceV2.findAll()
.map(PostResponseV2::of);
}
@GetMapping("/{id}")
public Mono<ResponseEntity<PostResponseV2>> findPost(@PathVariable Long id) {
return postServiceV2.findById(id)
.map(p -> ResponseEntity.ok().body(PostResponseV2.of(p)))
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
}
@DeleteMapping("/{id}")
public Mono<ResponseEntity<?>> deletePost(@PathVariable Long id) {
return postServiceV2.deleteById(id).then(Mono.just(ResponseEntity.noContent().build()));
}
}
테스트
이렇게 R2DBC를 사용한 연습도 끝났다..
2024.07.07 - [Spring/대용량 트래픽] - Spring Webflux R2DBC 여러가지 기능들
Spring Webflux R2DBC 여러가지 기능들
2024.07.06 - [Spring/대용량 트래픽] - Spring Webflux R2DBC 사용해보기 Spring Webflux R2DBC 사용해보기2024.07.03 - [Spring/대용량 트래픽] - R2DBC 알아보기!! R2DBC 알아보기!!2024.07.03 - [Spring/대용량 트래픽] - Spring W
lsdiary.tistory.com
'Spring > 대용량 트래픽' 카테고리의 다른 글
Reactive Redis란? (0) | 2024.07.07 |
---|---|
Spring Webflux R2DBC 여러가지 기능들 (0) | 2024.07.07 |
R2DBC 알아보기!! (0) | 2024.07.03 |
Spring Webflux에서 WebClient 사용하기 (0) | 2024.07.03 |
Spring Webflux 사용해보기 (0) | 2024.05.29 |