Spring-webflux에서 MongoDB, R2DBC(MySQL) 2가지 DB를 사용하고 있고 JPA처럼 @Transactional을 사용하여 트랜잭션을 처리하고 싶었다.
구글링을 통해 여러 자료를 참고하여 @Transactional을 MongoDB, R2DBC 각각 사용할 수 있도록 설정했다.
@Configuration
@EnableReactiveMongoRepositories(basePackages = "xxx.xxxxxxx.xxx.repository.mongo")
public class MongoConfig {
@Bean
public ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory) {
return new ReactiveMongoTransactionManager(reactiveMongoDatabaseFactory);
}
@Bean
public TransactionalOperator transactionalOperator(ReactiveTransactionManager transactionManager) {
return TransactionalOperator.create(transactionManager);
}
}
@Configuration
@EnableR2dbcRepositories(basePackages = "xxx.xxxxxxx.xxx.repository.r2dbc")
public class R2dbcConfig {
@Bean
public R2dbcTransactionManager r2dbcTransactionManager(ConnectionFactory connectionFactory) {
return new R2dbcTransactionManager(connectionFactory);
}
}
@Transactional
public Mono<HelloResDto> saveHelloEx(HelloReqDto reqDto){
Hello req = Hello.builder()
.message(reqDto.getMessage())
.build();
return helloRepository.insert(req)
.map(HelloResDto::from)
.doOnSuccess(helloResDto -> {throw new RuntimeException();})
;
}
@Test
void rollbackTest(){
HelloReqDto test = HelloReqDto
.builder()
.message("rollback Test242343242")
.build();
StepVerifier
.create(helloService.saveHelloEx(test))
.assertNext(helloResDto -> assertThat(helloResDto.getMessage()).isEqualTo(test.getMessage()))
.verifyComplete();
}
@Transactional
public Mono<UserResDto> saveUserEx(UserReqDto userReqDto){
User user = User.builder()
.username(userReqDto.getUsername())
.password(userReqDto.getPassword())
.name(userReqDto.getName())
.department(userReqDto.getDepartment())
.build();
return userRepository.save(user).map(UserResDto::from)
.doOnSuccess(userResDto -> {throw new RuntimeException();});
}
@Test
void rollbackTest(){
UserReqDto test = UserReqDto
.builder()
.username("test@test.com")
.password("test@test.com")
.department("test@test.com")
.name("test@test.com")
.build();
StepVerifier
.create(userService.saveUserEx(test))
.assertNext(userResDto -> assertThat(userResDto.getName()).isEqualTo(test.getName()))
.verifyComplete();
}
No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: transactionManager,r2dbcTransactionManager
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.TransactionManager' available: expected single matching bean but found 2: transactionManager,r2dbcTransactionManager
MongoDB, R2DBC에 각각 설정한 transaction bean type이 동일하여 @Trasactional에서 어떤 bean을 사용할지 몰라서 발생하는 오류였다.
그래서 bean type이 충돌나지 않도록 MongoDB transaction config에 @Primary 추가하여 우선권을 주었다.
@Bean
@Primary
public ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory) {
return new ReactiveMongoTransactionManager(reactiveMongoDatabaseFactory);
}
그러고 실행했더니 MongoDB는 정상적으로 transaction 처리가 되었는데 R2DBC는 transaction 처리가 안되는 것이였다.
둘 다 설정하는 건 안되는 것일까?
우리의 구글신을 영접하여 @Transactional별로 transactionManager bean을 선택할 수 있는 방법을 알아내었다.
value 옵션을 사용하여 transactionManager Bean을 선택할 수 있다.
@Transactional(value = "r2dbcTransactionManager")
public Mono<UserResDto> saveUserEx(UserReqDto userReqDto){
User user = User.builder()
.username(userReqDto.getUsername())
.password(userReqDto.getPassword())
.name(userReqDto.getName())
.department(userReqDto.getDepartment())
.build();
return userRepository.save(user).map(UserResDto::from)
.doOnSuccess(userResDto -> {throw new RuntimeException();});
}
그리고 다시 테스트하였더니 R2DBC도 transaction이 정상적으로 수행되는 것을 확인하였다.