본문 바로가기

Reactive stack

[Reactive stack] R2DBC with mysql 삽질기

재 가공한 글을 medium에서도 읽을 수 있습니다.

 

최근 진행하고있는 프로젝트를 MVC에서 Web flux로 변경함에 따라 DB 통신기술또한 비동기로 변경해야 했습니다.

때문에, 더이상 Spring data jpa 를 사용할 수 없었고 Database와 비동기로 통신할 수 있는 기술이 무엇이 있는지 찾아보게 되었습니다.

 

제가 찾은 기술은 크게 2가지 였는데요,

1. Hibernate Reactive

2. R2DBC

 

Hibernate Reactive는 기존의 ORM기능을 전부 활용할 수 있다는 장점이 있지만 다음과 같은 이유때문에 저는 R2DBC를 선택했습니다.

1. Spring에서 지원하지않음.

2. 트랜잭션 범위가 하나의 스레드로 국한되며 선언적 트랜잭션을 사용할 수 없음.

 

Hibernate Reactive transaction

 

3. 기존 도메인이 굉장히 간단해서 별도의 매핑과정이 필요가 없음.

4. 미래에 MongoDB로 변경을 고려중이며, MongoDB는 R2DBC에서 완전히 지원하므로 Hibernate를 고려할 필요가없음.

 

이러한 차이에도 ORM을 사용하는게 더 이득이 되는 상황에서는 Hibernate Reactive의 대용으로 line에서 개발한 Kotlin JDSL 를 추천드립니다.

 

R2DBC 사용하기

R2DBC는 spring data 프로젝트에서 지원하는 기술 중 하나로 다음 의존성을 추가하는것으로 사용할 수 있습니다.

implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'

또한, r2dbc는 인터페이스에 불과하기 때문에, 구현체의 의존성도 같이 추가해줘야 하는데요, 현재 spring r2dbc 공식문서에 적혀있는 대표적 구현체는 아래와 같으며, 시간이 지나서 다른 구현체를 찾아보고 싶으시다면, 다음 링크를 참조하시는것을 추천드립니다.

spring r2dbc docs

https://spring.io/projects/spring-data-r2dbc

저는 MySQL과 함께 사용할것이기 때문에, 다음 의존성을 추가해줬습니다. h2같은경우는 테스트를 위해 추가해주었습니다.

(이 의존성이 database driver를 다 설정해주기 때문에, 후술할 flyway와 같은 상황이 아니면.. 별도의 Connector를 잡아줄 필요는 없습니다.)

implementation 'io.asyncer:r2dbc-mysql:1.0.4'

testRuntimeOnly 'com.h2database:h2'
testImplementation 'io.r2dbc:r2dbc-h2'

R2DBC설정은 properties로 하는방법과 코드로 하는방법 두가지가 있는데요, 저는 간단하게 properties로 했습니다.

spring-boot-starter-data-r2dbc를 받으면, r2dbc-pool 의존성도 딸려서 오기때문에, 다음과 같이 pool size를 설정해줄수 있습니다. 제가 한 설정은 모두 기본값 입니다.

더 많은 설정을 하고 싶으신분은 다음 링크를 확인해주세요. https://github.com/r2dbc/r2dbc-pool

 

여기까지 설정해주시면 R2DBC를 사용할 준비가 끝납니다.

 

R2DBC에서 지원하는 매핑들

R2DBC는 ORM이 아닙니다. 그렇기 때문에, jakarata.persistence에서 제공하는 어노테이션들을 사용할 수 없으며, org.springframework.data 하위의 어노테이션들을 이용해서 매핑해줘야합니다. 다음은 R2DBC에서 사용할 수 있는 매핑목록 입니다.

 

위 사진에는 나와있지 않지만, @EnableR2dbcAuditing 설정을 켜주실경우, 아래 어노테이션도 사용할 수 있습니다.

@CreatedDate
@LastModifiedDate
@CreatedBy
@LastModifiedBy

 

실제로 위 Auditing을 적용하면 아래와 같이 됩니다.

(Persistable에 대해서는 후술하겠습니다.)

