iT邦幫忙

2021 iThome 鐵人賽

DAY 5
1
Modern Web

Kotlin 怎麼操作資料庫?談談 Kotlin Exposed 框架系列 第 5

[Day 05] Exposed 和資料庫進行互動的方式之一:DSL

  • 分享至 

  • xImage
  •  

昨天我們成功和資料庫進行了串接,但是我們只建立了一張資料表,還沒有實際的處理資料內容。

今天我們嘗試對資料庫進行基本的 CRUD 操作,也就是常說的建立、讀取、修改、刪除四個操作。

在 Exposed 框架內,有兩種操作的方式:DSL 和 DAO。

今天我們先來學習使用 DSL 的方式,和資料庫進行互動。

什麼是 DSL

DSL(Domain Specific Language),簡單的說,就是 Exposed 框架所設計的一系列函數,協助我們不需要直接撰寫 SQL Query 就可以進行對資料庫的存取。

這個做法類似於其他框架或語言的 Query Builder,如果讀者已經有熟悉的資料庫存取框架,可以拿 Exposed 框架的 DSL 和自己熟悉的做法比較看看有什麼異同之處。

新增和讀取

調整資料前,我們先將 addLogger(StdOutSqlLogger) 移除,這樣可以簡化我們後面印出的資料內容。

要新增一筆資料,我們在建立資料表後面的程式,加上以下程式碼

Cities.insert {  
    it[name] = "Taipei"  
}

就可以建立資料。

不過單純建立資料,不讀取看看的話,我們也不知道資料是否成功建立。

我們可以用另一種寫法,來取得我們所建立資料的 ID

val id = Cities.insertAndGetId {  
    it[name] = "Tainan"  
}

這樣我們就可以用取得的 id,從資料庫取出我們之前寫入的資料。

透過 id 取出資料的方式如下

Cities  
    .select { Cities.id eq id }  
    .forEach {  
        println("City #$id: ${it[Cities.name]}")  
    }

整個 transaction 看起來像是這樣

transaction {  
    SchemaUtils.create(Cities)  
    Cities.insert {  
        it[name] = "Taipei"  
 	}  
 	val id = Cities.insertAndGetId {  
 		it[name] = "Tainan"  
 	}  

 	Cities  
        .select { Cities.id eq id }  
 		.forEach {  
 			println("City #$id: ${it[Cities.name]}")  
        }
}

運作程式後,我們應該可以看到

City #2: Tainan

如果要印出所有的 City,我們可以用 selectAll() 這個函數來調整一下程式

transaction {  
 	SchemaUtils.create(Cities)  
    Cities.insert {  
 		it[name] = "Taipei"  
	}  
 	val id = Cities.insertAndGetId {  
 		it[name] = "Tainan"  
 	}  
  
 	Cities
        .selectAll()  
        .forEach {  
 			println("City #${it[Cities.id]}: ${it[Cities.name]}")  
		}  
}

到這邊,我們就可以做到新增和讀取資料了!

修改

要修改資料的話,我們可以這樣做

Cities.update({ Cities.id eq id }) {  
    it[name] = "Kaohsiung"  
}

如果你不希望用 id 找出要修改的資料,也可以使用其他條件

Cities.update({ Cities.name eq "Tainan" }) {  
    it[name] = "Kaohsiung"  
}

加上這段邏輯後,如果我們嘗試印出原本的資料,就會得到

City #2: Kaohsiung

到這邊,我們就可以做到修改資料了!

刪除

要刪除資料,我們可以透過 deleteWhere() 函數來移除資料

Cities.deleteWhere { Cities.id eq id }

資料移除之後,無法讀取內容,那我們怎麼知道我們成功刪除了呢?

我們可以透過 count() 函數來取得資料筆數,如果資料筆數為零,我們就知道原先的資料已經不存在,資料刪除成功了

println(  
    Cities  
        .select { Cities.id eq id }  
 		.count()  
)

成功的話就會印出 0,證明我們的資料刪除成功。

到這邊,資料的 CRUD 就全部介紹完畢了。

能夠做到資料的 CRUD 之後,下面我們來說明一下前面的程式碼。

什麼是 it

除了前面介紹過的 Passing trailing lambdas,在一開始,我們最先會注意到的,應該就是寫入資料時

Cities.insert {
    it[name] = "Taipei"  
}

的這個 it 了。

根據 kotlin 的官方文件Context object: this or it,裏面提到 it 是 Lambda 函數內參數(Argument)的預設值。

也就是說,我們在 insert() 函數裏面指定要操作的資料時,我們沒有特別寫一個變數名稱,來指定我們要寫入的資料內容。

取而代之,我們的做法是透過 Kotlin 提供的預設名稱來撰寫我們需要的內容。直接說 it[name] = "Taipei" 就知道要寫入的資料是什麼樣子了。

類似的邏輯,也出現在要印出所有資料時,我們透過 forEach() 的操作上

Cities  
    .selectAll()  
    .forEach {  
 		println("All Cities: ${it[Cities.name]}")  
    }

只要在 forEach() 內寫 it,kotlin 就知道指的是我們所取出的單筆資料,並且可以針對單筆資料進行操作。

這樣的設計方式,是不是很直觀呢?

什麼是 eq

我們會特別注意到的部分,還有取得資料時

Cities  
	.select { Cities.id eq id }  
	.forEach {  
		println("City #$id: ${it[Cities.name]}")  
	}

所使用的 eq 了,這是什麼做法呢?

在 kotlin 內,除了定義前綴(prefix)與後綴(suffix)之外,還可以特別定義所謂的中綴(infix)函數。這個函數可以從前後接受參數並進行運算,讓我們的語法看起來更加直觀。

在框架的程式碼裡面,是這麼定義 eq 這個函數的

  
/** Checks if this expression is equals to some [t] value. */
infix fun <T> ExpressionWithColumnType<T>.eq(t: T): Op<Boolean> = if (t == null) isNull() else EqOp(this, wrap(t))

利用 infix 的特性,我們就可以不用類似 eq(Cities.id, id) 的做法,直接寫 Cities.id eq id,讓語法更加簡潔並且直觀。

字串樣板 "${}"

再來,我們注意到的,應該就是印出資料時

println("City #$id: ${it[Cities.name]}")

所使用的 $id${it[Cities.name]} 了。

在 kotlin 內,如果我們需要印出某個變數,我們可以透過在印出的字串內加上 $ 符號,來加入該變數內容。kotlin 會自動將

這個方式,在 kotlin 內,稱為 string templates

如果我們要印出的內容比較複雜,需要透過表達式(expression)來進行呈現,我們可以用 ${} 包裝整個表達式,就像是範例裏面的 ${it[Cities.name]} 這樣。

到這邊,希望所有的範例都能讓讀者們操作成功並理解了,我們明天見!


上一篇
[Day 04] 用 Exposed 和資料庫進行串接
下一篇
[Day 06] DSL 其他和資料庫互動的方式
系列文
Kotlin 怎麼操作資料庫?談談 Kotlin Exposed 框架30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言