Back to Exposed

Working with ConnectionFactory

documentation-website/Writerside/topics/Working-with-ConnectionFactory.md

1.2.06.4 KB
Original Source

Working with ConnectionFactory

<primary-label ref="r2dbc"/>

In R2DBC, ConnectionFactory from the io.r2dbc.spi package is the reactive equivalent of a DataSource in JDBC. It is responsible for producing non-blocking Connection instances that support reactive and coroutine-based database access.

In Exposed, the exposed-r2dbc module integrates R2DBC support by allowing you to connect to a database using a ConnectionFactory. This is done implicitly through the R2dbcDatabase.connect() function whenever a URL is passed:

kotlin

{src="exposed-databases-r2dbc/src/main/kotlin/org/example/R2DBCDatabases.kt" include-symbol="database" }

This is equivalent to using the R2dbcDatabase.connect() overload that accepts a configuration block only:

kotlin
import io.r2dbc.spi.IsolationLevel
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase

val database = R2dbcDatabase.connect {
    defaultMaxAttempts = 1
    defaultR2dbcIsolationLevel = IsolationLevel.READ_COMMITTED

    setUrl("r2dbc:h2:mem:///test;DB_CLOSE_DELAY=-1;")
}

Using ConnectionFactoryOptions

When a URL is passed to R2dbcDatabase.connect(), the string is parsed to construct a new ConnectionFactoryOptions object, which holds details of the configuration state related to the ConnectionFactory.

This state can be configured manually, using the R2dbcDatabaseConfig.connectionFactoryOptions builder, either alongside a provided URL:

kotlin
import io.r2dbc.spi.ConnectionFactoryOptions
import io.r2dbc.spi.IsolationLevel
import io.r2dbc.spi.Option
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabaseConfig

val database = R2dbcDatabase.connect(
    url = "r2dbc:h2:mem:///test;",
    databaseConfig = R2dbcDatabaseConfig {
        defaultMaxAttempts = 1
        defaultR2dbcIsolationLevel = IsolationLevel.READ_COMMITTED

        connectionFactoryOptions {
            option(Option.valueOf("DB_CLOSE_DELAY"), "-1")
        }
    }
)

Or by constructing the state holder fully from scratch:

kotlin
import io.r2dbc.spi.ConnectionFactoryOptions
import io.r2dbc.spi.IsolationLevel
import io.r2dbc.spi.Option
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase

val database = R2dbcDatabase.connect {
    defaultMaxAttempts = 1
    defaultR2dbcIsolationLevel = IsolationLevel.READ_COMMITTED

    connectionFactoryOptions {
        option(ConnectionFactoryOptions.DRIVER, "h2")
        option(ConnectionFactoryOptions.PROTOCOL, "mem")
        option(ConnectionFactoryOptions.DATABASE, "test")
        option(Option.valueOf("DB_CLOSE_DELAY"), "-1")
    }
}

You can also pre-construct a ConnectionFactoryOptions object and use it to initialize a custom R2dbcDatabaseConfig instance. You can then pass both directly to R2dbcDatabase.connect() at a later point:

kotlin
import io.r2dbc.spi.ConnectionFactoryOptions
import io.r2dbc.spi.IsolationLevel
import io.r2dbc.spi.Option
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabaseConfig

val options = ConnectionFactoryOptions.builder()
    .option(ConnectionFactoryOptions.DRIVER, "h2")
    .option(ConnectionFactoryOptions.PROTOCOL, "mem")
    .option(ConnectionFactoryOptions.DATABASE, "test")
    .option(Option.valueOf("DB_CLOSE_DELAY"), "-1")
    .build()

val databaseConfig = R2dbcDatabaseConfig {
    defaultMaxAttempts = 1
    defaultR2dbcIsolationLevel = IsolationLevel.READ_COMMITTED
    connectionFactoryOptions = options
}

val database = R2dbcDatabase.connect(databaseConfig = databaseConfig)

Using a ConnectionFactory

To connect Exposed to a database using R2DBC, you can optionally rely on manual programmatic connection factory discovery by providing an explicit ConnectionFactory. This connection source can then be passed to the R2dbcDatabase.connect() function:

kotlin
import io.r2dbc.spi.ConnectionFactories
import io.r2dbc.spi.ConnectionFactoryOptions
import io.r2dbc.spi.IsolationLevel
import io.r2dbc.spi.Option
import org.jetbrains.exposed.v1.core.vendors.H2Dialect
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabaseConfig

val options = ConnectionFactoryOptions.builder()
    .option(ConnectionFactoryOptions.DRIVER, "h2")
    .option(ConnectionFactoryOptions.PROTOCOL, "mem")
    .option(ConnectionFactoryOptions.DATABASE, "test")
    .option(Option.valueOf("DB_CLOSE_DELAY"), "-1")
    .build()

val connectionFactory = ConnectionFactories.get(options)

val database = R2dbcDatabase.connect(
    connectionFactory = connectionFactory,
    databaseConfig = R2dbcDatabaseConfig {
        defaultMaxAttempts = 1
        defaultR2dbcIsolationLevel = IsolationLevel.READ_COMMITTED
        explicitDialect = H2Dialect()
    }
)
<note> If an explicit <code>ConnectionFactory</code> is being used, it is required in this case that a value is set to <code>R2dbcDatabaseConfig.explicitDialect</code>. This avoids any potential that the database dialect cannot be resolved from the <code>ConnectionFactory</code> or its configuration options. </note>

For simplicity or for more fine-tuned connection customization, you can use database-specific connection factories and configuration builders via programmatic configuration. These database-specific objects can be created and passed in the same way as the general R2DBC SPI objects:

kotlin
import io.r2dbc.h2.H2ConnectionConfiguration
import io.r2dbc.h2.H2ConnectionFactory
import io.r2dbc.h2.H2ConnectionOption
import io.r2dbc.spi.IsolationLevel
import org.jetbrains.exposed.v1.core.vendors.H2Dialect
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabaseConfig

val connectionFactory = H2ConnectionFactory(
    H2ConnectionConfiguration.builder()
        .inMemory("test")
        .property(H2ConnectionOption.DB_CLOSE_DELAY, "-1")
        .build()
)

val database = R2dbcDatabase.connect(
    connectionFactory = connectionFactory,
    databaseConfig = R2dbcDatabaseConfig {
        defaultMaxAttempts = 1
        defaultR2dbcIsolationLevel = IsolationLevel.READ_COMMITTED
        explicitDialect = H2Dialect()
    }
)

Both these ways register the connection source so that you can perform coroutine-based database operations using suspendTransaction.