你還在用 MyBatis 嗎,Ktorm 了解一下?

自從 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")
}

可以看到破讨,DepartmentsEmployees 都繼承了 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)兩處不同:

  1. Table 類的泛型參數(shù)鬼贱,我們需要指定為實體類的類型,以便 Ktorm 將表對象與實體類進(jìn)行綁定香璃;在之前这难,我們設(shè)置為 Nothing 表示不綁定到任何實體類。
  2. 在每個列聲明函數(shù)的調(diào)用后葡秒,都鏈?zhǔn)秸{(diào)用了 bindToreferences 函數(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ù)桦山,比如 filtermap证薇、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)

使用 sortedBysortedByDescending 對序列中的元素進(jìn)行排序:

val employees = Employees.asSequence().sortedBy { it.salary }.toList()

使用 droptake 函數(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 }

除此之外酣衷,還有 mapColumns2mapColumns3 等更多函數(shù)次泽,它們用來同時獲取多個列的結(jié)果穿仪,這時我們需要在閉包中使用 PairTriple 包裝我們的這些字段,函數(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啊片、reduceforEach 等袭异,下面使用 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é)果上真,可以改用 aggregateColumns2aggregateColumns3 函數(shù),這時我們需要在閉包中使用 PairTriple 包裝我們的這些聚合表達(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)的就珠,分別是 countany醒颖、none妻怎、allsumBy泞歉、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秩仆、foldreduce 等函數(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!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末维蒙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子果覆,更是在濱河造成了極大的恐慌颅痊,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件局待,死亡現(xiàn)場離奇詭異斑响,居然都是意外死亡,警方通過查閱死者的電腦和手機钳榨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門舰罚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人薛耻,你說我怎么就攤上這事营罢。” “怎么了饼齿?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵饲漾,是天一觀的道長。 經(jīng)常有香客問我缕溉,道長考传,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任证鸥,我火速辦了婚禮僚楞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘枉层。我一直安慰自己泉褐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布返干。 她就那樣靜靜地躺著兴枯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪矩欠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天悠夯,我揣著相機與錄音癌淮,去河邊找鬼。 笑死沦补,一個胖子當(dāng)著我的面吹牛乳蓄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播夕膀,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼虚倒,長吁一口氣:“原來是場噩夢啊……” “哼美侦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起魂奥,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤菠剩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耻煤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體具壮,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年哈蝇,在試婚紗的時候發(fā)現(xiàn)自己被綠了棺妓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡炮赦,死狀恐怖怜跑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吠勘,我是刑警寧澤性芬,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站看幼,受9級特大地震影響批旺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诵姜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一汽煮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧棚唆,春花似錦暇赤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瞎惫,卻和暖如春溜腐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓜喇。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工挺益, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乘寒。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓望众,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子烂翰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353