本篇文章給大家分享的是有關如何在Room中使用Kotlin API,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
定義數據庫表
在我們的數據庫中僅有一個表,就是保存詞匯的表。Word 類代表表中的一條記錄,并且它需要使用注解 @Entity。我們使用 @PrimaryKey 注解為表定義主鍵。然后,Room 會生成一個 SQLite 表,表名和類名相同。每個類的成員對應表中的列。列名和類型與類中每個字段的名稱和類型一致。如果您希望改變列名而不使用類中的變量名稱作為列名,可以通過 @ColumnInfo 注解來修改。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @Entity(tableName = "word_table") data class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)
我們推薦大家使用 @ColumnInfo 注解,因為它可以使您更靈活地對成員進行重命名而無需同時修改數據庫的列名。因為修改列名會涉及到修改數據庫模式,因而您需要實現數據遷移。
如需訪問表中的數據,需要創建一個數據訪問對象 (DAO)。也就是一個叫做 WorkDao 的接口,它會帶有 @Dao 注解。我們希望通過它實現表級別的數據插入、刪除和獲取,所以數據訪問對象中會定義相應的抽象方法。操作數據庫屬于比較耗時的 I/O 操作,所以需要在后臺線程中完成。我們將把 Room 與 Kotlin 協程和 Flow 相結合來實現上述功能。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @Dao interface WordDao { @Query("SELECT * FROM word_table ORDER BY word ASC") fun getAlphabetizedWords(): Flow<List<Word>> @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insert(word: Word) }
我們在視頻 Kotlin Vocabulary 中介紹了 協程的相關基本概念, 在 Kotlin Vocabulary 另一個視頻中則介紹了 Flow 相關的內容。
要實現插入數據的操作,首先創建一個抽象的掛起函數,需要插入的單詞作為它的參數,并且添加 @Insert 注解。Room 會生成將數據插入數據庫的全部操作,并且由于我們將函數定義為可掛起,所以 Room 會將整個操作過程放在后臺線程中完成。因此,該掛起函數是主線程安全的,也就是在主線程可以放心調用而不必擔心阻塞主線程。
@Insert suspend fun insert(word: Word)
在底層 Room 生成了 Dao 抽象函數的實現代碼。下面代碼片段就是我們的數據插入方法的具體實現:
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @Override public Object insert(final Word word, final Continuation<? super Unit> p1) { return CoroutinesRoom.execute(__db, true, new Callable<Unit>() { @Override public Unit call() throws Exception { __db.beginTransaction(); try { __insertionAdapterOfWord.insert(word); __db.setTransactionSuccessful(); return Unit.INSTANCE; } finally { __db.endTransaction(); } } }, p1); }
CoroutinesRoom.execute() 函數被調用,里面包含三個參數: 數據庫、一個用于表示是否正處于事務中的標識、一個 Callable 對象。Callable.call() 包含處理數據庫插入數據操作的代碼。
如果我們看一下 CoroutinesRoom.execute() 的 實現,我們會看到 Room 將 callable.call() 移動到另外一個 CoroutineContext。該對象來自構建數據庫時您所提供的執行器,或者默認使用 Architecture Components IO Executor。
為了能夠查詢表數據,我們這里創建一個抽象函數,并且為其添加 @Query 注解,注解后緊跟 SQL 請求語句: 該語句從單詞數據表中請求全部單詞,并且以字母順序排序。
我們希望當數據庫中的數據發生改變的時候,能夠得到相應的通知,所以我們返回一個 Flow<List<Word>>。由于返回類型是 Flow,Room 會在后臺線程中執行數據請求。
@Query(“SELECT * FROM word_table ORDER BY word ASC”) fun getAlphabetizedWords(): Flow<List<Word>>
在底層,Room 生成了 getAlphabetizedWords():
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @Override public Flow<List<Word>> getAlphabetizedWords() { final String _sql = "SELECT * FROM word_table ORDER BY word ASC"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); return CoroutinesRoom.createFlow(__db, false, new String[]{"word_table"}, new Callable<List<Word>>() { @Override public List<Word> call() throws Exception { final Cursor _cursor = DBUtil.query(__db, _statement, false, null); try { final int _cursorIndexOfWord = CursorUtil.getColumnIndexOrThrow(_cursor, "word"); final List<Word> _result = new ArrayList<Word>(_cursor.getCount()); while(_cursor.moveToNext()) { final Word _item; final String _tmpWord; _tmpWord = _cursor.getString(_cursorIndexOfWord); _item = new Word(_tmpWord); _result.add(_item); } return _result; } finally { _cursor.close(); } } @Override protected void finalize() { _statement.release(); } }); }
我們可以看到代碼里調用了 CoroutinesRoom.createFlow(),它包含四個參數: 數據庫、一個用于標識我們是否正處于事務中的變量、一個需要監聽的數據庫表的列表 (在本例中列表里只有 word_table) 以及一個 Callable 對象。Callable.call() 包含需要被觸發的查詢的實現代碼。
如果我們看一下 CoroutinesRoom.createFlow() 的 實現代碼,會發現這里同數據請求調用一樣使用了不同的 CoroutineContext。同數據插入調用一樣,這里的分發器來自構建數據庫時您所提供的執行器,或者來自默認使用的 Architecture Components IO 執行器。
我們已經定義了存儲在數據庫中的數據以及如何訪問他們,現在我們來定義數據庫。要創建數據庫,我們需要創建一個抽象類,它繼承自 RoomDatabase,并且添加 @Database 注解。將 Word 作為需要存儲的實體元素傳入,數值 1 作為數據庫版本。
我們還會定義一個抽象方法,該方法返回一個 WordDao 對象。所有這些都是抽象類型的,因為 Room 會幫我們生成所有的實現代碼。就像這里,有很多邏輯代碼無需我們親自實現。
最后一步就是構建數據庫。我們希望能夠確保不會有多個同時打開的數據庫實例,而且還需要應用的上下文來初始化數據庫。一種實現方法是在類中添加伴生對象,并且在其中定義一個 RoomDatabase 實例,然后在類中添加 getDatabase 函數來構建數據庫。如果我們希望 Room 查詢不是在 Room 自身創建的 IO Executor 中執行,而是在另外的 Executor 中執行,我們需要通過調用 setQueryExecutor() 將新的 Executor 傳入 builder。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ companion object { @Volatile private var INSTANCE: WordRoomDatabase? = null fun getDatabase(context: Context): WordRoomDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, WordRoomDatabase::class.java, "word_database" ).build() INSTANCE = instance // 返回實例 instance } } }
為了測試 Dao,我們需要實現 AndroidJUnit 測試來讓 Room 在設備上創建 SQLite 數據庫。
當實現 Dao 測試的時候,在每個測試運行之前,我們創建數據庫。當每個測試運行后,我們關閉數據庫。由于我們并不需要在設備上存儲數據,當創建數據庫的時候,我們可以使用內存數據庫。也因為這僅僅是個測試,我們可以在主線程中運行請求。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @RunWith(AndroidJUnit4::class) class WordDaoTest { private lateinit var wordDao: WordDao private lateinit var db: WordRoomDatabase @Before fun createDb() { val context: Context = ApplicationProvider.getApplicationContext() // 由于當進程結束的時候會清除這里的數據,所以使用內存數據庫 db = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java) // 可以在主線程中發起請求,僅用于測試。 .allowMainThreadQueries() .build() wordDao = db.wordDao() } @After @Throws(IOException::class) fun closeDb() { db.close() } ... }
要測試單詞是否能夠被正確添加到數據庫,我們會創建一個 Word 實例,然后插入數據庫,然后按照字母順序找到單詞列表中的第一個,然后確保它和我們創建的單詞是一致的。由于我們調用的是掛起函數,所以我們會在 runBlocking 代碼塊中運行測試。因為這里僅僅是測試,所以我們無需關心測試過程是否會阻塞測試線程。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ @Test @Throws(Exception::class) fun insertAndGetWord() = runBlocking { val word = Word("word") wordDao.insert(word) val allWords = wordDao.getAlphabetizedWords().first() assertEquals(allWords[0].word, word.word) }
除了本文所介紹的功能,Room 提供了非常多的功能性和靈活性,遠遠超出本文所涵蓋的范圍。比如您可以指定 Room 如何處理數據庫沖突、可以通過創建 TypeConverters 存儲原生 SQLite 無法存儲的數據類型 (比如 Date 類型)、可以使用 JOIN 以及其它 SQL 功能實現復雜的查詢、創建數據庫視圖、預填充數據庫以及當數據庫被創建或打開的時候觸發特定動作。
以上就是如何在Room中使用Kotlin API,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。