В этой заметке разберу 2 момента, которые мне не нравятся в jOOQ.
jOOQ, Quarkus и реактивность
- jOOQ поддерживает реактивность через R2DBC
- Quarkus поддерживает реактивность через Vert.x
Кто-то из них должен уступить. Есть специальный генератор jOOQ c Vert.x и пример – но он не обновлялся уже год (чуть-чуть устарел). Нужно попробовать, поразбираться как оно.
Хороший вопрос на эту тему: https://stackoverflow.com/questions/72669626/using-jooq-with-reactive-sql-client-in-quarkus
В принципе, многие считают, что нереактивные запросы к БД – это не проблема. Сам не тестировал, но многие годы и без этого работал. Понятно, что будет работать, но хотелось бы уж реактивности.
jOOQ, Spring и реактивность
Казалось бы: что может пойти не так и здесь?
Автоматически не заводится, ConnectionFactory должно быть задано вручную:
@Configuration
@EnableR2dbcRepositories
class R2dbcConfig(
@Value("\${spring.r2dbc.url}") private val dsUrl: String,
@Value("\${spring.r2dbc.username}") private val dsUsername: String,
@Value("\${spring.r2dbc.password}") private val dsPassword: String,
) : AbstractR2dbcConfiguration() {
override fun connectionFactory(): ConnectionFactory {
return ConnectionFactories.get(
ConnectionFactoryOptions
.parse(dsUrl)
.mutate()
.option(ConnectionFactoryOptions.USER, dsUsername)
.option(ConnectionFactoryOptions.PASSWORD, dsPassword)
.build(),
)
}
}
Плюс, реактивно работает так (через Flux.from
):
fun getAllEmails(): Flow<String?> {
return Flux.from(db.select(USERS.EMAIL).from(USERS))
.asFlow()
.map { it.value1() }
}
Но не так (через fetchAsync
):
suspend fun getAll(): List<UsersRecord> {
return jdbcDb.fetchAsync(USERS).await()
}
Из-за этого приходится делать 2 DSLContext:
@Primary
@Bean
fun dslContext(r2dbcConfig: R2dbcConfig): DSLContext {
return DSL.using(r2dbcConfig.connectionFactory())
}
@Bean("jdbcDb")
fun jdbcDslContext(dataSource: HikariDataSource): DSLContext {
return DSL.using(dataSource.connection)
}
Все еще непонятно почему fetchAsync
не работает через R2DBC, но можно записать в таком виде (кода немного больше, но работает):
suspend fun byIdNullable(id: Long): UsersRecord? {
return Mono.from(
db.select(USERS).from(USERS).where(USERS.ID.eq(id)),
).map { it.value1() }.awaitSingleOrNull()
}
Т.к. jOOQ для сложных случаев, то немного больше кода вполне приемлемо.
jOOQ, Kotlin и null-значения
- разработчики jOOQ утверждают, что несмотря на non-null поля в таблице вполне возможно написать запрос так, что вернется null (что правда).
- только из-за этого все поля всегда nullable.
- и во всех случаях вручную нужно, условно говоря, писать
!!
Как по мне, все-таки лучше делать с учетом нульности колонок, а специфичные случаи как раз можно отдельно рассматривать. Но такого сейчас пока что нет – неудобно.
Выводы
В связи с этим, на практике имеет смысл совмещать 2 движка доступа с Postgres: Spring Data Repositories (простые случаи) & jOOQ (сложные случаи).
Отдельно отмечу, что Flyway не работает с R2DBC и ему нужны JDBC-настройки (spring.datasource
).