Given a Kotlin project
-
Hibernate is big, slow and complex
-
Sql2o - verbose code, JVM crashes
-
Spring Data - generates code that you can't debug
-
Exposed for Kotlin - too big a DSL - I still like plain SQL
-
Frameworks should not hide the real stuff from the dev; also GWT/React/etc
-
They should simplify common use cases, letting to go deeper for full-power (e.g. plain SQL)
You just need small helpers to reduce boilerplate. Kotlin's extension functions are perfect for that
jdbc:
val rs = conn.prepareStatement("select * from users where email = ?").run {
setString(1, "hello@example.com")
executeQuery()
}
while (rs.next()) {
User(rs.getString("name"), rs.getString("email"))
}
// BUT: don't forget to close() thingsklite:
db.query("select * from users", mapOf("email" to "hello@example.com"))
db.select("users", mapOf("email" to "hello@example.com")) { // this == ResultSet
User(getString("name"), getString("email"))
}
// More convenience extensions, e.g.:
ResultSet.getLocalDate()
ResultSet.getId()It's good to keep domain classes close to DB tables, with same column names
val user = db.select("users", "email" to "hello@example.com") { create<User>() }.first()val user = db.select("users", "email" to "hello@example.com") { create(User::role to Role.BACKOFFICE) }val user = db.select<User>("users", User::email to Email("hello@example.com"))User::birthDay to null
User::birthDay to SqlOp("<", someDate)
User::age to listOf(1, 2, 3)
User::birthDay to notNull
User::birthDay lte someDate
User::name like "Anton%"
User::name to SqlExpr("(select ... where x = ?)", x)db.insert("users", mapOf("name" to "Anton", "email" to Email("hello@example.com")))
// will generate the trivial: insert into users (name, email) values (?, ?)
// with "ORM"
db.insert("users", user.toValues())
db.insert("users", user.toValuesSkipping(User::role) + mapOf("anything" to "else"))
db.update("users", mapOf("id" to user.id), user.toValues())
db.delete("users", mapOf("id" to user.id))
db.upsert("users", user.toValues()) // "id" by default checked for uniqueness- BaseRepository
- CrudRepository
Autogenerated IDs:
data class User(val name: String, val email: Email, override val id: UUID = randomUUID()): EntityAuto-validation of required fields, proper types for more complex checks (e.g. Email)
val generatedId = GeneratedKey<Int>()
db.insert(table, mapOf("id" to generatedId, "hello" to "Hello"))- DBMigrator vs Liquibase, SQL format
- Treat DB objects as maintainable code
db is DataSource
Caller is responsible for starting/closing transactions, e.g. klite request handler or JobRunner