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?