[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 专属语法以及建立资料表的方式。


<<:  Amazon Linux 2 上解决跨来源资源共用 (CORS) 与开机自动启动 uwsgi - Day 09

>>:  Day 9 - Kotlin的回圈(下)

Day 30-完赛结论,所有公有云的问题,我一率建议 Terraform

本篇是 30 天铁人赛的最後一篇,本篇做个小节与心得 课程内容与代码会放在 Github 上: ht...

【DAY 02】如何选择网页开发的编辑器

前言 在学程序之前当然就是要先选择好适合自己的编译器啦~ 有许许多多的网页开发工具中如何选择呢? 我...

Day 27: 暴力破解 WPA/WPA2 加密 wifi 密码

Day 27: 暴力破解 WPA/WPA2 加密 wifi 密码 tags: Others 自我挑战...

EP 15: The Button of item in ListView binds Command to ViewModel

Hello, 各位 iT邦帮忙 的粉丝们大家好~~~ 本篇是 Re: 从零开始用 Xamarin 技...

Day 27 : Python - 什麽是列表推导式?又该如何将它和if、if-else一起做使用?

如标题,这篇想和大家聊聊「列表推导式」是什麽东西 我们先看看范例再说明,这样大家会比较好理解 Ex ...