R2DBC :: Spring Data Relational

위의 문제점을 해결하기 위해 나온 것이 R2DBC[Reactive Relational Database Connectivity]이다.

Untitled

Spring MVC와 Spring Webflux 비교

Untitled

Untitled

둘 간 호환이 되지 않기 때문에, MVC → Webflux, Webflux → MVC로 전환을 하게 된다면, Reactor API 기반 Webflux 코드부터 시작해서 WebClient 또는 RestTemplate 등 DB 요청까지 다시 작성해야 한다.

실습

1. build.gradle

dependencies{
implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    **implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'**
    **implementation "io.asyncer:r2dbc-mysql:1.0.2"// spi 1.0.0 (spring boot3)**
		compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
}

2. connection 정보 추가

spring:
  r2dbc:
    url: r2dbc:mysql://localhost:3306/sns
    username: root
    password: 1234

lazy하게 연결을 해서 참고만 할 뿐 (틀리게 적어도 실행은 된다)

**@EnableR2dbcAuditing**
**@EnableR2dbcRepositories**
@Slf4j
@RequiredArgsConstructor
@Component
public class R2dbcConfig implements ApplicationListener<ApplicationReadyEvent> {

    private final DatabaseClient databaseClient;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // DB가 연결될 때 연결 검증을 위함
        databaseClient.sql("SELECT 1").fetch().one()
                .subscribe( // Reactor에 있는 pub/sub을 말함
                        success -> {
                            log.info("Initialize r2dbc databas connection");
                        },
                        error -> {
                            log.error("Failed to initialize r2dbc database connection");
                            SpringApplication.exit(event.getApplicationContext(), () -> -110); // 종료시키기
                        }
                );
    }
}

3. controller

PostController 예시

@RequiredArgsConstructor
@RequestMapping("/v2/posts")
@RestController
public class PostControllerV2 {

    private final PostServiceV2 postServiceV2;

    @PostMapping("")
    public Mono<PostResponseV2> createPost(@RequestBody PostCreateRequest request){
        return postServiceV2.createPost(request.getUserId(), request.getTitle(), request.getContent())
                .map(PostResponseV2::of);
    }

    @GetMapping("")
    public Flux<PostResponseV2> findAllPost() {
        return postServiceV2.findAll()
                .map(PostResponseV2::of);
    }

    @GetMapping("/{id}")
    public Mono<ResponseEntity<PostResponseV2>> findPost(@PathVariable Long id) {
        return postServiceV2.findById(id)
                .map(post -> ResponseEntity.ok().body(PostResponseV2.of(post)))
                .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
    }

    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<PostResponseV2>> deletePost(@PathVariable Long id) {
        return postServiceV2.deleteById(id)
                .then(Mono.just(ResponseEntity.noContent().build()));
    }
}

4. service

@RequiredArgsConstructor
@Service
public class PostServiceV2 {

    private final PostR2dbcRepository postR2dbcRepository;

// create
public Mono<Post> createPost(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);
    }

    public Flux<Post> findAllByUserId(Long id) {
        return postR2dbcRepository.findAllByUserId(id);
    }

// delete
public Mono<Void> deleteById(Long id) {
        return postR2dbcRepository.deleteById(id);
    }

}

5. repository