安裝 Exposed 框架完成之後,再來我們要和資料庫進行串接。
首先我們將原本的 main(){}
改成
fun main() {
Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
}
這裡對接的是 Java 的 H2 資料庫,在這邊我們利用這個資料庫做一個簡單的連接測試。
不過因為我們還沒有安裝 H2 Driver 的緣故,執行 main
時,我們會看到以下錯誤訊息
Exception in thread "main" java.lang.ClassNotFoundException: org.h2.Driver
要修正這個問題,我們就要安裝 H2 Driver
跟安裝 Exposed 框架時類似,我們在 build.gradle.kts
的 dependencies {}
段落加上
implementation("com.h2database:h2:1.4.200")
重新 Load Gradle Change
套件同步完成之後,我們的程式就可以順利執行了。
不過因為我們還沒開始撰寫與資料庫連線相關的程式,我們的程式僅僅是順利的執行完成,並沒有任何和資料庫的互動。
首先,我們先來嘗試建立一個新的資料表,先在程式最開頭加入
import org.jetbrains.exposed.dao.id.IntIdTable
這裏引用了 Exposed 框架所定義的 IntIdTable
。
在 main
的外面,我們定義一個 Cities
的object
object Cities: IntIdTable() {
val name = varchar("name", 50)
}
然後,我們在 Database.connect()
後面,加入一段與資料庫的互動交易
transaction {
// 加上 StdOutSqlLogger 將 SQL 印出結果
addLogger(StdOutSqlLogger)
// 建立 CITIES 資料表
SchemaUtils.create (Cities)
}
執行這段程式後,我們會看到以下結果
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
SQL: SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME = 'MODE'
SQL: CREATE TABLE IF NOT EXISTS CITIES (ID INT AUTO_INCREMENT PRIMARY KEY, "NAME" VARCHAR(50) NOT NULL)
Process finished with exit code 0
到這裏,我們可以看到我們成功的印出了和資料庫互動的 SQL 語法。
也證明我們成功的利用 Exposed 框架和資料庫進行互動了。
錯誤訊息裡面提到的 SLF4J,是Simple Logging Facade for Java 的簡稱。
這是 Java Logging 的一個套件,在這邊因為設置不完善,所以拋出錯誤。
雖然並不影響程式的運行,不過看到 SLF4J 拋出的錯誤,總是有點影響後面開發的順暢感。
這邊,我們將設置處理正常,以便於我們後面進行開發。
首先我們安裝新的 SLF4J,在 build.gradle.kts
的 dependencies {}
段落內加上
implementation("org.slf4j:slf4j-api:1.7.32")
implementation("org.slf4j:slf4j-log4j12:1.7.32")
別忘記要重新 Load Gradle Change,這已經是第三次了
套件同步完畢之後,再次執行程式,應該會看到
log4j:WARN No appenders could be found for logger (Exposed).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
的錯誤訊息。
這是因為,我們雖然成功安裝了 SLF4J 套件,
但是我們並沒有撰寫相關的設置,導致 SLF4J 無法成功的找到該出現的 logger。
要修正這個問題,我們要在 src/main/resources/
資料夾裡面,
加入 log4j.properties
這個檔案,並在檔案裡面加入以下內容
log4j.rootLogger=ERROR
這樣設置後,我們重新執行程式,就不會看到 SLF4J 的錯誤訊息了。
下面我們來說明
object Cities: IntIdTable() {
val name = varchar("name", 50)
}
fun main() {
Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
transaction {
addLogger(StdOutSqlLogger)
SchemaUtils.create (Cities)
}
}
這段程式和資料庫的互動,具體來說是怎麼一回事
object
首先我們注意到的,可能是這個在其他語言比較少見的關鍵字:object
。
這個關鍵字的用途是什麼呢?在 kotlin 的官方文件裡,針對 Object declarations 是這麼說的:
The Singleton pattern can be useful in several cases, and Kotlin makes it easy to declare singletons:
這邊我們假設讀者應該對單例模式(Singleton pattern)略有概念,如果有疑惑的話,可以去看看設計模式學習筆記的文章,這邊就不多做說明。
object
這個關鍵字簡單的說,也就是原本在其他語言裡所使用的 Singleton pattern,在 kotlin 內不僅一樣可以使用,甚至還幫這個 pattern 建立一個關鍵字,讓它使用起來更方便。
與 Java 的 Singleton pattern 寫法比較:
public class ThisIsASingleton {
private static ThisIsASingleton instance = new ThisIsASingleton();
private ThisIsASingleton(){}
public static ThisIsASingleton getInstance(){
return instance;
}
}
Kotlin 只要這麼寫就可以建立一個 Singleton:
object ThisIsASingleton {
}
如何,是不是簡單很多呢?
transction{}
函式不需要小括號?再來,我們看到 transction()
這個函式,為什麼在我們的程式碼裡面,transction()
沒有小括號就可以直接宣告了呢?
首先,我們要知道,Kotlin 跟傳統的 OOP 語言不太一樣,除了傳統語言的多形、繼承⋯⋯等語言特性,Kotlin 還支援了許多函數式導向程式語言(Functional Programming)的特性,比方說可以將函數作為參數傳遞。
理解這件事情之後,我們再看 Kotlin 官方文件針對 Passing trailing lambdas 的說明:
According to Kotlin convention, if the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses:
也就是說,當一個函數的最後參數也是一個函數,那我們可以直接將這個函數寫在 {}
裡面。乍看說明文件時,或許會覺得這是一個很神奇的設計,但是實際和 transction()
實作的程式碼比對一下:
fun <T> transaction(db: Database? = null, statement: Transaction.() -> T): T
如果不是透過 Passing trailing lambdas 的寫法,那麼就會變成我們得先宣告一個函數,包含建立資料庫的邏輯,最後再將這個函數放在 transaction()
的參數內。與這個寫法相比,直接放在大括弧內,看起來其實更加直觀,語法也更加簡潔。
理解了Passing trailing lambdas 的寫法之後,transaction()
函數的動作就比較容易了解了,其實就是根據前面 Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
建立好的連線內容,執行一段資料庫互動的交易(transaction),內容根據最後所提供的函數邏輯即可。
在這段程式裡面,我們提供的邏輯為
addLogger(StdOutSqlLogger)
SchemaUtils.create (Cities)
這兩個函數都很直觀,addLogger(StdOutSqlLogger)
是加上 SQL Logger 協助印出 Query 內容,SchemaUtils.create (Cities)
則是根據 Cities
單例的結構建立資料表。
到這邊,我們就成功的和資料庫進行了互動,並且也詳細說明了互動所用到的 Kotlin 專屬語法以及建立資料表的方式。