自從 Google 宣布 Kotlin 成為 Android 的官方語言审残,Kotlin 可以說是突然火了一波梭域。其實不僅僅是 Android斑举,在服務(wù)端開發(fā)的領(lǐng)域搅轿,Kotlin 也可以說是優(yōu)勢明顯。由于其支持空安全富玷、方法擴展璧坟、協(xié)程等眾多的優(yōu)良特性,以及與 Java 幾乎完美的兼容性赎懦,選擇 Kotlin 可以說是好處多多雀鹃。
然而,切換到 Kotlin 之后励两,你還在用 MyBatis 嗎黎茎?MyBatis 作為一個 Java 的 SQL 映射框架,雖然在國內(nèi)使用人數(shù)眾多当悔,但是也受到了許多吐槽傅瞻。使用 MyBatis,你必須要忍受在 XML 里寫 SQL 這種奇怪的操作盲憎,以及在眾多 XML 與 Java 接口文件之間跳來跳去的麻煩嗅骄,以及往 XML 中傳遞多個參數(shù)時的一坨坨 @Param
注解(或者你使用 Map
?那就更糟了饼疙,連基本的類型校驗都沒有溺森,參數(shù)名也容易寫錯)。甚至窑眯,在與 Kotlin 共存的時候屏积,還會出現(xiàn)一些奇怪的問題,比如: Kotlin 遇到 MyBatis:到底是 Int 的錯磅甩,還是 data class 的錯肾请?。
這時更胖,你可能想要一款專屬于 Kotlin 的 ORM 框架铛铁。它可以充分利用 Kotlin 的各種優(yōu)良特性,讓我們寫出更加 Kotlin 的代碼却妨。它應(yīng)該是輕量級的饵逐,只需要添加依賴即可直接使用,不需要各種麻煩的配置文件彪标。它的 SQL 最好可以自動生成倍权,不需要像 MyBatis 那樣每條 SQL 都自己寫,但是也給我們保留精確控制 SQL 的能力,不至于像 Hibernate 那樣難以進(jìn)行 SQL 調(diào)優(yōu)薄声。
如果你真的這么想的話当船,Ktorm 可能會適合你。Ktorm 是直接基于純 JDBC 編寫的高效簡潔的 Kotlin ORM 框架默辨,它提供了強類型而且靈活的 SQL DSL 和方便的序列 API德频,以減少我們操作數(shù)據(jù)庫的重復(fù)勞動。當(dāng)然缩幸,所有的 SQL 都是自動生成的壹置。本文的目的就是對 Ktorm 進(jìn)行介紹,幫助我們快速上手使用表谊。
你可以在 Ktorm 的官網(wǎng)上獲取更詳細(xì)的使用文檔钞护,如果使用遇到問題,還可以在 GitHub 提出 issue爆办。如果 Ktorm 對你有幫助的話难咕,請在 GitHub 留下你的 star,也歡迎加入我們距辆,共同打造 Kotlin 優(yōu)雅的 ORM 解決方案余佃。
Ktorm 官網(wǎng):https://ktorm.liuwj.me/
GitHub 地址:https://github.com/vincentlauvlwj/Ktorm
Hello, Ktorm!
還記得我們剛開始學(xué)編程的時候?qū)懙牡谝粋€程序嗎,現(xiàn)在我們先從 Ktorm 的 "Hello, World" 開始挑格,了解如何快速地搭建一個使用 Ktorm 的項目咙冗。
Ktorm 已經(jīng)發(fā)布到 maven 中央倉庫和 jcenter,因此漂彤,如果你使用 maven 的話雾消,首先需要在 pom.xml
文件里面添加一個依賴:
<dependency>
<groupId>me.liuwj.ktorm</groupId>
<artifactId>ktorm-core</artifactId>
<version>${ktorm.version}</version>
</dependency>
或者 gradle:
compile "me.liuwj.ktorm:ktorm-core:${ktorm.version}"
在使用 Ktorm 之前,我們需要要讓它能夠了解我們的表結(jié)構(gòu)挫望。假設(shè)我們有兩個表立润,他們分別是部門表 t_department
和員工表 t_employee
, 它們的建表 SQL 如下媳板,我們要如何描述這兩個表呢桑腮?
create table t_department(
id int not null primary key auto_increment,
name varchar(128) not null,
location varchar(128) not null
);
create table t_employee(
id int not null primary key auto_increment,
name varchar(128) not null,
job varchar(128) not null,
manager_id int null,
hire_date date not null,
salary bigint not null,
department_id int not null
);
一般來說,Ktorm 使用 Kotlin 中的 object 關(guān)鍵字定義一個繼承 Table
類的對象來描述表結(jié)構(gòu)蛉幸,上面例子中的兩個表可以像這樣在 Ktorm 中定義:
object Departments : Table<Nothing>("t_department") {
val id by int("id").primaryKey() // Column<Int>
val name by varchar("name") // Column<String>
val location by varchar("location") // Column<String>
}
object Employees : Table<Nothing>("t_employee") {
val id by int("id").primaryKey()
val name by varchar("name")
val job by varchar("job")
val managerId by int("manager_id")
val hireDate by date("hire_date")
val salary by long("salary")
val departmentId by int("department_id")
}
可以看到破讨,Departments
和 Employees
都繼承了 Table
,并且在構(gòu)造函數(shù)中指定了表名奕纫,Table
類還有一個泛型參數(shù)提陶,它是此表綁定到的實體類的類型,在這里我們不需要綁定到任何實體類匹层,因此指定為 Nothing
即可隙笆。表中的列則使用 val 和 by 關(guān)鍵字定義為表對象中的成員屬性,列的類型使用 int、long撑柔、varchar瘸爽、date 等函數(shù)定義,它們分別對應(yīng)了 SQL 中的相應(yīng)類型铅忿。
定義好表結(jié)構(gòu)后剪决,我們就可以使用 Database.connect
函數(shù)連接到數(shù)據(jù)庫,然后執(zhí)行一個簡單的查詢:
fun main() {
Database.connect("jdbc:mysql://localhost:3306/ktorm", driver = "com.mysql.jdbc.Driver")
for (row in Employees.select()) {
println(row[Employees.name])
}
}
這就是一個最簡單的 Ktorm 項目辆沦,這個 main
函數(shù)中只有短短三四行代碼昼捍,但是你運行它時识虚,它卻可以連接到數(shù)據(jù)庫肢扯,自動生成一條 SQL select * from t_employee
,查詢表中所有的員工記錄担锤,然后打印出他們的名字蔚晨。因為 select
函數(shù)返回的查詢對象實現(xiàn)了 Iterable<QueryRowSet>
接口,所以你可以在這里使用 for-each 循環(huán)語法肛循。當(dāng)然铭腕,任何針對 Iteralble
的擴展函數(shù)也都可用,比如 Kotlin 標(biāo)準(zhǔn)庫提供的 map/filter/reduce 系列函數(shù)多糠。
SQL DSL
讓我們在上面的查詢里再增加一點篩選條件:
val names = Employees
.select(Employees.name)
.where { (Employees.departmentId eq 1) and (Employees.name like "%vince%") }
.map { row -> row[Employees.name] }
println(names)
生成的 SQL 如下:
select t_employee.name as t_employee_name
from t_employee
where (t_employee.department_id = ?) and (t_employee.name like ?)
這就是 Kotlin 的魔法累舷,使用 Ktorm 寫查詢十分地簡單和自然,所生成的 SQL 幾乎和 Kotlin 代碼一一對應(yīng)夹孔。并且被盈,Ktorm 是強類型的,編譯器會在你的代碼運行之前對它進(jìn)行檢查搭伤,IDE 也能對你的代碼進(jìn)行智能提示和自動補全只怎。
實現(xiàn)基于條件的動態(tài)查詢也十分簡單,因為都是純 Kotlin 代碼怜俐,直接使用 if 語句就好身堡,比 MyBatis 在 XML 里面寫 <if>
標(biāo)簽好太多。
val names = Employees
.select(Employees.name)
.whereWithConditions {
if (someCondition) {
it += Employees.managerId.isNull()
}
if (otherCondition) {
it += Employees.departmentId eq 1
}
}
.map { it.getString(1) }
聚合查詢:
val t = Employees
val salaries = t
.select(t.departmentId, avg(t.salary))
.groupBy(t.departmentId)
.having { avg(t.salary) greater 100.0 }
.associate { it.getInt(1) to it.getDouble(2) }
Union:
Employees
.select(Employees.id)
.unionAll(
Departments.select(Departments.id)
)
.unionAll(
Departments.select(Departments.id)
)
.orderBy(Employees.id.desc())
多表連接查詢:
data class Names(val name: String, val managerName: String?, val departmentName: String)
val emp = Employees.aliased("emp")
val mgr = Employees.aliased("mgr")
val dept = Departments.aliased("dept")
val results = emp
.leftJoin(dept, on = emp.departmentId eq dept.id)
.leftJoin(mgr, on = emp.managerId eq mgr.id)
.select(emp.name, mgr.name, dept.name)
.orderBy(emp.id.asc())
.map {
Names(
name = it.getString(1),
managerName = it.getString(2),
departmentName = it.getString(3)
)
}
插入:
Employees.insert {
it.name to "jerry"
it.job to "trainee"
it.managerId to 1
it.hireDate to LocalDate.now()
it.salary to 50
it.departmentId to 1
}
更新:
Employees.update {
it.job to "engineer"
it.managerId to null
it.salary to 100
where {
it.id eq 2
}
}
刪除:
Employees.delete { it.id eq 4 }
這就是 Ktorm 提供的 SQL DSL拍鲤,使用這套 DSL贴谎,我們可以使用純 Kotlin 代碼來編寫查詢,不再需要在 XML 中寫 SQL季稳,也不需要在代碼中拼接 SQL 字符串擅这。而且,強類型的 DSL 還能讓我們獲得一些額外的好處绞幌,比如將一些低級的錯誤暴露在編譯期蕾哟,以及 IDE 的智能提示和自動補全。最重要的是,它生成的 SQL 幾乎與我們的 Kotlin 代碼一一對應(yīng)谭确,因此雖然我們的 SQL 是自動生成的帘营,我們?nèi)匀粚λ鼡碛薪^對的控制。
這套 DSL 幾乎可以覆蓋我們工作中常見的所有 SQL 的用法逐哈,比如 union芬迄、聯(lián)表、聚合等昂秃,甚至對嵌套查詢也有一定的支持禀梳。當(dāng)然,肯定也有一些暫時不支持的用法肠骆,比如某些數(shù)據(jù)庫中的特殊語法算途,或者十分復(fù)雜的查詢(如相關(guān)子查詢)。這其實十分罕見蚀腿,但如果真的發(fā)生嘴瓤,Ktorm 也提供了一些解決方案:
- Ktorm 可以方便的對 SQL DSL 進(jìn)行擴展,以支持某些數(shù)據(jù)庫中的特殊語法莉钙,這些擴展主要以獨立的 jar 包提供廓脆,比如
ktorm-support-mysql
。當(dāng)然磁玉,我們也能自己編寫擴展停忿。 - 對于確實無法支持的情況,Ktorm 也可以直接使用原生 SQL 進(jìn)行查詢蚊伞,并額外提供了一些方便的擴展函數(shù)支持席赂。
更多 SQL DSL 的用法,請參考 Ktorm 的具體文檔厚柳。
實體類與列綁定
前面我們已經(jīng)介紹了 SQL DSL氧枣,但是如果只有 DSL,Ktorm 還遠(yuǎn)不能稱為一個 ORM 框架别垮。接下來我們將介紹實體類的概念便监,了解如何將數(shù)據(jù)庫中的表與實體類進(jìn)行綁定,這正是 ORM 框架的核心:對象 - 關(guān)系映射碳想。
我們?nèi)匀灰郧懊娴牟块T表 t_department
和員工表 t_employee
為例烧董,創(chuàng)建兩個 Ktorm 的實體類,分別用來表示部門和員工這兩個業(yè)務(wù)概念:
interface Department : Entity<Department> {
companion object : Entity.Factory<Department>()
val id: Int
var name: String
var location: String
}
interface Employee : Entity<Employee> {
companion object : Entity.Factory<Employee>()
val id: Int?
var name: String
var job: String
var manager: Employee?
var hireDate: LocalDate
var salary: Long
var department: Department
}
可以看到胧奔,Ktorm 中的實體類都繼承了 Entity<E>
接口逊移,這個接口為實體類注入了一些通用的方法。實體類的屬性則使用 var 或 val 關(guān)鍵字直接定義即可龙填,根據(jù)需要確定屬性的類型及是否為空胳泉。
有一點可能會違背你的直覺拐叉,Ktorm 中的實體類并不是 data class,甚至也不是一個普通的 class扇商,而是 interface凤瘦。這是 Ktorm 的設(shè)計要求,通過將實體類定義為 interface案铺,Ktorm 才能夠?qū)崿F(xiàn)一些特別的功能蔬芥,以后你會了解到它的意義。
眾所周知控汉,接口并不能實例化笔诵,既然實體類被定義為接口,我們要如何才能創(chuàng)建一個實體對象呢姑子?其實很簡單乎婿,只需要像下面這樣,假裝它有一個構(gòu)造函數(shù):
val department = Department()
有心的同學(xué)應(yīng)該已經(jīng)發(fā)現(xiàn)壁酬,上面定義實體類接口的時候次酌,還為這兩個接口都增加了一個伴隨對象恨课。這個伴隨對象重載了 Kotlin 中的 invoke
操作符舆乔,因此可以使用括號像函數(shù)一樣直接調(diào)用。在 Ktorm 的內(nèi)部剂公,我們使用了 JDK 的動態(tài)代理創(chuàng)建了實體對象希俩。
還記得在上一節(jié)中我們定義的兩個表對象嗎?現(xiàn)在我們已經(jīng)有了實體類纲辽,下一步就是把實體類和前面的表對象進(jìn)行綁定颜武。這個綁定其實十分簡單,只需要在聲明列之后繼續(xù)鏈?zhǔn)秸{(diào)用 bindTo
函數(shù)或 references
函數(shù)即可拖吼,下面的代碼修改了前面的兩個表對象鳞上,完成了 ORM 綁定:
object Departments : Table<Department>("t_department") {
val id by int("id").primaryKey().bindTo { it.id }
val name by varchar("name").bindTo { it.name }
val location by varchar("location").bindTo { it.location }
}
object Employees : Table<Employee>("t_employee") {
val id by int("id").primaryKey().bindTo { it.id }
val name by varchar("name").bindTo { it.name }
val job by varchar("job").bindTo { it.job }
val managerId by int("manager_id").bindTo { it.manager.id }
val hireDate by date("hire_date").bindTo { it.hireDate }
val salary by long("salary").bindTo { it.salary }
val departmentId by int("department_id").references(Departments) { it.department }
}
命名規(guī)約:強烈建議使用單數(shù)名詞命名實體類,使用名詞的復(fù)數(shù)形式命名表對象吊档,如:Employee/Employees篙议、Department/Departments。
把兩個表對象與修改前進(jìn)行對比怠硼,我們可以發(fā)現(xiàn)兩處不同:
-
Table
類的泛型參數(shù)鬼贱,我們需要指定為實體類的類型,以便 Ktorm 將表對象與實體類進(jìn)行綁定香璃;在之前这难,我們設(shè)置為Nothing
表示不綁定到任何實體類。 - 在每個列聲明函數(shù)的調(diào)用后葡秒,都鏈?zhǔn)秸{(diào)用了
bindTo
或references
函數(shù)將該列與實體類的某個屬性進(jìn)行綁定姻乓;如果沒有這個調(diào)用嵌溢,則不會綁定到任何屬性。
列綁定的意義在于蹋岩,通過查詢從數(shù)據(jù)庫中獲取實體對象的時候(如 findList
函數(shù))堵腹,Ktorm 會根據(jù)我們的綁定配置,將某個列的數(shù)據(jù)填充到它所綁定的屬性中去星澳;在將實體對象中的修改更新到數(shù)據(jù)庫中的時候(如 flushChanges
函數(shù))疚顷,Ktorm 也會根據(jù)我們的綁定配置,將某個屬性的變更禁偎,同步更新到綁定它的那個列腿堤。
完成列綁定后,我們就可以使用針對實體類的各種方便的擴展函數(shù)如暖。比如根據(jù)名字獲取員工:
val vince = Employees.findOne { it.name eq "vince" }
println(vince)
findOne
函數(shù)接受一個 lambda 表達(dá)式作為參數(shù)笆檀,使用該 lambda 的返回值作為條件,生成一條查詢 SQL盒至,自動 left jion 了關(guān)聯(lián)表 t_department
酗洒。生成的 SQL 如下:
select *
from t_employee
left join t_department _ref0 on t_employee.department_id = _ref0.id
where t_employee.name = ?
其他 find*
系列函數(shù):
Employees.findAll()
Employees.findById(1)
Employees.findListByIds(listOf(1))
Employees.findMapByIds(listOf(1))
Employees.findList { it.departmentId eq 1 }
Employees.findOne { it.name eq "vince" }
將實體對象保存到數(shù)據(jù)庫:
val employee = Employee {
name = "jerry"
job = "trainee"
manager = Employees.findOne { it.name eq "vince" }
hireDate = LocalDate.now()
salary = 50
department = Departments.findOne { it.name eq "tech" }
}
Employees.add(employee)
將內(nèi)存中實體對象的變化更新到數(shù)據(jù)庫:
val employee = Employees.findById(2) ?: return
employee.job = "engineer"
employee.salary = 100
employee.flushChanges()
從數(shù)據(jù)庫中刪除實體對象:
val employee = Employees.findById(2) ?: return
employee.delete()
更多實體 API 的用法,可參考列綁定和實體查詢相關(guān)的文檔枷遂。
可以看到樱衷,只需要將表對象與實體類進(jìn)行綁定,我們就可以使用這些方便的函數(shù)酒唉,大部分對實體對象的增刪改查操作矩桂,都只需要一個函數(shù)調(diào)用即可完成,但 Ktorm 能做到的痪伦,還遠(yuǎn)不止于此侄榴。
實體序列 API
除了 find*
函數(shù)以外,Ktorm 還提供了一套名為”實體序列”的 API网沾,用來從數(shù)據(jù)庫中獲取實體對象癞蚕。正如其名字所示,它的風(fēng)格和使用方式與 Kotlin 標(biāo)準(zhǔn)庫中的序列 API 及其類似辉哥,它提供了許多同名的擴展函數(shù)桦山,比如 filter
、map
证薇、reduce
等度苔。
要獲取一個實體序列,我們可以在表對象上調(diào)用 asSequence
擴展函數(shù):
val sequence = Employees.asSequence()
Ktorm 的實體序列 API浑度,大部分都是以擴展函數(shù)的方式提供的寇窑,這些擴展函數(shù)大致可以分為兩類,它們分別是中間操作和終止操作箩张。
中間操作
這類操作并不會執(zhí)行序列中的查詢甩骏,而是修改并創(chuàng)建一個新的序列對象窗市,比如 filter
函數(shù)會使用指定的篩選條件創(chuàng)建一個新的序列對象。下面使用 filter
獲取部門 1 中的所有員工:
val employees = Employees.asSequence().filter { it.departmentId eq 1 }.toList()
可以看到饮笛,用法幾乎與 kotlin.Sequence
完全一樣咨察,不同的僅僅是在 lambda 表達(dá)式中的等號 ==
被這里的 eq
函數(shù)代替了而已。filter
函數(shù)還可以連續(xù)使用福青,此時所有的篩選條件將使用 and
操作符進(jìn)行連接摄狱,比如:
val employees = Employees
.asSequence()
.filter { it.departmentId eq 1 }
.filter { it.managerId.isNotNull() }
.toList()
生成 SQL:
select *
from t_employee
left join t_department _ref0 on t_employee.department_id = _ref0.id
where (t_employee.department_id = ?) and (t_employee.manager_id is not null)
使用 sortedBy
或 sortedByDescending
對序列中的元素進(jìn)行排序:
val employees = Employees.asSequence().sortedBy { it.salary }.toList()
使用 drop
和 take
函數(shù)進(jìn)行分頁:
val employees = Employees.asSequence().drop(1).take(1).toList()
終止操作
實體序列的終止操作會馬上執(zhí)行一個查詢,獲取查詢的執(zhí)行結(jié)果无午,然后執(zhí)行一定的計算媒役。for-each 循環(huán)就是一個典型的終止操作,下面我們使用 for-each 循環(huán)打印出序列中所有的員工:
for (employee in Employees.asSequence()) {
println(employee)
}
生成的 SQL 如下:
select *
from t_employee
left join t_department _ref0 on t_employee.department_id = _ref0.id
toCollection
宪迟、toList
等方法用于將序列中的元素保存為一個集合:
val employees = Employees.asSequence().toCollection(ArrayList())
mapColumns
函數(shù)用于獲取指定列的結(jié)果:
val names = Employees.asSequenceWithoutReferences().mapColumns { it.name }
除此之外酣衷,還有 mapColumns2
、mapColumns3
等更多函數(shù)次泽,它們用來同時獲取多個列的結(jié)果穿仪,這時我們需要在閉包中使用 Pair
或 Triple
包裝我們的這些字段,函數(shù)的返回值也相應(yīng)變成了 List<Pair<C1?, C2?>>
或 List<Triple<C1?, C2?, C3?>>
:
Employees
.asSequenceWithoutReferences()
.filter { it.departmentId eq 1 }
.mapColumns2 { Pair(it.id, it.name) }
.forEach { (id, name) ->
println("$id:$name")
}
生成 SQL:
select t_employee.id, t_employee.name
from t_employee
where t_employee.department_id = ?
其他我們熟悉的序列函數(shù)也都支持意荤,比如 fold
啊片、reduce
、forEach
等袭异,下面使用 fold
計算所有員工的工資總和:
val totalSalary = Employees
.asSequenceWithoutReferences()
.fold(0L) { acc, employee ->
acc + employee.salary
}
序列聚合
實體序列 API 不僅可以讓我們使用類似 kotlin.Sequence
的方式獲取數(shù)據(jù)庫中的實體對象钠龙,它還支持豐富的聚合功能,讓我們可以方便地對指定字段進(jìn)行計數(shù)御铃、求和、求平均值等操作沈矿。
下面使用 aggregateColumns
函數(shù)獲取部門 1 中工資的最大值:
val max = Employees
.asSequenceWithoutReferences()
.filter { it.departmentId eq 1 }
.aggregateColumns { max(it.salary) }
如果你希望同時獲取多個聚合結(jié)果上真,可以改用 aggregateColumns2
或 aggregateColumns3
函數(shù),這時我們需要在閉包中使用 Pair
或 Triple
包裝我們的這些聚合表達(dá)式羹膳,函數(shù)的返回值也相應(yīng)變成了 Pair<C1?, C2?>
或 Triple<C1?, C2?, C3?>
睡互。下面的例子獲取部門 1 中工資的平均值和極差:
val (avg, diff) = Employees
.asSequenceWithoutReferences()
.filter { it.departmentId eq 1 }
.aggregateColumns2 { Pair(avg(it.salary), max(it.salary) - min(it.salary)) }
生成 SQL:
select avg(t_employee.salary), max(t_employee.salary) - min(t_employee.salary)
from t_employee
where t_employee.department_id = ?
除了直接使用 aggregateColumns
函數(shù)以外,Ktorm 還為序列提供了許多方便的輔助函數(shù)陵像,他們都是基于 aggregateColumns
函數(shù)實現(xiàn)的就珠,分別是 count
、any
醒颖、none
妻怎、all
、sumBy
泞歉、maxBy
逼侦、minBy
匿辩、averageBy
。
下面改用 maxBy
函數(shù)獲取部門 1 中工資的最大值:
val max = Employees
.asSequenceWithoutReferences()
.filter { it.departmentId eq 1 }
.maxBy { it.salary }
除此之外榛丢,Ktorm 還支持分組聚合铲球,只需要先調(diào)用 groupingBy
,再調(diào)用 aggregateColumns
晰赞。下面的代碼可以獲取所有部門的平均工資稼病,它的返回值類型是 Map<Int?, Double?>
,其中鍵為部門 ID掖鱼,值是各個部門工資的平均值:
val averageSalaries = Employees
.asSequenceWithoutReferences()
.groupingBy { it.departmentId }
.aggregateColumns { avg(it.salary) }
生成 SQL:
select t_employee.department_id, avg(t_employee.salary)
from t_employee
group by t_employee.department_id
在分組聚合時溯饵,Ktorm 也提供了許多方便的輔助函數(shù),它們是 eachCount(To)
锨用、eachSumBy(To)
丰刊、eachMaxBy(To)
、eachMinBy(To)
增拥、eachAverageBy(To)
啄巧。有了這些輔助函數(shù),上面獲取所有部門平均工資的代碼就可以改寫成:
val averageSalaries = Employees
.asSequenceWithoutReferences()
.groupingBy { it.departmentId }
.eachAverageBy { it.salary }
除此之外掌栅,Ktorm 還提供了 aggregate
秩仆、fold
、reduce
等函數(shù)猾封,它們與 kotlin.collections.Grouping
的相應(yīng)函數(shù)同名澄耍,功能也完全一樣。下面的代碼使用 fold
函數(shù)計算每個部門工資的總和:
val totalSalaries = Employees
.asSequenceWithoutReferences()
.groupingBy { it.departmentId }
.fold(0L) { acc, employee ->
acc + employee.salary
}
更多實體序列 API 的用法晌缘,可參考實體序列和序列聚合相關(guān)的文檔齐莲。
小結(jié)
本文從一個 "Hello, World" 程序開始,對 Ktorm 的幾大特性進(jìn)行了介紹磷箕,它們分別是 SQL DSL选酗、實體類與列綁定、實體序列 API 等岳枷。有了 Ktorm芒填,我們就可以使用純 Kotlin 代碼方便地完成數(shù)據(jù)持久層的操作,不需要再使用 MyBatis 煩人的 XML空繁。同時殿衰,由于 Ktorm 是專注于 Kotlin 語言的框架,因此沒有兼容 Java 的包袱盛泡,能夠讓我們更加充分地使用 Kotlin 各種優(yōu)越的語法特性闷祥,寫出更加優(yōu)雅的代碼。既然語言都已經(jīng)切換到 Kotlin饭于,為何不嘗試一下純 Kotlin 的框架呢蜀踏?
Enjoy Ktorm, enjoy Kotlin!