Unable to create a ConnectionFactory Error with H2 and R2DBC in Spring Boot with WebFlux
Asked Answered
C

5

6

I've created a Java Spring Boot service using the WebFlux reactive module, H2 in-memory database, and R2DBC reactive driver.

When I run the service, it fails with an "Unable to create a ConnectionFactory" error:

Unable to create a ConnectionFactory for 'ConnectionFactoryOptions{options={driver=h2, protocol=mem, database=contentitem, options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE}}'. Available drivers: [ pool ]

This is a nested exception that seems to start in the repository and then propagate back through the service to the controller.

Having read through the resulting stack trace carefully and finding no indication about what the problem might be, the only clue I can find is that the 'connectionFactory' input parameter in my CustomConnectionFactoryInitializer class (please see below) is being highlighted with a red squiggle ("Could not autowire. There is more than one bean of 'ConnectionFactory' type") ... except that there isn't. At least there's only one explicitly defined bean -- the one in my CustomConnectionFactoryInitializer class.

Any idea what's going on here?

Details

It's a Gradle project, and my build.gradle file is:

plugins {
    id 'org.springframework.boot' version '2.3.9.RELEASE'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.mycorp.service'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'io.r2dbc:r2dbc-h2'
    runtimeOnly 'io.r2dbc:r2dbc-postgresql'
    runtimeOnly 'org.postgresql:postgresql'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'io.projectreactor:reactor-test'
    compile 'io.springfox:springfox-swagger2:3.0.0'
    compile 'io.springfox:springfox-swagger-ui:3.0.0'
    compile 'io.springfox:springfox-spring-webflux:3.0.0'
}

test {
    useJUnitPlatform()
}

I've added a schema.sql file under main/resources, which contains the following:

CREATE TABLE contentitem ( contentItemId INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, localizedName VARCHAR(100) NOT NULL);

I populate the table with a data.sql file in the same directory:

INSERT INTO contentitem (contentItemId, localizedName) VALUES (0, 'Zero');
INSERT INTO contentitem (contentItemId, localizedName) VALUES (1, 'One');
INSERT INTO contentitem (contentItemId, localizedName) VALUES (2, 'Two');

I've created a CustomConnectionFactoryInitializer to create and populate the database:

@Configuration
public class CustomConnectionFactoryInitializer {
    @Bean
    public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
        ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
        initializer.setConnectionFactory(connectionFactory);
        CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
        populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
        initializer.setDatabasePopulator(populator);
        return initializer;
    }
}

I've defined a 'test' profile using in-memory H2 and made it active in my application.yml file:

spring:
  profiles:
    active: test
---
spring:
  profiles: dev
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/test
    username: postgres
    password: postgres
logging:
  level:
    org.springframework.data.r2dbc: Debug
---
spring:
  profiles: test
  r2dbc:
    url: r2dbc:h2:mem:///contentitem?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    name: sa
    password:
---
spring:
  profiles: prod
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/test
    username: postgres
    password: postgres
  logging:
    level:
      org.springframework.data.r2dbc: Debug

My ContentItem model is:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("contentitem")
public class ContentItem {
    @Id
    @Column("contentItemId")
    private Integer contentItemId;
    @Column("localizedName")
    private String localizedName;
}

My ContentItemController is:

@RestController
@RequestMapping("/contentItems")
public class ContentItemController {
    @Autowired
    private ContentItemService contentItemService;

    @GetMapping("/{contentItemId}")
    public Mono<ResponseEntity<ContentItem>> getContentItemByUserId(@PathVariable Integer contentItemId){
        Mono<ContentItem> contentItem = contentItemService.getContentItemById(contentItemId);
        return contentItem.map( u -> ResponseEntity.ok(u))
                .defaultIfEmpty(ResponseEntity.notFound().build());
    }

My ContentItemService is:

@Service
@Slf4j
@Transactional
public class ContentItemService {

    @Autowired
    private ContentItemRepository contentItemRepository;

    public Mono<ContentItem> getContentItemById(Integer contentItemId){
        return contentItemRepository.findByContentItemId(contentItemId);
    }

}

And my ContentItemRepository is:

public interface ContentItemRepository extends ReactiveCrudRepository<ContentItem,Integer> {
    Mono<ContentItem> findByContentItemId(Integer contentItemId);
}

Complicating all this is that the H2 console, which I've enabled in the application.properties file with spring.h2.console.enabled=true is failing with a 404 Not Found error when I call it with http://localhost:8081/h2-console. Any ideas what could be going on here?

Civic answered 13/5, 2021 at 23:29 Comment(2)
H2-console does not work with reactive application since it uses servlet technology. could you please post your pom.xmlVulcanism
This is one of the most helpful answers I've ever seen because it includes the "why". Thank you, @Toerktumlare! I'm using Gradle, so I'll add my build.gradle file to the original question.Civic
C
0

OK, so I went back through my project file by file, diffing each file with a copy of the repo I was using as a guide. I found some extra database connection configuration code I thought I'd gotten rid of. As soon as I removed it, problem solved. Thanks to everyone who took a look and offered suggestions.

Civic answered 25/5, 2021 at 23:41 Comment(2)
This doesn't say what the solution was.Arita
"I found some extra database connection configuration code I thought I'd gotten rid of. As soon as I removed it, problem solved."Civic
P
4

For me r2dbc-postgresql was missing in the pom.xml

<dependency>
    <groupId>io.r2dbc</groupId>
    <artifactId>r2dbc-postgresql</artifactId>
</dependency>
Pronunciation answered 13/9, 2021 at 16:54 Comment(1)
Question is related to H2.Expose
T
1

If you are using an Oracle database, you can add oracle r2dbs dependency to solve the issue.

<!-- https://mvnrepository.com/artifact/com.oracle.database.r2dbc/oracle-r2dbc -->
<dependency>
    <groupId>com.oracle.database.r2dbc</groupId>
    <artifactId>oracle-r2dbc</artifactId>
    <version>1.0.0</version>
</dependency>
Triennium answered 7/3, 2023 at 11:32 Comment(1)
Question is related to H2Civic
C
0

OK, so I went back through my project file by file, diffing each file with a copy of the repo I was using as a guide. I found some extra database connection configuration code I thought I'd gotten rid of. As soon as I removed it, problem solved. Thanks to everyone who took a look and offered suggestions.

Civic answered 25/5, 2021 at 23:41 Comment(2)
This doesn't say what the solution was.Arita
"I found some extra database connection configuration code I thought I'd gotten rid of. As soon as I removed it, problem solved."Civic
A
0

I had a similar error, but for me the root cause is the scope of dependencies, from:

testRuntimeOnly("com.h2database:h2")
testRuntimeOnly("io.r2dbc:r2dbc-h2")

to:

runtimeOnly("com.h2database:h2")
runtimeOnly("io.r2dbc:r2dbc-h2")
Abjure answered 26/5, 2023 at 22:53 Comment(0)
M
0

for spring-boot 3.2 , i added io.r2dbc:r2dbc-h2 and removed com.h2database:h2

runtimeOnly 'io.r2dbc:r2dbc-h2'
//runtimeOnly 'com.h2database:h2'
Mola answered 18/3 at 19:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.