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



둘 간 호환이 되지 않기 때문에, MVC → Webflux, Webflux → MVC로 전환을 하게 된다면, Reactor API 기반 Webflux 코드부터 시작해서 WebClient 또는 RestTemplate 등 DB 요청까지 다시 작성해야 한다.
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'
}
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); // 종료시키기
}
);
}
}
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()));
}
}
@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);
}
}