Skip to content

feat: R2DBC DAO base structure#2751

Open
obabichevjb wants to merge 2 commits intomainfrom
obabichev/r2dbc-dao-2
Open

feat: R2DBC DAO base structure#2751
obabichevjb wants to merge 2 commits intomainfrom
obabichev/r2dbc-dao-2

Conversation

@obabichevjb
Copy link
Copy Markdown
Collaborator

Description

Summary of the change: Implements R2DBC DAO layer - a functional, suspending equivalent of Exposed's JDBC DAO API, enabling entity-based reactive database access with change tracking and relationships.

  • What:

    • Core Entity Classes: R2dbcEntity, R2dbcEntityClass
    • Automatic INSERT/UPDATE execution with dependency-aware flushing via R2dbcEntityCache and R2dbcEntityLifecycleInterceptor
    • Relationship Support: Relationship types implemented with suspending access but not completed and not well tested: referencedOnSuspend / optionalReferencedOnSuspend, referrersOnSuspend / optionalReferrersOnSuspend, backReferencedOnSuspend / optionalBackReferencedOnSuspend
    • Entities are automatically flushed before SELECT queries to ensure data consistency
  • How:

    • Architecture mirrors JDBC DAO but adapted for suspending operations:

      • JDBC: val city = person.city (blocking property access)
      • R2DBC: val city = person.city() (suspend function call via invoke operator)
      • Relationship setters use infix operators: person.city set newCity (non suspend)
    • Lifecycle interceptor (GlobalSuspendStatementInterceptor) automatically flushes pending entity changes before queries, sorted by foreign key dependencies using SchemaUtils.sortTablesByReferences()

Key Differences from JDBC DAO:

  • Many entity operations are suspend functions
  • Relationships return suspend lambdas accessed via invoke(): entity.relationship()
  • No batch update support yet (updates execute individually)
  • No subscription/entity change tracking yet
  • Uses R2DBC-specific transaction management

Similarities with JDBC DAO:

  • Similar entity definition syntax
  • Same relationship DSL (by EntityClass referencedOnSuspend Column)

Type of Change

  • New feature

Affected databases:

  • All

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within this PR the main approach was in taking tests from jdbc's EntityTests and adopting them to r2dbc with implementing minimal functionality to cover these tests. Not so many tests are moved, but they cover many key features like relationships, entity cache, lifecycle interceptor and so on

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the main difference in relationships comparing to jdbc. It allows to make non-suspend set(), and suspend get() (via invoke())

SuspendAccessor - is in the code, but actually not used, but OptionalSuspendAccessor is present in the tests

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was copied from jdbc dao for now

@obabichevjb obabichevjb force-pushed the obabichev/r2dbc-dao-2 branch from c6f3b4d to da5a2fe Compare March 4, 2026 20:07
@obabichevjb obabichevjb requested review from bog-walk and e5l March 6, 2026 10:02
Copy link
Copy Markdown
Member

@bog-walk bog-walk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great how much can be successfully accomplished & its a promising step towards providing DAO + R2DBC support.

My main thought overall is the huge amount of duplication that enters into the public API. With DSL there was of course a lot duplicated, but a huge amount was also kept in the core, so the end API for new R2DBC users remained almost the same unless you were customizing classes. For Entity versus R2dbcEntity for example, it seems to so far only be refresh(), flush(), and lookup() that differs.

I thought that 1 of the possible goals of R2DBC + DAO was to redesign the jdbc-dao so that it could be used common to both approaches. In the same way that a Table and most DSL non-executables can be shared by both, I hoped it might be possible to use the same Entity across both. With the executable methods (like save, refresh, update etc) extracted to interface implementations. So that a project could use both drivers together, even temporarily as they migrate or test r2dbc in parts of code.
With this approach, they'd instead have to either change their existing entities (just 1 change is nice though) or copy-paste new entities that implement R2dbcEntity. In the end, it's not really a problem, I'm just surprised that it's not possible to share code given how much similarity this semi-final product has.

Would it not at all be possible to have an exposed-dao-core module that is depended on by both? I suppose it would technically be a breaking change even if change wasn't felt in final API, but I don't think that should be a deterrent from trying a POC like that.

Comment thread build.gradle.kts Outdated
Comment thread exposed-dao-r2dbc/src/main/kotlin/org/jetbrains/exposed/r2dbc/dao/R2dbcEntity.kt Outdated
Comment thread exposed-dao-r2dbc/src/main/kotlin/org/jetbrains/exposed/r2dbc/dao/R2dbcEntity.kt Outdated
Comment on lines 132 to 133
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another question: What is wrong/incompatible with the existing EntityTypeChange implementation that jdbc-dao uses?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope to bring it in the next PR with subscribtions if it's ok, this PR was getting bigger and bigger already

Comment thread exposed-dao-r2dbc/src/main/kotlin/org/jetbrains/exposed/r2dbc/dao/LongEntity.kt Outdated
Copy link
Copy Markdown
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new api looks nice for the further implementation. Please check the package name

@obabichevjb obabichevjb force-pushed the obabichev/r2dbc-dao-2 branch 2 times, most recently from 1e4ab80 to 55e0a07 Compare April 13, 2026 12:16
@obabichevjb obabichevjb requested a review from bog-walk April 13, 2026 12:21
@obabichevjb obabichevjb force-pushed the obabichev/r2dbc-dao-2 branch from 55e0a07 to f0e3044 Compare April 17, 2026 06:16
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.

3 participants