快速上手
Ktorm 已经发布到 maven 中央仓库,因此,如果你使用 maven 的话,只需要在 pom.xml
文件里面添加一个依赖:
1 | <dependency> |
或者 gradle:
1 | compile "org.ktorm:ktorm-core:${ktorm.version}" |
首先,创建 Kotlin object,描述你的表结构:
1 | object Departments : Table<Nothing>("t_department") { |
然后,连接到数据库,执行一个简单的查询:
1 | fun main() { |
现在,你可以执行这个程序了,Ktorm 会生成一条 SQL select * from t_employee
,查询表中所有的员工记录,然后打印出他们的名字。 因为 select
函数返回的查询对象重载了迭代运算符,所以你可以在这里使用 for-each 循环的语法。
SQL DSL
让我们在上面的查询里再增加一点筛选条件:
1 | database |
生成的 SQL 如下:
1 | select t_employee.name as t_employee_name |
这就是 Kotlin 的魔法,使用 Ktorm 写查询十分地简单和自然,所生成的 SQL 几乎和 Kotlin 代码一一对应。并且,Ktorm 是强类型的,编译器会在你的代码运行之前对它进行检查,IDE 也能对你的代码进行智能提示和自动补全。
动态查询,根据不同的情况在 where 子句中增加不同的筛选条件:
1 | val query = database |
聚合查询:
1 | val t = Employees.aliased("t") |
Union:
1 | val query = database |
多表连接查询:
1 | data class Names(val name: String?, val managerName: String?, val departmentName: String?) |
插入:
1 | database.insert(Employees) { |
更新:
1 | database.update(Employees) { |
删除:
1 | database.delete(Employees) { it.id eq 4 } |
更多 SQL DSL 的用法,请参考具体文档。
实体类与列绑定
除了 SQL DSL 以外,Ktorm 也支持实体对象。首先,我们需要定义实体类,然后在表对象中使用 bindTo
函数将表与实体类进行绑定。在 Ktorm 里面,我们使用接口定义实体类,继承 Entity<E>
即可:
1 | interface Department : Entity<Department> { |
修改前面的表对象,把数据库中的列绑定到实体类的属性上:
1 | object Departments : Table<Department>("t_department") { |
命名规约:强烈建议使用单数名词命名实体类,使用名词的复数形式命名表对象,如:Employee/Employees、Department/Departments。
完成列绑定后,我们就可以使用序列 API 对实体进行各种灵活的操作。我们先给 Database
定义两个扩展属性,它们使用 sequenceOf
函数创建序列对象并返回。这两个属性可以帮助我们提高代码的可读性:
1 | val Database.departments get() = this.sequenceOf(Departments) |
下面的代码使用 find
函数从序列中根据名字获取一个 Employee 对象:
1 | val employee = database.employees.find { it.name eq "vince" } |
我们还能使用 filter
函数对序列进行筛选,比如获取所有名字为 vince 的员工:
1 | val employees = database.employees.filter { it.name eq "vince" }.toList() |
find
和 filter
函数都接受一个 lambda 表达式作为参数,使用该 lambda 的返回值作为条件,生成一条查询 SQL。可以看到,生成的 SQL 自动 left join 了关联表 t_department
:
1 | select * |
将实体对象保存到数据库:
1 | val employee = Employee { |
将内存中实体对象的变化更新到数据库:
1 | val employee = database.employees.find { it.id eq 2 } ?: return |
从数据库中删除实体对象:
1 | val employee = database.employees.find { it.id eq 2 } ?: return |
更多实体 API 的用法,可参考列绑定和实体查询相关的文档。
实体序列 API
Ktorm 提供了一套名为”实体序列”的 API,用来从数据库中获取实体对象。正如其名字所示,它的风格和使用方式与 Kotlin 标准库中的序列 API 极其类似,它提供了许多同名的扩展函数,比如 filter
、map
、reduce
等。
Ktorm 的实体序列 API,大部分都是以扩展函数的方式提供的,这些扩展函数大致可以分为两类,它们分别是中间操作和终止操作。
中间操作
这类操作并不会执行序列中的查询,而是修改并创建一个新的序列对象,比如 filter
函数会使用指定的筛选条件创建一个新的序列对象。下面使用 filter
获取部门 1 中的所有员工:
1 | val employees = database.employees.filter { it.departmentId eq 1 }.toList() |
可以看到,用法几乎与 kotlin.sequences
完全一样,不同的仅仅是在 lambda 表达式中的等号 ==
被这里的 eq
函数代替了而已。filter
函数还可以连续使用,此时所有的筛选条件将使用 and
运算符进行连接,比如:
1 | val employees = database.employees |
生成 SQL:
1 | select * |
使用 sortedBy
或 sortedByDescending
对序列中的元素进行排序:
1 | val employees = database.employees.sortedBy { it.salary }.toList() |
使用 drop
和 take
函数进行分页:
1 | val employees = database.employees.drop(1).take(1).toList() |
终止操作
实体序列的终止操作会马上执行一个查询,获取查询的执行结果,然后执行一定的计算。for-each 循环就是一个典型的终止操作,下面我们使用 for-each 循环打印出序列中所有的员工:
1 | for (employee in database.employees) { |
生成的 SQL 如下:
1 | select * |
toCollection
、toList
等方法用于将序列中的元素保存为一个集合:
1 | val employees = database.employees.toCollection(ArrayList()) |
mapColumns
函数用于获取指定列的结果:
1 | val names = database.employees.mapColumns { it.name } |
除此之外,mapColumns
还可以同时获取多个列的结果,这时我们只需要在闭包中使用 tupleOf
包装我们的这些字段,函数的返回值也相应变成了 List<TupleN<C1?, C2?, .. Cn?>>
:
1 | database.employees |
生成 SQL:
1 | select t_employee.id, t_employee.name |
其他我们熟悉的序列函数也都支持,比如 fold
、reduce
、forEach
等,下面使用 fold
计算所有员工的工资总和:
1 | val totalSalary = database.employees.fold(0L) { acc, employee -> acc + employee.salary } |
序列聚合
实体序列 API 不仅可以让我们使用类似 kotlin.sequences
的方式获取数据库中的实体对象,它还支持丰富的聚合功能,让我们可以方便地对指定字段进行计数、求和、求平均值等操作。
下面使用 aggregateColumns
函数获取部门 1 中工资的最大值:
1 | val max = database.employees |
如果你希望同时获取多个聚合结果,只需要在闭包中使用 tupleOf
包装我们的这些聚合表达式即可,此时函数的返回值就相应变成了 TupleN<C1?, C2?, .. Cn?>
。下面的例子获取部门 1 中工资的平均值和极差:
1 | val (avg, diff) = database.employees |
生成 SQL:
1 | select avg(t_employee.salary), max(t_employee.salary) - min(t_employee.salary) |
除了直接使用 aggregateColumns
函数以外,Ktorm 还为序列提供了许多方便的辅助函数,他们都是基于 aggregateColumns
函数实现的,分别是 count
、any
、none
、all
、sumBy
、maxBy
、minBy
、averageBy
。
下面改用 maxBy
函数获取部门 1 中工资的最大值:
1 | val max = database.employees |
除此之外,Ktorm 还支持分组聚合,只需要先调用 groupingBy
,再调用 aggregateColumns
。下面的代码可以获取所有部门的平均工资,它的返回值类型是 Map<Int?, Double?>
,其中键为部门 ID,值是各个部门工资的平均值:
1 | val averageSalaries = database.employees |
生成 SQL:
1 | select t_employee.department_id, avg(t_employee.salary) |
在分组聚合时,Ktorm 也提供了许多方便的辅助函数,它们是 eachCount(To)
、eachSumBy(To)
、eachMaxBy(To)
、eachMinBy(To)
、eachAverageBy(To)
。有了这些辅助函数,上面获取所有部门平均工资的代码就可以改写成:
1 | val averageSalaries = database.employees |
除此之外,Ktorm 还提供了 aggregate
、fold
、reduce
等函数,它们与 kotlin.collections.Grouping
的相应函数同名,功能也完全一样。下面的代码使用 fold
函数计算每个部门工资的总和:
1 | val totalSalaries = database.employees |