Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
dokka(projects.exposed.exposedSpringBoot4Starter)
dokka(projects.exposed.springTransaction)
dokka(projects.exposed.spring7Transaction)
dokka(projects.exposed.exposedDaoR2dbc)

// Kover aggregated coverage dependencies
// Include all source modules for coverage aggregation
Expand All @@ -65,10 +66,12 @@ dependencies {
kover(project(":exposed-migration-jdbc"))
kover(project(":exposed-migration-r2dbc"))
kover(project(":exposed-r2dbc"))
kover(project(":exposed-dao-r2dbc"))

// Include test modules to ensure their tests are executed and coverage is collected
kover(project(":exposed-tests"))
kover(project(":exposed-r2dbc-tests"))
kover(project(":exposed-dao-r2dbc-tests"))
}

repositories {
Expand All @@ -80,6 +83,7 @@ allprojects {
if (this.name != "exposed-tests" &&
this.name != "exposed-r2dbc-tests" &&
this.name != "exposed-jdbc-r2dbc-tests" &&
this.name != "exposed-dao-r2dbc-tests" &&
this != rootProject
) {
apply(plugin = "com.vanniktech.maven.publish")
Expand All @@ -97,7 +101,7 @@ allprojects {

apiValidation {
ignoredProjects.addAll(
listOf("exposed-tests", "exposed-bom", "exposed-r2dbc-tests", "exposed-jdbc-r2dbc-tests", "exposed-version-catalog")
listOf("exposed-tests", "exposed-bom", "exposed-r2dbc-tests", "exposed-jdbc-r2dbc-tests", "exposed-version-catalog", "exposed-dao-r2dbc-tests")
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ infix fun <T> Property<T>.by(value: T) {
set(value)
}

/**
* Whether this project publishes a Maven artifact.
*
* The root build applies the Maven Publish plugin to exactly the modules that are released, so its
* presence is the source of truth for publish state. Prefer this over a hand-maintained list of module
* names, which silently goes stale whenever a module is added.
*/
fun Project.publishesMavenArtifact(): Boolean = plugins.hasPlugin("maven-publish")

fun MavenPom.configureMavenCentralMetadata(project: Project) {
name by project.name
description by "Exposed, an ORM framework for Kotlin"
Expand Down
1 change: 1 addition & 0 deletions documentation-website/Writerside/hi.tree
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
<toc-element topic="Breaking-Changes.md"/>
<toc-element topic="Migration-Guide-0-46-0.md"/>
<toc-element topic="Migration-Guide-1-0-0.md"/>
<toc-element topic="Migration-Guide-DAO-JDBC-to-R2DBC.md"/>
</toc-element>
<toc-element topic="Frequently-Asked-Questions.md"/>
<toc-element toc-title="Samples" href="https://github.com/JetBrains/Exposed/tree/main/samples"/>
Expand Down
162 changes: 160 additions & 2 deletions documentation-website/Writerside/topics/DAO-CRUD-Operations.topic
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
<show-structure for="chapter,procedure" depth="2"/>
<tldr>
<p>
<b>Required dependencies</b>: <code>org.jetbrains.exposed:exposed-dao</code>
<b>Required dependencies</b>: <code>org.jetbrains.exposed:exposed-dao</code> (JDBC),
<code>org.jetbrains.exposed:exposed-dao-r2dbc</code> (R2DBC)
</p>
<include from="lib.topic" element-id="jdbc-supported"/>
<include from="lib.topic" element-id="r2dbc-not-supported"/>
<include from="lib.topic" element-id="r2dbc-limited-support"/>
</tldr>
<p>
CRUD (Create, Read, Update, Delete) are the four basic operations supported by any database. This section
Expand Down Expand Up @@ -342,5 +343,162 @@
<a href="Statement-Interceptors.md">DSL Statement Interceptors</a>.
</tip>
</chapter>
<chapter title="R2DBC differences" id="r2dbc-differences">
<include from="lib.topic" element-id="r2dbc-dao-experimental"/>
<chapter title="Every operation suspends" id="r2dbc-suspending">
<p>
Run all DAO calls inside <code>suspendTransaction</code>, and make the enclosing functions
<code>suspend</code>:
</p>
<tabs group="connectivity">
<tab id="jdbc-crud-tx" title="JDBC" group-key="jdbc">
<code-block lang="kotlin"><![CDATA[
import org.jetbrains.exposed.v1.jdbc.transactions.transaction

transaction {
val movie = StarWarsFilmEntity.new {
name = "The Last Jedi"
director = "Rian Johnson"
}
val found = StarWarsFilmEntity.findById(movie.id)
found?.director = "R. Johnson"
found?.delete()
}
]]>
</code-block>
</tab>
<tab id="r2dbc-crud-tx" title="R2DBC" group-key="r2dbc">
<code-block lang="kotlin"><![CDATA[
import org.jetbrains.exposed.v1.r2dbc.transactions.suspendTransaction

suspendTransaction {
val movie = StarWarsFilmEntity.new {
name = "The Last Jedi"
director = "Rian Johnson"
}
val found = StarWarsFilmEntity.findById(movie.id)
found?.director = "R. Johnson"
found?.delete()
}
]]>
</code-block>
</tab>
</tabs>
<p>
<code>new()</code>, <code>findById()</code>, <code>get()</code>, <code>findByIdAndUpdate()</code>,
<code>findSingleByAndUpdate()</code>, <code>count()</code>, <code>reload()</code>,
<code>Entity.delete()</code>, <code>Entity.flush()</code>, and <code>Entity.refresh()</code> are all
suspending. <code>all()</code> and <code>find { }</code> are not, because they only build a query.
</p>
<p>
Because <code>new { }</code> suspends, its initializer block does too, so you can call other DAO
operations inside it:
</p>
<code-block lang="kotlin"><![CDATA[
val movie = StarWarsFilmEntity.new {
name = "The Last Jedi"
// findById() suspends, which the initializer block allows
sequelId = (StarWarsFilmEntity.findById(previousFilmId)?.sequelId ?: 0) + 1
}
]]>
</code-block>
</chapter>
<chapter title="Query results are flows" id="r2dbc-flows">
<p>
In <code>exposed-r2dbc</code>, <code>SizedIterable</code> extends
<code>kotlinx.coroutines.flow.Flow</code>. Collect the result before using operators that produce a
collection:
</p>
<tabs group="connectivity">
<tab id="jdbc-crud-read" title="JDBC" group-key="jdbc">
<code-block lang="kotlin"><![CDATA[
val all = StarWarsFilmEntity.all().toList()
val names = StarWarsFilmEntity.all().map { it.name }
val count = StarWarsFilmEntity.all().count()
]]>
</code-block>
</tab>
<tab id="r2dbc-crud-read" title="R2DBC" group-key="r2dbc">
<code-block lang="kotlin"><![CDATA[
val all = StarWarsFilmEntity.all().toList()
val names = StarWarsFilmEntity.all().toList().map { it.name }
val count = StarWarsFilmEntity.all().count()
]]>
</code-block>
</tab>
</tabs>
<warning>
If <code>kotlinx.coroutines.flow.map</code> is in scope, the <code>map</code> call still compiles but
returns a <code>Flow</code> instead of a <code>List</code>. Add <code>.toList()</code> before any
operator that should produce a collection.
</warning>
</chapter>
<chapter title="Updating an entity in a later transaction" id="r2dbc-attach">
<p>
The JDBC DAO silently re-registers an entity on its first write in a new transaction. The R2DBC DAO
cannot, because that check is a database round trip and a property setter cannot suspend. Call
<code>attach()</code> first, or the write throws:
</p>
<tabs group="connectivity">
<tab id="jdbc-crud-attach" title="JDBC" group-key="jdbc">
<code-block lang="kotlin"><![CDATA[
val movie = transaction { StarWarsFilmEntity.new { name = "The Last Jedi" } }

transaction {
movie.director = "Rian Johnson"
}
]]>
</code-block>
</tab>
<tab id="r2dbc-crud-attach" title="R2DBC" group-key="r2dbc">
<code-block lang="kotlin"><![CDATA[
val movie = suspendTransaction { StarWarsFilmEntity.new { name = "The Last Jedi" } }

suspendTransaction {
StarWarsFilmEntity.attach(movie)
movie.director = "Rian Johnson"
}
]]>
</code-block>
</tab>
</tabs>
<p>
<code>attach()</code> throws <code>EntityNotFoundException</code> if the row no longer exists.
</p>
</chapter>
<chapter title="Batching inserts" id="r2dbc-new-deferred">
<p>
<code>new { }</code> flushes immediately, costing one <code>INSERT</code> per entity.
<code>newDeferred { }</code> has no JDBC counterpart: it schedules the insert without flushing and
returns a cold <code>Flow</code>, so collecting several together produces a single batched
<code>INSERT</code>:
</p>
<code-block lang="kotlin"><![CDATA[
val films: List<StarWarsFilmEntity> = listOf("A New Hope", "The Empire Strikes Back")
.map { title -> StarWarsFilmEntity.newDeferred { name = title } }
.asFlow()
.flattenConcat()
.toList()
]]>
</code-block>
<p>
Use <code>flattenConcat</code> rather than <code>merge</code>: <code>merge</code> does not preserve
the order in which the entities were scheduled.
</p>
<warning>
Discarding the flow does not cancel the insert. It is flushed at the first of collection, any other
statement in the same transaction, or commit. Batching therefore only holds if nothing else touches
the database in between — an intervening <code>new { }</code> splits the batch. Collecting the flow
outside the transaction that created it throws.
</warning>
</chapter>
<chapter title="Not available" id="r2dbc-crud-unavailable">
<p>
<code>EntityClass.view { }</code>, <code>findWithCacheCondition()</code>,
<code>testCache(predicate)</code>, and the <code>Alias</code> overloads of <code>wrapRows()</code>
have no R2DBC equivalent yet.
</p>
</chapter>
</chapter>

</topic>
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
<show-structure for="chapter,procedure" depth="2"/>
<tldr>
<p>
<b>Required dependencies</b>: <code>org.jetbrains.exposed:exposed-dao</code>
<b>Required dependencies</b>: <code>org.jetbrains.exposed:exposed-dao</code> (JDBC),
<code>org.jetbrains.exposed:exposed-dao-r2dbc</code> (R2DBC)
</p>
<include from="lib.topic" element-id="jdbc-supported"/>
<include from="lib.topic" element-id="r2dbc-not-supported"/>
<include from="lib.topic" element-id="r2dbc-limited-support"/>
</tldr>
<p>
An <a href="https://jetbrains.github.io/Exposed/api/exposed-dao/org.jetbrains.exposed.v1.dao/-entity/index.html"><code>Entity</code></a>
Expand Down Expand Up @@ -183,7 +184,62 @@
<a href="DAO-CRUD-Operations.topic" anchor="delete">deleting records</a>.
</p>
</chapter>
<chapter title="R2DBC differences" id="r2dbc-differences">
<include from="lib.topic" element-id="r2dbc-dao-experimental"/>
<p>
Entity definitions are almost identical for both drivers. Table definitions come from
<code>exposed-core</code> and need no changes at all.
</p>
<p>
Change the imports to the <code>.r2dbc</code> package:
</p>
<tabs group="connectivity">
<tab id="jdbc-entity-def" title="JDBC" group-key="jdbc">
<code-block lang="kotlin"><![CDATA[
import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass

class StarWarsFilmEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<StarWarsFilmEntity>(StarWarsFilmsTable)

var sequelId by StarWarsFilmsTable.sequelId
var name by StarWarsFilmsTable.name
var director by StarWarsFilmsTable.director
}
]]>
</code-block>
</tab>
<tab id="r2dbc-entity-def" title="R2DBC" group-key="r2dbc">
<code-block lang="kotlin"><![CDATA[
import org.jetbrains.exposed.v1.dao.r2dbc.IntEntity
import org.jetbrains.exposed.v1.dao.r2dbc.IntEntityClass

class StarWarsFilmEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<StarWarsFilmEntity>(StarWarsFilmsTable)

var sequelId by StarWarsFilmsTable.sequelId
var name by StarWarsFilmsTable.name
var director by StarWarsFilmsTable.director
}
]]>
</code-block>
</tab>
</tabs>
<p>
Column properties are unchanged. Reference properties are not: they must become <code>val</code>. See
<a href="DAO-Relationships.topic" anchor="r2dbc-differences"/>.
</p>
<p>
<a href="#field-transformations">Field transformations</a>, including memoized ones, work the same way.
<a href="#immutable-entities">Immutable entities</a> are the one feature on this page with no R2DBC
equivalent yet.
</p>
</chapter>
<chapter title="Immutable entities" id="immutable-entities">
<note>
Available for JDBC only. Neither <code>ImmutableEntityClass</code> nor
<code>ImmutableCachedEntityClass</code> exists in <code>exposed-dao-r2dbc</code>.
</note>
<p>
For defining entities that are immutable, Exposed provides the additional
<a href="https://jetbrains.github.io/Exposed/api/exposed-dao/org.jetbrains.exposed.v1.dao/-immutable-entity-class/index.html"><code>ImmutableEntityClass</code></a>
Expand Down
Loading
Loading