iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 18
0
Mobile Development

Android Architecture Components 學習心得筆記系列 第 18

Day 18 Room (Last) 其他應用與總結

Room (Last)

對象之間的引用關係

不同于目前存在的大多數資料庫,Room 不支持 Entitiy 對象間的直接引用。具體原因可以看這

內容大致上是說,如果要將 Entitiy 對象內引用的其他對象使用延遲加載 (lateinit) 的話,因為一般的 UI Thread 大約會花 16毫秒 來計算和繪製 layout,所以即使查詢時間極短(大約 5毫秒 ),都會消耗掉寶貴的 UI Thread 的時間,若不使用懶加載,又會消耗掉不該佔用的資源,所以 Room 不推薦 Entitiy 對象間的直接引用。

既然不能直接引用對象,Room 提供了一個關鍵字 ForeignKey 用來定義對象之間的引用關係。

例如:
有一個 Entity 對象 Book,在 @Entity 註解內使用 ForeignKey 來定義和另一個 Entity 對象 User 的關係。

@Entity(foreignKeys = arrayOf(ForeignKey(

    entity = User::class,
    parentColumns = arrayOf("id"),
    childColumns = arrayOf("user_id"))
)
)
data class Book(

    @PrimaryKey val bookId: Int,
    
    val title: String
)

ForeignKey 非常強大,他能夠知道這些引用的 Entity 哪時候被更新,並且執行想要的動作。

例如:在 ForeignKey 註解內使用 onDelete = CASCADE 來告訴資料庫,如果 User 被刪除了,那麼連同他底下的所有實體 Book 也一併刪除。

巢狀物件

有時候,你想在實體中引用其他的對象,並且把該對象的成員作為自己的欄位,這時可以使用 @Embedded 來註解這個引用的對象。
例如:

data class Address(
    val street: String?,
    val state: String?,
    val city: String?,
    @ColumnInfo(name = "post_code") val postCode: Int
)

@Entity
data class User(
    @PrimaryKey val id: Int,
    val firstName: String?,
    @Embedded val address: Address?
)

這是一個 User 的 Entity 對象,並用 @Embedded 註解了另一個物件 Address
包含進去的物件就會自動生成欄位,所以這是一個包含 PrimaryKey idfirstNamestreetstatecitypostCodeUser Entity 對象。

類別轉換

Room 也提供了便利的方法來完成基本型態和物件型態的轉換,只需要透過 @TypeConverter 註解即可完成。

例如:
Date 這個物件轉換成 Long,或是把 Long 轉換成 Date

class Converters {
    @TypeConverter
    fun timeStampToLong(timeStamp: Long?): Date? {
        return timeStamp?.let { Date(it) }
    }

    @TypeConverter
    fun dateToTimeStamp(date: Date?): Long? {
        return date?.time?.toLong()
    }
}

接著,在 Database 加上 @TypeConverters 註解,就可以使用在 Converters 這個類別裡面的 @TypeConverter

@Database(entities = arrayOf(User::class), version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

測試

官方推薦使用 JUnit 來為資料庫寫測試,因為他不需要創建 Activity,能夠更快速地執行想要的測試。
以下是官方範例:

@RunWith(AndroidJUnit4::class)
class SimpleEntityReadWriteTest {
    private lateinit var userDao: UserDao
    private lateinit var db: TestDatabase

    @Before
    fun createDb() {
        val context = ApplicationProvider.getApplicationContext<Context>()
        db = Room.inMemoryDatabaseBuilder(
                context, TestDatabase::class.java).build()
        userDao = db.getUserDao()
    }

    @After
    @Throws(IOException::class)
    fun closeDb() {
        db.close()
    }

    @Test
    @Throws(Exception::class)
    fun writeUserAndReadInList() {
        val user: User = TestUtil.createUser(3).apply {
            setName("george")
        }
        userDao.insert(user)
        val byName = userDao.findUsersByName("george")
        assertThat(byName.get(0), equalTo(user))
    }
}

值得注意的一點,並不要為了測試新開另一個 Table,而是應該使用 inMemoryDatabaseBuilder 來獲取已經存在的資料庫,才能保持這個測試的封閉性。 官方文檔

Room 小結

以前在寫資料庫時都會寫一堆落落長的 SQL 語法,非常麻煩而且容易出錯,有時候不小心多打一個空格就要找老半天,(親身經歷)

而 Room 使用註解的方式把代碼縮到最簡,而且還能在編譯時幫忙尋找語法的錯誤,大幅提升了開發速度和除錯時間,可讀性和維護性也有顯著的提升,真是開發人員的一大福音。

有任何問題或講得不清楚的地方歡迎留言和我討論。

更歡迎留言糾正我任何說錯的地方!

下一篇:Paging (ㄧ) 介紹


上一篇
Day 17 Room (二) Query 的詳細用法以及如何升級(Migrate)資料庫版本
下一篇
Day 19 Paging (ㄧ) 介紹
系列文
Android Architecture Components 學習心得筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言