溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何在Room中使用Kotlin API

發(fā)布時間:2021-04-12 15:47:00 來源:億速云 閱讀:161 作者:Leah 欄目:開發(fā)技術(shù)

本篇文章給大家分享的是有關(guān)如何在Room中使用Kotlin API,小編覺得挺實用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

定義數(shù)據(jù)庫表

在我們的數(shù)據(jù)庫中僅有一個表,就是保存詞匯的表。Word 類代表表中的一條記錄,并且它需要使用注解 @Entity。我們使用 @PrimaryKey 注解為表定義主鍵。然后,Room 會生成一個 SQLite 表,表名和類名相同。每個類的成員對應(yīng)表中的列。列名和類型與類中每個字段的名稱和類型一致。如果您希望改變列名而不使用類中的變量名稱作為列名,可以通過 @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 注解,因為它可以使您更靈活地對成員進行重命名而無需同時修改數(shù)據(jù)庫的列名。因為修改列名會涉及到修改數(shù)據(jù)庫模式,因而您需要實現(xiàn)數(shù)據(jù)遷移。

訪問表中的數(shù)據(jù)

如需訪問表中的數(shù)據(jù),需要創(chuàng)建一個數(shù)據(jù)訪問對象 (DAO)。也就是一個叫做 WorkDao 的接口,它會帶有 @Dao 注解。我們希望通過它實現(xiàn)表級別的數(shù)據(jù)插入、刪除和獲取,所以數(shù)據(jù)訪問對象中會定義相應(yīng)的抽象方法。操作數(shù)據(jù)庫屬于比較耗時的 I/O 操作,所以需要在后臺線程中完成。我們將把 Room 與 Kotlin 協(xié)程和 Flow 相結(jié)合來實現(xiàn)上述功能。

