Provides simple extension functions for JDBC standard classes for a simple way to query a DB. Transaction management is also supported.
A more concise alternative to Spring’s JdbcTemplate, and also includes SQL statement generation. Tested mostly with PostgreSQL.
Registration with Server instance:
use<DBModule>() // to register a DataSource and connection pool using Config variables.
use<DBMigrator>() // to migrate the DB using .sql files or changesets in code, see below
use<RequestTransactionHandler>() // to enable per-request transactions
Stand-alone usage without klite-server is also possible:
val db = PooledDataSource(...) // or ConfigDataSource / HikariDataSource
DBMigrator(db).migrate()
Usage:
// obtain the registered DataSource (or declare it as a constructor parameter in your Repository class)
val db = require<DataSource>()
// basic insert into a table
db.insert("table", mapOf("col1" to value, "col2" to value2))
// insert of all fields of an entity
db.insert("table", entity.toValues())
// redefine some entity field value before inserting
db.upsert("table", entity.toValues(Entity::field to "another value"))
// basic query from a table (mapper runs in context of ResultSet)
db.select("table", "column" to value) { MyEntity(getId(), getString("column")) }
// or if all entity properties are contained in the result set
db.select("table", "column" to value) { create<MyEntity>() }
// you can write more complex custom sql conditions easily
db.select("table", sql("(column is null or column >= ?)", 10))
// where can also be written in a type-safe way, and some common operators are available
db.select("table", MyEntity::column gte 123) { create<MyEntity>() }
// where tokens are "and"-ed together, nulls filtered out, so convenient conditionals are possible
db.select("table", MyEntity::column gte 123, (MyEntity::other to something).takeIf { something != null }) { create<MyEntity>() }
// or another option is to use list filtering
db.select("table", notNullValues(MyEntity::column gte 123, MyEntity::other to something)) { create<MyEntity>() }
// "or" is also possible
db.select("table", MyEntity::column gte 123, or(MyEntity::other to something, "hello" to "world")) { create<MyEntity>() }
// more advanced query with suffix and create() auto-mapper
db.select("table", "col1" to notNull, "col2" gte value, suffix = "order by col3 limit 10") { create<MyEntity>() }
// single row, with joins, etc
db.select("table1 left join table2 on table1.id = table2.megaId", listOf("table2.field" to value), "limit 1") {
create<MyEntity>(MyEntity::other to create<OtherEntity>("table2.")) // you can provide table alias to create (PostgresSQL only)
}.first()
// or you can write full sql manually using db.query() and db.exec()
Note: before Klite 1.5 query/select functions had the opposite meaning, but users found select(“select…”) not nice.
See all available operations and comparison operators.
All the above can run without a transaction: in this case, every query will obtain and release its own connection from the pool, in autocommit mode. If Transaction is active, then it will obtain a connection on first use, set autoCommit=false and reuse it until the transaction is closed with either commit or rollback.
In query mappers you can either use ResultSet methods and extensions to build your entities or use the ResultSet.create. Likewise, Any.toValues is provided to simplify conversion of entities to Maps for use with insert/update/upsert.
JdbcConverter can be used to register conversion of custom types to be sent to the DB.
BaseRepository and CrudRepository are provided for convenience.
They work with entity classes implementing BaseEntity<ID>
, where you can provide your own ID class, like UUID or TSID.
Experimental:
NullableId<ID>
is also provided if you prefer to assign ids only during saving.UpdatabaleEntity
can be used for optimistic locking when saving, not letting concurrent users overwrite each other’s changes.DBMigrator is provided for simple SQL-based DB migrations, it supports a very similar syntax to Liquibase SQL Format, see sample.
Advantages over Liquibase:
--include
migrator/liquibase.sql, or copy the changeset with your modifications to your db.sql
Advantages over Flyway:
Not supported:
onFail:SKIP
can be easier to useIt is better for security to use a user with fewer rights for the running application.
Your changesets can actually create this user and grant only select/insert/update permissions, but not e.g. create/drop table.
// start dev db, for developer convenience
if (Config.isDev) startDevDB()
// migrate with the default all-rights user
use<DBMigrator>()
// use app user for the application DataSource and connection pool
useAppDBUser()
use(DBModule {
// override any other connection pool settings
})
It’s more convenient to treat DB objects as maintainable code, e.g.
users.personalCode
, which adds users.personalCode columncontext:TODO