Search code examples
javaspring-booth2spring-webfluxr2dbc

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


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?


Solution

  • 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.