| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- docker
- 딥시크
- IntelliJ
- AOP
- 인증
- 로그인
- 컨퍼런스
- NGINX
- Kotlin
- 공동인증서
- AWS
- postgis
- Spring Boot
- API
- PostgreSQL
- exception
- db
- 본인인증
- Mono
- webflux
- deepseek vs chatgpt
- 보안
- spring security
- Flux
- netty
- 허깅 페이스
- 코틀린
- ktlin
- Spring
- 본인확인
- Today
- Total
[수미수의 개발 브로구]
[WebFlux] Spring WebFlux R2DBC를 이용한 데이터베이스 연동 본문
들어가기 전
Spring WebFlux#4 까지 비동기 기반의 Spring Framework 인 WebFlux 를 이용하여, 프로젝트를 생성 하고, 비지니스 로직을 구현하기 위한 패키지 구성 그리고 샘플 API 를 생성하여 응답 테스트 까지 하였다. 이번 장에서는 WebFlux 에서 데이터베이스 연동 및 데이터베이스로 부터 데이터를 가공 하여 응답 하는 예제 샘플을 통해서 WebFlux 에서는 어떻게 데이터베이스 연결 하는지에 대해서 알아 본다.
WebFlux 에서 데이터베이스와 연동하기 위해서 지금까지 사용했던 JDBC 가 아닌 새로운 프레임워크인 R2DBC (Reactive Relational Database Connectivity) 를 사용하야 하며, 이는 JDBC 와 다르게 Reactive Stream 을 사용하여 블로킹 하지 않으면서 비동기적으로 데이터베이스 연산을 수행 할 수 있게 해준다. 주로 데이터 베이스 연동을 위한 비동기 API 로 사용되며, 대부분의 관계형 데이터베이스 (예, PostgreSQL, MySQL, H2 등)과 통합하여 사용 할 수 있다. WebFlux 와 R2DBC 를 함께 사용하여, WebFlux 의 장점을 살릴 수 있으며, 효율적인 비동기 통신을 할 수 있다. 이를 통해 WebFlux 가 추구 하는 적은 리소스를 사용하면서 대용량 트래픽을 처리 하는 애플리케이션 개발을 할 수 있게된다.
따라하기
해당 글에서는 고객 도메인에서 고객 테이블에 고객 정보 조회/등록/수정/삭제 라는 Use Case 를 처리 하는 예제를 기반으로 설명 하고자 한다.
개발 환경
- Spring WebFlux with 2.5.12
- Java 17
- MySQL
- Kotlin
build.gradle 설정하기
R2DBC 를 사용하기 위해서 build.gradle.kt 파일에 dependencies 부분에 아래와 같이 라이브러리 프로젝트를 추가 한다.
............
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
runtimeOnly("mysql:mysql-connector-java")
runtimeOnly("dev.miku:r2dbc-mysql")
............
테이블 스키마
아래는 고객 정보를 저장하기 위한 테이블 스키마 SQL 예제이다.
CREATE TABLE TBL_CUSTOMER(
ID INT AUTO_INCREMENT COMMENT '아이디',
CUSTOMER_NAME VARCHAR(14) COMMENT '고객 이름',
ADDRESS VARCHAR(300) COMMENT '주소',
..........................
PRIMARY KEY (ID)
);
Entity 클래스 생성
해당 글에서는 앞서 설명한 바와 같이 Customer 도메인과 관련된 서비스를 개발한다고 가정하고, TBL_CUSTOMER 테이블을 조회/처리 하는 비지니스에 대해서 설명 한다. 아래 Customer 엔티티 클래스는 TBL_CUSTOMER 라는 테이블에 대한 속성과 행위를 가지는 클래스이며, 기존에 JPA 를 사용했던 것 처럼 어노테이션을 사용하여, 테이블 명과 Id 값을 지정 해준다.
@Table("TBL_CUSTOMER")
data class Customer(
@Id
val id: Long? = null,
val customerName: String,
val address: String,
)
Repository 클래스 생성
데이터 베이스로 부터 데이터를 조회/등록/수정/삭제를 하기 위한 Repository 클래스를 생성 한다. 이때, R2dbEntityTemplate 를 주입하여 사용 한다. 아래 예제 소스는 Customer 엔티티를 이용하여, 새로운 Customer 데이터를 등록, 수정, 조회, 삭제 한다. 이때 리스트 조회 시 Flux 로 반환하고, 단일 조회 시 Mono 로 반환 한다.
R2dbEntityTemplate
R2DBC 에서 제공하는 클래스로, 비동기로 데이터베이스와 상호작용을 해주는 클래스이며, 엔티티 클래스를 통해서 CRUD 작업을 처리할 수 있게 지원 해주는 클래스이다. 해당 클래스를 사용하여 간단하게 CRUD 기능을 사용 할 수 있고, 복잡한 쿼리를 위한 쿼리 메서드 또한 지원 한다.
class CustomerRepository(private val r2dbcEntityTemplate : R2dbcEntityTemplate){
fun insertCustomer(customer: Customer): Mono<Customer> {
return r2dbcEntityTemplate.insert(customer)
}
fun deleteCustomer(id: Long): Mono<Int> {
return r2dbcEntityTemplate.delete(Query.query(where("id").`is`(id)), Customer::class.java)
}
fun updateCustomer(id: Long, customer: Customer): Mono<Int> {
return r2dbcEntityTemplate.update(Customer::class.java)
.matching(Query.query(where("id").`is`(id)))
.apply(
Update.update("customerName", customer.customerName)
.set("address", customer.address)
)
}
fun getCustomerList(): Flux<Customer> {
return r2dbcEntityTemplate.select(Customer::class.java).all()
}
fun getCustomerByCustomerName(customerName: String): Mono<Customer> {
return r2dbcEntityTemplate.selectOne(Query.query(where("customerName").`is`(customerName)), Customer::class.java)
}
fun getCustomerById(id: Long): Mono<Customer> {
return r2dbcEntityTemplate.selectOne(Query.query(where("id").`is`(id)), Customer::class.java)
}
}
Service 클래스 생성
엔티티 클래스와 Repository 클래스를 모두 정의 하였다. 이제는 Customer 라는 도메인에 대해서, 비지니스 Use Case 를 담당하는 Service 클래스를 생성 한다. 아래 예제 소스는 Customer 에 대한 조회, 추가, 수정, 삭제 에대한 Use Case 에 대해서 구현한 클래스 이다.
class CustomerService(private val repository: CustomerRepository) {
fun getCustomer(id: Long): Mono<Customer> {
return repository.getCustomerById(id)
.switchIfEmpty(Mono.defer {
throw CustomerNotFoundException(CustomerExceptionMessage.CUSTOMER_NOT_FOUND.code, CustomerExceptionMessage.CUSTOMER_NOT_FOUND.message ) })
}
fun getCustomerLis(): Flux<Customer> {
return repository.getCustomerList().switchIfEmpty(Flux.defer { throw CustomerNotFoundException(CustomerExceptionMessage.CUSTOMER_NOT_FOUND.code, CustomerExceptionMessage.CUSTOMER_NOT_FOUND.message ) })
}
@Transactional
fun postCustomer(customer: Customer): Mono<Customer> {
return repository.insertCustomer(customer)
}
@Transactional
fun putCustomer(id: Long, customer: Customer): Mono<Customer> {
return repository.getCustomerById(id)
.switchIfEmpty(Mono.defer { throw CustomerNotFoundException(CustomerExceptionMessage.CUSTOMER_NOT_FOUND.code, CustomerExceptionMessage.CUSTOMER_NOT_FOUND.message ) })
.then(repository.updateCustomer(id ,customer))
.then(repository.getCustomerById(id))
}
@Transactional
fun deleteCustomer(id: Long): Mono<Int> {
return repository.getCustomerById(id)
.switchIfEmpty(Mono.defer { throw CustomerNotFoundException(CustomerExceptionMessage.CUSTOMER_NOT_FOUND.code, CustomerExceptionMessage.CUSTOMER_NOT_FOUND.message ) })
.then(repository.deleteCustomer(id))
}
}
Controller 클래스
결론
이번 글에서는 WebFlux 를 이용하여 데이터 베이스를 핸들링 하기 위한 방법에 대해서 예제 코드와 함께 설명 하였다. 기본적으로 사용되는 JDBC 의 경우 WebFlux 와 같이 사용 할 경우 오히려 성능면에서 이점을 발휘할 수 없으며, 대안으로 R2DBC 를 사용하여 비동기/논블럭킹 기반으로 데이터베이스를 핸들링 할 수 있다. 실제 업무에 해당 기능을 적용하기 위해 많은 검색을 하였지만 레퍼런스가 부족하였다. 하지만, 현재 업무에서 대용량 요청에도 장애 없이 잘 사용하고 있으며, 복잡한 쿼리가 많이 없는 비지니스 로직에 사용하면 좋을 것 같다.
'Language & Framework > WebFlux' 카테고리의 다른 글
| [WebFlux] WebFlux AOP 사용하기 (1) | 2023.08.21 |
|---|---|
| [WebFlux] WebClient Connection Reset By Peer 오류 발생 (0) | 2023.08.18 |
| [WebFlux] Spring WebFlux 프로젝트 생성 하기 (0) | 2023.08.14 |
| [WebFlux] Spring WebFlux 원리 (2) | 2023.08.13 |
| [WebFlux] Spring WebFlux란? #1 (0) | 2023.08.13 |