Quick Start
Ktorm was deployed to maven central, so you just need to add a dependency to your pom.xml
file if you are using maven:
1 | <dependency> |
Or Gradle:
1 | compile "org.ktorm:ktorm-core:${ktorm.version}" |
Firstly, create Kotlin objects to describe your table schemas:
1 | object Departments : Table<Nothing>("t_department") { |
Then, connect to your database and write a simple query:
1 | fun main() { |
Now you can run this program, Ktorm will generate a SQL select * from t_employee
, selecting all employees in the table and printing their names. You can use the for-each loop here because the query object returned by the select
function overloads the iteration operator.
SQL DSL
Let’s add some filter conditions to the query:
1 | database |
Generated SQL:
1 | select t_employee.name as t_employee_name |
That’s the magic of Kotlin, writing a query with Ktorm is easy and natural, the generated SQL is exactly corresponding to the origin Kotlin code. And moreover, it’s strong-typed, the compiler will check your code before it runs, and you will be benefited from the IDE’s intelligent sense and code completion.
Dynamic query that will apply different filter conditions in different situations:
1 | val query = database |
Aggregation:
1 | val t = Employees.aliased("t") |
Union:
1 | val query = database |
Joining:
1 | data class Names(val name: String?, val managerName: String?, val departmentName: String?) |
Insert:
1 | database.insert(Employees) { |
Update:
1 | database.update(Employees) { |
Delete:
1 | database.delete(Employees) { it.id eq 4 } |
Refer to detailed documentation for more usages about SQL DSL.
Entities & Column Binding
In addition to SQL DSL, entity objects are also supported just like other ORM frameworks do. We need to define entity classes firstly and bind table objects to them. In Ktorm, entity classes are defined as interfaces extending from Entity<E>
:
1 | interface Department : Entity<Department> { |
Modify the table objects above, binding database columns to entity properties:
1 | object Departments : Table<Department>("t_department") { |
Naming Strategy: It’s highly recommended to name your entity classes by singular nouns, name table objects by plurals (eg. Employee/Employees, Department/Departments).
Now that column bindings are configured, so we can use sequence APIs to perform many operations on entities. Let’s add two extension properties for Database
first. These properties return new created sequence objects via sequenceOf
and they can help us improve the readability of the code:
1 | val Database.departments get() = this.sequenceOf(Departments) |
The following code uses the find
function to obtain an employee by its name:
1 | val employee = database.employees.find { it.name eq "vince" } |
We can also filter the sequence by the function filter
. For example, obtaining all the employees whose names are vince:
1 | val employees = database.employees.filter { it.name eq "vince" }.toList() |
The find
and filter
functions both accept a lambda expression, generating a select sql with the condition returned by the lambda. The generated SQL auto left joins the referenced table t_department
:
1 | select * |
Save entities to database:
1 | val employee = Employee { |
Flush property changes in memory to database:
1 | val employee = database.employees.find { it.id eq 2 } ?: return |
Delete an entity from database:
1 | val employee = database.employees.find { it.id eq 2 } ?: return |
Detailed usages of entity APIs can be found in the documentation of column binding and entity query.
Entity Sequence APIs
Ktorm provides a set of APIs named Entity Sequence, which can be used to obtain entity objects from databases. As the name implies, its style and use pattern are highly similar to the sequence APIs in Kotlin standard lib, as it provides many extension functions with the same names, such as filter
, map
, reduce
, etc.
Most of the entity sequence APIs are provided as extension functions, which can be divided into two groups, they are intermediate operations and terminal operations.
Intermediate Operations
These functions don’t execute the internal queries but return new-created sequence objects applying some modifications. For example, the filter
function creates a new sequence object with the filter condition given by its parameter. The following code obtains all the employees in department 1 by using filter
:
1 | val employees = database.employees.filter { it.departmentId eq 1 }.toList() |
We can see that the usage is almost the same as kotlin.sequences
, the only difference is the ==
in the lambda is replaced by the eq
function. The filter
function can also be called continuously, as all the filter conditions are combined with the and
operator.
1 | val employees = database.employees |
Generated SQL:
1 | select * |
Use sortedBy
or soretdByDescending
to sort entities in a sequence:
1 | val employees = database.employees.sortedBy { it.salary }.toList() |
Use drop
and take
for pagination:
1 | val employees = database.employees.drop(1).take(1).toList() |
Terminal Operations
Terminal operations of entity sequences execute the queries right now, then obtain the query results and perform some calculations on them. The for-each loop is a typical terminal operation, and the following code uses it to print all employees in the sequence:
1 | for (employee in database.employees) { |
Generated SQL:
1 | select * |
The toCollection
functions (including toList
, toSet
, etc.) are used to collect all the elements into a collection:
1 | val employees = database.employees.toCollection(ArrayList()) |
The mapColumns
function is used to obtain the results of a column:
1 | val names = database.employees.mapColumns { it.name } |
Additionally, if we want to select two or more columns, we just need to wrap our selected columns by tupleOf
in the closure, and the function’s return type becomes List<TupleN<C1?, C2?, .. Cn?>>
.
1 | database.employees |
Generated SQL:
1 | select t_employee.id, t_employee.name |
Other familiar functions are also supported, such as fold
, reduce
, forEach
, etc. The following code calculates the total salary of all employees:
1 | val totalSalary = database.employees.fold(0L) { acc, employee -> acc + employee.salary } |
Sequence Aggregation
The entity sequence APIs not only allow us to obtain entities from databases just like using kotlin.sequences
, but they also provide rich support for aggregations, so we can conveniently count the columns, sum them, or calculate their averages, etc.
The following code obtains the max salary in department 1:
1 | val max = database.employees |
Also, if we want to aggregate two or more columns, we just need to wrap our aggregate expressions by tupleOf
in the closure, and the function’s return type becomes TupleN<C1?, C2?, .. Cn?>
. The example below obtains the average and the range of salaries in department 1:
1 | val (avg, diff) = database.employees |
Generated SQL:
1 | select avg(t_employee.salary), max(t_employee.salary) - min(t_employee.salary) |
Ktorm also provides many convenient helper functions implemented based on aggregateColumns
, they are count
, any
, none
, all
, sumBy
, maxBy
, minBy
, averageBy
.
The following code obtains the max salary in department 1 using maxBy
instead:
1 | val max = database.employees |
Additionally, grouping aggregations are also supported, we just need to call groupingBy
before calling aggregateColumns
. The following code obtains the average salaries for each department. Here, the result’s type is Map<Int?, Double?>
, in which the keys are departments’ IDs, and the values are the average salaries of the departments.
1 | val averageSalaries = database.employees |
Generated SQL:
1 | select t_employee.department_id, avg(t_employee.salary) |
Ktorm also provides many convenient helper functions for grouping aggregations, they are eachCount(To)
, eachSumBy(To)
, eachMaxBy(To)
, eachMinBy(To)
, eachAverageBy(To)
. With these functions, we can write the code below to obtain average salaries for each department:
1 | val averageSalaries = database.employees |
Other familiar functions are also supported, such as aggregate
, fold
, reduce
, etc. They have the same names as the extension functions of kotlin.collections.Grouping
, and the usages are totally the same. The following code calculates the total salaries for each department using fold
:
1 | val totalSalaries = database.employees |
Detailed usages of entity sequence APIs can be found in the documentation of entity sequence and sequence aggregation.