透過 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 參數的錯誤。