当前位置: 首页 > 資訊 >

[Day 04] 用 Exposed 和資料庫進行串接

安裝 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

安裝 H2 Driver

跟安裝 Exposed 框架時類似,我們在 build.gradle.ktsdependencies {} 段落加上

implementation("com.h2database:h2:1.4.200")

重新 Load Gradle Change

Load Gradle Change

套件同步完成之後,我們的程式就可以順利執行了。

不過因為我們還沒開始撰寫與資料庫連線相關的程式,我們的程式僅僅是順利的執行完成,並沒有任何和資料庫的互動。

和資料庫進行互動

首先,我們先來嘗試建立一個新的資料表,先在程式最開頭加入

import org.jetbrains.exposed.dao.id.IntIdTable

這裏引用了 Exposed 框架所定義的 IntIdTable

main 的外面,我們定義一個 Citiesobject

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 的錯誤訊息

錯誤訊息裡面提到的 SLF4J,是Simple Logging Facade for Java 的簡稱。

這是 Java Logging 的一個套件,在這邊因為設置不完善,所以拋出錯誤。

雖然並不影響程式的運行,不過看到 SLF4J 拋出的錯誤,總是有點影響後面開發的順暢感。

這邊,我們將設置處理正常,以便於我們後面進行開發。

首先我們安裝新的 SLF4J,在 build.gradle.ktsdependencies {} 段落內加上

implementation("org.slf4j:slf4j-api:1.7.32")  
implementation("org.slf4j:slf4j-log4j12:1.7.32")

別忘記要重新 Load Gradle Change,這已經是第三次了

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 專屬語法以及建立資料表的方式。