透過 DAO 方式存取資料,除了用傳統的 join 方式處理資料表之間的關聯外,也可以直接從物件之間的關聯來思考。
下面我們來介紹 DAO 物件之間的關聯如何設計。
像我們之前範例的 city
和 user
之間的關聯,我們可以想成是一個 city
能夠對應多個 user
的一對多關聯。
city
的部分跟之前是一樣的
object Cities : IntIdTable() {
val name = varchar("name", 50)
}
class City(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<City>(Cities)
var name by Cities.name
}
user
的部分,要改成用 reference
的方式和 city
進行關聯。
我們可以用 reference()
和 referencedOn
,來改變 user
的資料表和 DAO 物件宣告
object Users : IntIdTable() {
val city = reference("city", Cities)
val name = varchar("name", 50)
}
class User(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<User>(Users)
var city by City referencedOn Users.city
var name by Users.name
}
宣告完了之後,我們就可以用物件的方式,來處理 city
和 user
之間的關聯
SchemaUtils.create(Cities)
SchemaUtils.create(Users)
val paris = City.new {
name = "Paris"
}
User.new {
city = paris
name = "Alice"
}
User
.all()
.forEach {
println("${it.name}: ${it.city.name}")
}
我們就可以看到資料的關聯了
Alice: Paris
有時候我們可能會希望某個關聯是可選擇的,這時我們可以用 optional 的方式,調整我們的資料表和 DAO 物件。
比方說,或許我們會希望允許 user
可以不輸入 city
的資料。
我們可以這樣調整 user
的資料表和 DAO 物件。
object Users : IntIdTable() {
val city = reference("city", Cities).nullable()
val name = varchar("name", 50)
}
class User(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<User>(Users)
var city by City optionalReferencedOn Users.city
var name by Users.name
}
這樣調整之後,我們就可以在寫入 user
時略過 city
物件。
SchemaUtils.create(Cities)
SchemaUtils.create(Users)
val paris = City.new {
name = "Paris"
}
User.new {
city = paris
name = "Alice"
}
User.new {
name = "Bob"
}
User
.all()
.forEach {
println("${it.name}: ${it.city?.name}")
}
這樣我們印出資料時就會看到
Alice: Paris
Bob: null
到這邊,資料的一對多關聯就就介紹完畢了。
?.
我們在 optional 關係時,印出 city.name
時,改變了一點程式
${it.city?.name}
這是為什麼呢?
Kotlin 語言在大多數的狀況下,都是不會出現 null 的。不過在 optional 的狀況下,我們有可能會出現 it.city
為 null 的狀態,導致 it.city.name
出錯的狀況。
幸運的是,Kotlin 在編譯時就可以知道這個狀況,並且在編譯時跳出提示
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type City?
根據他的提示,我們將程式調整成 null-safe
的版本
${it.city?.name}
這樣就會在 it.city
為 null
的狀況下,不嘗試存取 it.city.name
,避免了嘗試存取 null
參數的錯誤。