매핑을 하시면서 주의할점은, jakarta.persistence 나 java.persistence쪽 어노테이션을 매핑하지 않아야합니다. 애가 인식하지 못해요 ㅠㅠ 

또한, R2DBC에서는 객체를 자동으로 만들어주지 않기 때문에, 직접 테이블을 설계하셔야 합니다.

저같은 경우는 테스트에서는 test/resource 하위에 schema.sql을 만들어서 사용하고 프로덕션에서는 flyway를 사용하고 있습니다.

 

R2DBC Persistable

Spring data 에서 제공하는 Repository 방식(ex. R2dbcRepository)으로 R2DBC를 사용할때, 새로운 객체를 저장하거나 업데이트 (JPA 스펙이 아니기 때문에, EntityManager가 없고 따라서, 변경감지도 없습니다.) 하기위해서, save메소드를 사용하게됩니다.

 

이때, R2DBC는 @Id와 @Version에 따라서 새로운 값인지 판별하는데요, Id와 Version을 함께 사용할때 Version이 0 이면 새로운 값으로 인식하며, id가 null일때도 새로운 값으로 인식합니다.

 

새로운 값으로 판별될때만 insert 쿼리를 수행하고, 새로운 값이 아니라 기존에 저장된 값이라고 판별되면, R2DBC는 update 쿼리를 수행합니다.

 

PersistentEntityIsNewStrategy

 

따라서, Id를 애플리케이션에서 별도로 생성하고 있으시다면, Persistable을 구현해주셔야 합니다. Persistable을 구현하면, isNew() 메소드의 결과로 새로운값 유무를 확인합니다.

 

R2DBC @Transactional

R2DBC는 트랜잭션을 지원합니다.

Spring data jpa 에서 선언적 트랜잭션은 얻어온 커넥션을 ThreadLocal에 저장하기 때문에 같은 스레드에서만 트랜잭션을 유지할 수 있습니다.

하지만, R2DBC는 로직들이 같은 트랜잭션에서 진행되는것이 보장되지 않는데요, 스프링팀은 Spring Security의 context에서 이미 비슷한 문제를 겪었었고 이를 비슷한 방식으로 해결했다고 합니다. https://spring.io/blog/2019/05/16/reactive-transactions-with-spring

spring에서 선언적 트랜잭션을 사용하고 싶다면 직접 R2dbcTransactionManager를 등록해주면 되지만, SpringBoot에서는 아래 클래스가 자동으로 등록해줍니다 👍

 

R2dbcTransactionManagerAutoConfiguration

 

따라서, 별다른 설정을 할 필요없이 spring data jpa 를 사용하듯이 이용하시면 됩니다.

선언적 트랜잭션이 아닌 직접 트랜잭션을 열고 사용하고 싶으신분은 다음 글을 참고해주세요

 

Flyway와 함께 사용하기

baeldung에 따르면, flyway는 JDBC 드라이버가 필요하기 때문에, r2dbc만으로는 사용할 수없으며, jdbc driver를 추가해주어야 사용할 수 있다고 합니다.

설정 방법도 매우 간단한데, 다음 의존성을 추가로 설정해주고,

implementation 'mysql:mysql-connector-java:8.0.33'

기존에 flyway를 사용하듯이 사용해주시면 됩니다. 단, 이때, spring.flyway.url에 r2dbc: 로 시작하는 url이 아니라, jdbc:로 시작하는 url을 넣어주셔야합니다.

url에 기존의 jdbc:를 넣으면 됩니다.

 

공식문서를 제대로 읽지않고 개발했다가 너무 많은 삽질을 했습니다. 혹시나, R2DBC를 고려하고 있으신분이 있다면, 이 글이 도움이 되면 좋겠습니다.

 

다루지 않았던 테스트나 실제 코드는 모두 다음 레포지토리에서 확인하실 수 있습니다.

https://github.com/rooftop-MSA/identity-server

 

GitHub - rooftop-MSA/identity-server: Accounts and Authentication server

Accounts and Authentication server. Contribute to rooftop-MSA/identity-server development by creating an account on GitHub.

github.com