javaspring-bootspring-webfluxspring-data-r2dbcr2dbc-postgresql

Spring Boot 2.4 mixed project with JPA and R2DBC doesn't start


I'm working on an existing Spring Boot 2.4 project and trying to bring in Webflux and R2DBC into an existing JPA based app.

I'm able to bring in the Webflux dependency:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

without any issues, my app starts and behaves as it should, was able to perform some tests using Flux/Mono and all is good.

To get full reactivity however, I need to go all the way down to the persistence layer, i.e. also bringing in R2DBC dependency (for postgres)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-r2dbc</artifactId>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-postgresql</artifactId>
    <version>0.8.6.RELEASE</version>
</dependency>

I have the following as my configuration class:

@Configuration
@EnableR2dbcRepositories(
        repositoryBaseClass = ReactiveCrudRepository.class
)
public class R2DBCConfiguration extends AbstractR2dbcConfiguration {

    @Bean
    @Qualifier("rxConnectionFactory")
    @Override
    public ConnectionFactory connectionFactory() {
        return new PostgresqlConnectionFactory(
                PostgresqlConnectionConfiguration
                .builder()
                .username("user")
                .password("password")
                .host("localhost")
                .port(5432)
                .database("my_db")
                .build()
        );
    }
}

With this dependency, my project fails to start, consistently giving the same exception:

Caused by: java.lang.IllegalStateException: Cannot apply reactive transaction to non-reactive return type: interface java.util.List
    at org.springframework.transaction.interceptor.TransactionAspectSupport.lambda$invokeWithinTransaction$0(TransactionAspectSupport.java:351)
    at java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:324)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:346)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:134)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
    at com.mycompany.myapplication.service.SettingsServiceImpl$$EnhancerBySpringCGLIB$$5198d258.findAll(<generated>)
    at com.mycompany.myapplication.service.RuntimeSettingsServiceImpl.populateMap(RuntimeSettingsServiceImpl.java:96)
    at com.mycompany.myapplication.service.RuntimeSettingsServiceImpl.init(RuntimeSettingsServiceImpl.java:65)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333)
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157)
    ... 109 common frames omitted

I'm failing to understand why this is happening - we do use the traditional SimpleJpaRepository implementation for our DAOs / none of which extend ReactiveCrudRepository and thus should not be "seen" as reactive stores from the app point of view.

In any case I was wondering if others have faced a similar scenario and a possible workaround/fix.


Solution

  • Check my answer to this question, and I also provided a working example.

    BTW, I do not think it is a good option mixed blocking JPA APIs and none-blocking R2dbc APIs in the sample application.