Flash ORM - Kotlin Usage Guide
A comprehensive guide to using Flash ORM with Kotlin projects, featuring JDBC, Exposed, and R2DBC driver support.
Table of Contents
- Prerequisites
- Quick Start
- Configuration
- Package Auto-Detection
- Generated Code Overview
- Driver Options
- Working with JDBC
- Working with Exposed
- Working with R2DBC
- ScyllaDB / Cassandra Support
- Schema Definition
- Writing Queries
- Building and Running
- Best Practices
Prerequisites
- Kotlin 1.9+
- JVM 17+
- Gradle (Kotlin DSL or Groovy) or Maven build system
- Flash ORM CLI installed via npm, pip, or direct binary
Quick Start
1. Initialize a Kotlin Project
# Initialize with PostgreSQL
flash init --postgresql
# Flash ORM auto-detects Gradle/Maven Kotlin projects2. Generate Code
# After defining schema and queries:
flash generate3. Configure Your flash.toml
[gen.kotlin]
enabled = true
out = "src/main/kotlin/com/myapp/db"
package = "com.myapp.db"
driver = "jdbc"Configuration
Flash TOML Settings
| Setting | Default | Description |
|---|---|---|
enabled | false | Enable Kotlin code generation |
out | flash_gen | Output directory for generated Kotlin files |
package | derived from out dir | Kotlin package name (e.g., com.example.db) |
driver | jdbc | Driver type: jdbc, exposed, or r2dbc |
Driver-Specific Config
[gen.kotlin]
enabled = true
out = "src/main/kotlin/com/myapp/db"
package = "com.myapp.db"
driver = "jdbc"Package Auto-Detection
Flash ORM automatically detects your Kotlin package from build files during flash init:
Gradle Kotlin DSL (build.gradle.kts)
The package is derived from the group property:
group = "com.kotlin.app"
// Generates: package = "com.kotlin.app"Also supports the group(...) function syntax:
group("com.kotlin.app")Gradle Groovy (build.gradle)
group = 'com.example'
// Generates: package = "com.example"Maven with Kotlin (pom.xml)
<project>
<groupId>com.example</groupId>
<artifactId>kotlin-app</artifactId>
</project>
<!-- Generates: package = "com.example.kotlin-app" -->Generated Code Overview
Running flash generate produces:
Models File
All models are generated in a single Models.kt file:
src/main/kotlin/com/myapp/db/Models.kt:
package com.myapp.db
import java.util.UUID
import java.time.LocalDateTime
data class Users(
val id: UUID?,
val name: String?,
val email: String?,
val createdAt: LocalDateTime?,
val updatedAt: LocalDateTime?
)
enum class UserRole {
ADMIN,
USER,
MODERATOR
}Per-Query-File Query Classes
Each .sql query file generates a dedicated query class:
src/main/kotlin/com/myapp/db/Users.kt:
package com.myapp.db
import java.sql.Connection
import java.sql.PreparedStatement
class UsersQueries(private val conn: Connection) {
private val stmts = mutableMapOf<String, PreparedStatement>()
fun close() { stmts.values.forEach { it.close() }; stmts.clear() }
fun getUser(id: Int): Users? {
val sql = """SELECT id, name, email FROM users WHERE id = ?"""
val stmt = stmts.getOrPut("getUser") { conn.prepareStatement(sql) }
stmt.setInt(1, id)
stmt.executeQuery().use { rs ->
return if (rs.next()) Users(rs.getObject("id", java.util.UUID::class.java), rs.getString("name"), rs.getString("email"), rs.getTimestamp("created_at")?.toLocalDateTime(), rs.getTimestamp("updated_at")?.toLocalDateTime()) else null
}
}
}Unified Query Interface
src/main/kotlin/com/myapp/db/Queries.kt:
package com.myapp.db
import java.sql.Connection
class Queries(private val conn: Connection) {
private val users = UsersQueries(conn)
companion object {
fun newq(conn: Connection): Queries = Queries(conn)
}
fun getUser(id: Int): Users? = users.getUser(id)
fun close() { users.close() }
}Driver Options
JDBC (Default)
Default driver for standard JVM database access. Uses java.sql.Connection and PreparedStatement with statement caching.
Best for: Most applications, Spring Boot with JDBC, raw JDBC.
[gen.kotlin]
enabled = true
driver = "jdbc"Exposed
Generates code that uses JetBrains Exposed ORM's Database object and exec() method.
Best for: Projects already using Exposed ORM.
[gen.kotlin]
enabled = true
driver = "exposed"Generated code wraps queries in a transaction:
import org.jetbrains.exposed.sql.Database
class UsersQueries(private val db: Database) {
fun getUser(id: Int): Users? = transaction(db) {
exec(sql, args = listOf(id)) { rs ->
if (rs.next()) Users(/* ... */) else null
}
}
}R2DBC
Generates reactive code compatible with R2DBC Connection.
Best for: Reactive/WebFlux applications.
[gen.kotlin]
enabled = true
driver = "r2dbc"Generated code uses createStatement() with bind parameters:
import io.r2dbc.spi.Connection
class UsersQueries(private val conn: Connection) {
fun getUser(id: Int): Users? {
val stmt = conn.createStatement(sql)
stmt.bind(0, id)
// Subscribe to stmt.execute() with reactor/coroutine bridge
return null // Replace with reactive mapping
}
}Note: R2DBC generates a blocking stub that you should bridge to your reactive framework (Project Reactor or Kotlin coroutines).
Working with JDBC
Using HikariCP Connection Pool
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import com.myapp.db.Queries
fun main() {
val config = HikariConfig().apply {
jdbcUrl = System.getenv("DATABASE_URL")
}
val ds = HikariDataSource(config)
ds.connection.use { conn ->
val db = Queries.newq(conn)
val user = db.getUser(42)
println(user)
}
}Spring Boot Integration
import org.springframework.stereotype.Repository
import com.myapp.db.Queries
import javax.sql.DataSource
@Repository
class UserRepository(private val ds: DataSource) {
fun findById(id: Int): Users? =
ds.connection.use { conn ->
Queries.newq(conn).getUser(id)
}
}Working with Exposed
import org.jetbrains.exposed.sql.Database
import com.myapp.db.Queries
fun main() {
val db = Database.connect(System.getenv("DATABASE_URL"))
val queries = Queries.newq(db)
val user = queries.getUser(42)
println(user)
}Working with R2DBC
import io.r2dbc.spi.ConnectionFactories
import com.myapp.db.Queries
fun main() {
val connectionFactory = ConnectionFactories.get(System.getenv("DATABASE_URL"))
val mono = MonoSingle(connectionFactory.create()) { conn ->
val queries = Queries.newq(conn)
queries.getUser(42)
}
}ScyllaDB / Cassandra Support
For ScyllaDB or Cassandra (CQL), the Kotlin generator produces code using the DataStax Java Driver (CqlSession).
Configuration
[database]
provider = "scylla"
url_env = "DATABASE_URL"
[gen.kotlin]
enabled = true
out = "src/main/kotlin/com/myapp/db"Generated CQL Model
package com.myapp.db
import java.util.UUID
import java.time.LocalDateTime
data class Messages(
val channelId: UUID?,
val id: UUID?,
val authorId: UUID?,
val content: String?,
val type: Int?,
val mentionUserIds: Set<UUID>?,
val createdAt: LocalDateTime?
)Usage with CqlSession
import com.datastax.oss.driver.api.core.CqlSession
import com.myapp.db.Queries
fun main() {
CqlSession.builder().build().use { session ->
val db = Queries.newq(session)
val message = db.getMessage(channelId, messageId)
println(message)
}
}Schema Definition
Define your database schema in db/schema/schema.sql:
CREATE TABLE users (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
role user_role NOT NULL DEFAULT 'user',
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TYPE user_role AS ENUM ('admin', 'user', 'moderator');Writing Queries
Write parameterized queries in .sql files using annotations:
db/queries/users.sql:
-- name: GetUser :one
SELECT * FROM users WHERE id = ?;
-- name: ListUsers :many
SELECT id, name, email FROM users ORDER BY name;
-- name: CreateUser :exec
INSERT INTO users (id, name, email, role) VALUES (?, ?, ?, ?);
-- name: CountUsersByRole :one
SELECT COUNT(*) FROM users WHERE role = ?;Annotations:
:one— Returns a single row (or null):many— Returns a list of rows:exec— Returns affected row count
Building and Running
Gradle Kotlin DSL
dependencies {
implementation("com.zaxxer:HikariCP:5.1.0")
implementation("org.jetbrains.exposed:exposed-core:0.44.1") // for Exposed driver
implementation("io.r2dbc:r2dbc-spi:1.0.0.RELEASE") // for R2DBC driver
}Maven
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.1.0</version>
</dependency>Run
# Generate code
flash generate
# Build with Gradle
gradle build
# Build with Maven
mvn compileBest Practices
- Use nullable types — Generated code uses
Type?for all fields since database values may be null - Place generated code in your source tree — Set
out = "src/main/kotlin/com/myapp/db"for Gradle/Maven - Use
packageconfig — Always set the package explicitly to match your project structure - Use HikariCP for JDBC — Connection pooling is essential for production
- Kotlin Coroutines with R2DBC — Bridge generated stubs to kotlinx.coroutines for reactive flows
- Close resources — Use
.use {}blocks orclose()on your Queries instance - Run
flash generateafter schema changes — Regenerate whenever you modify your schema or queries