Migrating from JDBC DAO to R2DBC DAO
Exposed 1.6.0 introduces experimental R2DBC support for the DAO API through the exposed-dao-r2dbc artifact. In this topic, you will learn how to migrate an existing JDBC DAO application to R2DBC DAO.
The migration changes the transport and DAO artifacts and requires source-code changes.
Prerequisites
Before you start, check whether your application uses JDBC DAO features that are not available in R2DBC DAO. If it does, you must change those parts of the application before completing the migration.
JDBC DAO | R2DBC DAO |
|---|---|
| Not available. |
| Not available. |
| Not available. Use |
| Not available. |
| Not available. Use |
| Not available. Pending values belong to the transaction's |
| Not available. The R2DBC entity cache does not evict entities. |
| Not available. This functionality is part of the |
| Available, but does not support the |
Update dependencies
Replace the JDBC transport and DAO artifacts with their R2DBC counterparts:
Use the R2DBC driver that corresponds to your database, for example:
Opt in to the experimental API
The R2DBC DAO API is experimental and requires an opt-in. To opt in for the entire module, add ExperimentalR2dbcDaoApi to the Kotlin compiler options:
To opt in at a narrower scope, use @OptIn(ExperimentalR2dbcDaoApi::class) instead.
Update imports
The R2DBC DAO API is located in the org.jetbrains.exposed.v1.dao.r2dbc package.
For DAO, replace the org.jetbrains.exposed.v1.dao. package with org.jetbrains.exposed.v1.dao.r2dbc. across your imports. For example:
JDBC DAO | R2DBC DAO |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Table definitions don't require changes. They are provided by the exposed-core module and are shared by both JDBC and R2DBC drivers.
Update transaction handling
R2DBC DAO operations are suspending, so the functions that contain them must be suspend when required. Replace the transaction() function with suspendTransaction():
Update entity creation
R2DBC DAO provides two entity creation functions:
Function | Suspends | When the | ID available after the call |
|---|---|---|---|
| No | At the next flush | Only if the ID is explicitly assigned or otherwise available without an insert |
| Yes | Before the call returns | Yes, including generated IDs |
Use newSuspend() for immediate inserts
The newSuspend() function suspends and issues the INSERT before returning, so a generated ID is available immediately.
Use newSuspend() when the entity must be inserted before the function returns:
Use new() for deferred and batch inserts
The new() function does not issue the INSERT immediately. Instead, it schedules the insert in the transaction's entity cache. This can be useful when you want to create multiple entities and flush them together:
The pending inserts are flushed when required by subsequent database operations or when the transaction commits. Call flushCache() when you need to explicitly control when the pending inserts are sent to the database.
Update reference properties
R2DBC reference properties use an accessor because reading a reference can require a suspending database operation.
Change the referencedOn and optionalReferencedOn properties from var to val. Read the reference by calling the accessor and update it with the .set() function:
The backReferencedOn and optionalBackReferencedOn properties remain unchanged. The via property remains a var and takes a SizedCollection.
Collect DAO flows
In R2DBC DAO, SizedIterable extends Flow. As a result, DAO collections are represented as flows rather than Kotlin collections.
This applies to DAO collections such as referrers, via relations, all(), and find().
Terminal operations such as count(), first(), firstOrNull(), and single() do not require any additional changes.
Attach entities across transactions
Unlike the JDBC DAO, in R2DBC DAO an entity loaded or created in one transaction is not automatically registered with another R2DBC transaction. This is because that requires a database check and a property setter cannot suspend.
To reuse entities across transactions, call the attach() function before modifying an entity from another transaction:
The attach() function throws EntityNotFoundException if the row no longer exists. If the current transaction already caches a different instance of the same row with unflushed changes, attach() throws IllegalStateException.
To replace the cached instance and discard its unflushed changes, use attach(item, force = true).