/* 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 中介紹了 協(xié)程的相關(guān)基本概念, 在 Kotlin Vocabulary 另一個視頻中則介紹了 Flow 相關(guān)的內(nèi)容。

插入數(shù)據(jù)

要實現(xiàn)插入數(shù)據(jù)的操作,首先創(chuàng)建一個抽象的掛起函數(shù),需要插入的單詞作為它的參數(shù),并且添加 @Insert 注解。Room 會生成將數(shù)據(jù)插入數(shù)據(jù)庫的全部操作,并且由于我們將函數(shù)定義為可掛起,所以 Room 會將整個操作過程放在后臺線程中完成。因此,該掛起函數(shù)是主線程安全的,也就是在主線程可以放心調(diào)用而不必?fù)?dān)心阻塞主線程。

@Insert
suspend fun insert(word: Word)

在底層 Room 生成了 Dao 抽象函數(shù)的實現(xiàn)代碼。下面代碼片段就是我們的數(shù)據(jù)插入方法的具體實現(xiàn):

/* 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() 函數(shù)被調(diào)用,里面包含三個參數(shù): 數(shù)據(jù)庫、一個用于表示是否正處于事務(wù)中的標(biāo)識、一個 Callable 對象。Callable.call() 包含處理數(shù)據(jù)庫插入數(shù)據(jù)操作的代碼。

如果我們看一下 CoroutinesRoom.execute() 的 實現(xiàn),我們會看到 Room 將 callable.call() 移動到另外一個 CoroutineContext。該對象來自構(gòu)建數(shù)據(jù)庫時您所提供的執(zhí)行器,或者默認(rèn)使用 Architecture Components IO Executor。

查詢數(shù)據(jù)

為了能夠查詢表數(shù)據(jù),我們這里創(chuàng)建一個抽象函數(shù),并且為其添加 @Query 注解,注解后緊跟 SQL 請求語句: 該語句從單詞數(shù)據(jù)表中請求全部單詞,并且以字母順序排序。

我們希望當(dāng)數(shù)據(jù)庫中的數(shù)據(jù)發(fā)生改變的時候,能夠得到相應(yīng)的通知,所以我們返回一個 Flow<List<Word>>。由于返回類型是 Flow,Room 會在后臺線程中執(zhí)行數(shù)據(jù)請求。

@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();
    }
  });
}

我們可以看到代碼里調(diào)用了 CoroutinesRoom.createFlow(),它包含四個參數(shù): 數(shù)據(jù)庫、一個用于標(biāo)識我們是否正處于事務(wù)中的變量、一個需要監(jiān)聽的數(shù)據(jù)庫表的列表 (在本例中列表里只有 word_table) 以及一個 Callable 對象。Callable.call() 包含需要被觸發(fā)的查詢的實現(xiàn)代碼。

如果我們看一下 CoroutinesRoom.createFlow() 的 實現(xiàn)代碼,會發(fā)現(xiàn)這里同數(shù)據(jù)請求調(diào)用一樣使用了不同的 CoroutineContext。同數(shù)據(jù)插入調(diào)用一樣,這里的分發(fā)器來自構(gòu)建數(shù)據(jù)庫時您所提供的執(zhí)行器,或者來自默認(rèn)使用的 Architecture Components IO 執(zhí)行器。

創(chuàng)建數(shù)據(jù)庫

我們已經(jīng)定義了存儲在數(shù)據(jù)庫中的數(shù)據(jù)以及如何訪問他們,現(xiàn)在我們來定義數(shù)據(jù)庫。要創(chuàng)建數(shù)據(jù)庫,我們需要創(chuàng)建一個抽象類,它繼承自 RoomDatabase,并且添加 @Database 注解。將 Word 作為需要存儲的實體元素傳入,數(shù)值 1 作為數(shù)據(jù)庫版本。

我們還會定義一個抽象方法,該方法返回一個 WordDao 對象。所有這些都是抽象類型的,因為 Room 會幫我們生成所有的實現(xiàn)代碼。就像這里,有很多邏輯代碼無需我們親自實現(xiàn)。

最后一步就是構(gòu)建數(shù)據(jù)庫。我們希望能夠確保不會有多個同時打開的數(shù)據(jù)庫實例,而且還需要應(yīng)用的上下文來初始化數(shù)據(jù)庫。一種實現(xiàn)方法是在類中添加伴生對象,并且在其中定義一個 RoomDatabase 實例,然后在類中添加 getDatabase 函數(shù)來構(gòu)建數(shù)據(jù)庫。如果我們希望 Room 查詢不是在 Room 自身創(chuàng)建的 IO Executor 中執(zhí)行,而是在另外的 Executor 中執(zhí)行,我們需要通過調(diào)用 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

為了測試 Dao,我們需要實現(xiàn) AndroidJUnit 測試來讓 Room 在設(shè)備上創(chuàng)建 SQLite 數(shù)據(jù)庫。

當(dāng)實現(xiàn) Dao 測試的時候,在每個測試運行之前,我們創(chuàng)建數(shù)據(jù)庫。當(dāng)每個測試運行后,我們關(guān)閉數(shù)據(jù)庫。由于我們并不需要在設(shè)備上存儲數(shù)據(jù),當(dāng)創(chuàng)建數(shù)據(jù)庫的時候,我們可以使用內(nèi)存數(shù)據(jù)庫。也因為這僅僅是個測試,我們可以在主線程中運行請求。

/* 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()
      // 由于當(dāng)進程結(jié)束的時候會清除這里的數(shù)據(jù),所以使用內(nèi)存數(shù)據(jù)庫
      db = Room.inMemoryDatabaseBuilder(context, WordRoomDatabase::class.java)
          // 可以在主線程中發(fā)起請求,僅用于測試。
          .allowMainThreadQueries()
          .build()
      wordDao = db.wordDao()
  }

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

要測試單詞是否能夠被正確添加到數(shù)據(jù)庫,我們會創(chuàng)建一個 Word 實例,然后插入數(shù)據(jù)庫,然后按照字母順序找到單詞列表中的第一個,然后確保它和我們創(chuàng)建的單詞是一致的。由于我們調(diào)用的是掛起函數(shù),所以我們會在 runBlocking 代碼塊中運行測試。因為這里僅僅是測試,所以我們無需關(guān)心測試過程是否會阻塞測試線程。

/* 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 提供了非常多的功能性和靈活性,遠(yuǎn)遠(yuǎn)超出本文所涵蓋的范圍。比如您可以指定 Room 如何處理數(shù)據(jù)庫沖突、可以通過創(chuàng)建 TypeConverters 存儲原生 SQLite 無法存儲的數(shù)據(jù)類型 (比如 Date 類型)、可以使用 JOIN 以及其它 SQL 功能實現(xiàn)復(fù)雜的查詢、創(chuàng)建數(shù)據(jù)庫視圖、預(yù)填充數(shù)據(jù)庫以及當(dāng)數(shù)據(jù)庫被創(chuàng)建或打開的時候觸發(fā)特定動作。

以上就是如何在Room中使用Kotlin API,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學(xué)到更多知識。更多詳情敬請關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI