昨天我们成功和资料库进行了串接,但是我们只建立了一张资料表,还没有实际的处理资料内容。
今天我们尝试对资料库进行基本的 CRUD 操作,也就是常说的建立、读取、修改、删除四个操作。
在 Exposed 框架内,有两种操作的方式:DSL 和 DAO。
今天我们先来学习使用 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]}
这样。
到这边,希望所有的范例都能让读者们操作成功并理解了,我们明天见!
<<: 从 IT 技术面细说 Search Console 的 27 组数字 KPI (10) :连结 - 内部连结
>>: 参与"在MCU 上全面建构AI能力" 9/10 心得
我把从第一天到现在每天的 Home 目录都放上 GitHub 了,README.md 里面有说明 ...
介绍 首先介绍什麽是载入别人的情绪给自己的电脑, 有个人已经将自己读文章时,所产生的情绪,让机器来学...
我抠 我抠 我抠抠抠,"钱歹赚"的2022,我的每一分钱都打上9个结,为了生存时...
你曾经被有家里的网路设备介面与功能雷到吗?你讨厌路由器原生不亲民的页面吗?你想要更多功能可是预算却有...
前言 要读取 .pkl 档,结果遇到各种状况,在网路上查了许多资料後终於解决了! 在这边简单做个过程...