Skip to content

feat: Exposed R2DBC DAO#2831

Open
obabichevjb wants to merge 7 commits into
mainfrom
obabichev/r2dbc-dao-5
Open

feat: Exposed R2DBC DAO#2831
obabichevjb wants to merge 7 commits into
mainfrom
obabichev/r2dbc-dao-5

Conversation

@obabichevjb

Copy link
Copy Markdown
Collaborator

No description provided.

@obabichevjb

Copy link
Copy Markdown
Collaborator Author

R2DBC DAO API — Key Differences from JDBC DAO

This is a work-in-progress. The API described below is not final and may change based on feedback.

This document highlights behavioral and structural differences between the JDBC DAO (exposed-dao) and the new R2DBC DAO (exposed-dao-r2dbc). Straightforward renames (e.g. EntityR2dbcEntity, referencedOnreferencedOnSuspend) are omitted — the focus is on things that change how you write code.


1. Relationship properties: val + accessor instead of var + assignment

JDBC relationships are mutable properties — you read and write them directly:

// JDBC
var broker by Broker referencedOn Clients.broker

client.broker                  // read — returns entity
client.broker = newBroker      // write — assignment

R2DBC relationships are val properties that return an accessor object with two operations:

// R2DBC
val broker by Broker referencedOnSuspend Clients.broker

client.broker()                // read — suspend invoke()
client.broker set newBroker    // write — infix set

Why: Kotlin's delegation protocol requires getValue to return the declared property type. Since reading a relationship in R2DBC requires a suspend call, the property can't directly return the entity. Instead it returns an accessor that provides suspend operator fun invoke() for reads and infix fun set(value) for writes.

One-to-many and back-references are read-only — they only have invoke():

// R2DBC
val clients by Client referrersOnSuspend Brokers.broker

broker.clients()               // suspend invoke() → SizedIterable<Client>

Exception — many-to-many: Inner table links use list assignment instead of set:

// R2DBC
val tags by Tag viaSuspend InstrumentTags

instrument.tags()              // read — suspend invoke()
instrument.tags set listOf(tag1, tag2)  // write — infix set

2. Explicit flushCache() required after entity creation

JDBC auto-flushes pending INSERTs when you access entity.id.value:

// JDBC
val broker = Broker.new { name = "Alice" }
broker.id.value  // triggers INSERT, returns generated ID

R2DBC cannot auto-flush because the flush operation is suspend, and EntityID.value is a synchronous property accessor:

// R2DBC
val broker = Broker.new { name = "Alice" }
flushCache()     // explicit — executes the INSERT
broker.id.value  // now safe to read

Forgetting flushCache() before reading the ID will throw or return an uninitialized value.


3. suspend on all I/O methods

Every method that touches the database is suspend in R2DBC. Key examples:

Method JDBC R2DBC
EntityClass.findById(id) fun findById(id): T? suspend fun findById(id): T?
EntityClass.count(op) fun count(op): Long suspend fun count(op): Long
Entity.delete() fun delete() suspend fun delete()
Entity.flush(batch) fun flush(batch): Boolean suspend fun flush(batch): Boolean
Entity.refresh(flush) fun refresh(flush) suspend fun refresh(flush)
EntityClass[id] operator fun get(id): T suspend operator fun get(id): T
EntityClass.reload(entity) open fun reload(entity): T? suspend fun reload(entity): T?
EntityCache.flush() fun flush() suspend fun flush()
EntityCache.clear(flush) fun clear(flush) suspend fun clear(flush)
Transaction.flushCache() fun Transaction.flushCache() suspend fun R2dbcTransaction.flushCache()

Entity hook subscriptions also accept suspend lambdas:

// JDBC
EntityHook.subscribe { change -> /* non-suspend */ }

// R2DBC
EntityHook.subscribe { change -> /* suspend */ }

4. Collections return SizedIterable backed by Flow

R2DBC's SizedIterable is Flow-based rather than Iterable-based. This means collecting results requires an explicit toList() call:

// JDBC
broker.clients.map { it.name }            // direct iteration

// R2DBC
broker.clients().toList().map { it.name }  // collect Flow, then map

This also affects eager loading — R2DBC requires two with() overloads (one for SizedIterable, one for Iterable) where JDBC has one.


5. Explicit attach() for cross-transaction entity reuse

JDBC auto-attaches entities to the current transaction when you set a column value (inside Column.setValue). R2DBC can't do this because the auto-attach would require a suspend call inside a non-suspend operator.

R2DBC provides an explicit attach() method instead:

// R2DBC
suspendTransaction {
    val broker = Broker.findById(id)!!
    // ... later, in a different transaction context:
    Broker.attach(broker)  // explicitly re-attach
}

6. Missing JDBC features (not yet ported)

The following JDBC DAO features have no R2DBC equivalent yet:

  • ImmutableEntityClass / ImmutableCachedEntityClass — immutable entities with cross-transaction caching
  • findWithCacheCondition — cache-first lookup with fallback to DB query
  • warmUpReferences / warmUpOptReferences — bulk eager-loading helpers (R2DBC has equivalent private helpers but issues per-parent queries instead of bulk compoundOr queries for composite FKs)

@HacktheTime

Copy link
Copy Markdown

Last time I asked you said its not usable yet or sth. How would you describe this 1? What stuff should I be aware of?

Also the attachment stuff isn't clear to me yet. I didnt find a good explanation in the doc either in a quick scan.

from the tutorial rn:
can i update them later like this?
val jamesList = suspendTransaction {
UsersTable.selectAll().where { UsersTable.firstName eq "James" }.toList()
}
//some other code
suspendTransaction{
jameslist.first().adress set "examplestreet"
}

What do I need to stay aware of if some fields of a entity could be changed while sth else still has it "cached"?

@HacktheTime

Copy link
Copy Markdown

Also build exposed with team city should skip detect. right now it fails with detekt weighted issues error.

A Seperate detekt pipeline is good though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants