安卓& Kotlin Tutorials

房间DB:高级数据持久性

本教程介绍了与房间持久性库一起使用的更高级概念,例如迁移和索引。

4.5/5 4个评分

版本

  • kotlin. 1.2,Android 8.1,Android Studio 3

使用 房间 谷歌的持久性库允许您添加 关系 坚持不懈的应用程序,无需编写大量的样板代码。但是,一旦您在您的应用程序中,您可能需要执行与其他记录的地图关系一样,更新数据模式并优化查询。

使用Android的时,本教程将您介绍更高级的概念 房间 数据库。如果你没有经历过 数据持久性与房间 教程,你应该先在那边掌握,以熟悉基本用途 房间 .

在本教程中,您将添加功能 列表硕士 应用程序,同时学习以下内容:

  • 迁移 更改现有数据存储。
  • 外钥匙 映射实体之间的关系。
  • 索引 让您的查询更快。

是时候开始了!

笔记 :本教程假设您有一些经验开发Android应用程序。要记住几点:

  • 你用了 安卓RecyclerView 显示列表。如果你从未使用过他们,或者你需要一种进修,那么 安卓Recyclerview教程与Kotlin 是一个很棒的地方。
  • 本教程利用 数据绑定绑定适配器。再次,如果你从未使用过这些,或者你需要进修,你应该看看 数据绑定 来自Android项目页面的文档,或查看我们的文件 在Android上的MVVM course.
  • 本教程中的代码片段不包含所需的导入语句。使用关键组合 选项+ Return. 在Mac - 或 alt +进入 在PC上 - 通过项目工作时解决任何缺少的依赖项。

入门

首先使用此教程下载材料 下划线咒语 本教程顶部或底部的按钮。解压缩文件并开始 安卓Studio 3.2.1 or later.

在里面 欢迎来到Android Studio 对话框,选择 进口项目(Eclipse Adt,Gradle等):

欢迎来到Android Studio

选择 listmaster. Starter项目的目录并点击 打开 :

进口项目

如果您看到要更新项目的Gradle Plugin的消息,因为您使用的是稍后版本的Android Studio,请选择 更新 .

查看项目 列表硕士 应用程序,您将看到两个包用于列表类别和列表项。

构建并运行应用程序,您的应用程序将允许您单击 + 按钮。添加A. 分类名称 并在列表中看到它:

类别

创建迁移

虽然具有类别是一个很好的开始,但如果每个人都有一个项目,您的类别将更有用。唯一的问题是您已经发布了一个应用程序版本。当你运行你的时候 房间 - 首次启用应用程序,它会使用其表和属性生成数据库和架构,并将其保存到应用程序存储。不幸的是,大多数情况都是如此 orm. S,表生成机制没有能够将当前数据库更新为新结构。

处理情况的一种方法可以是删除旧数据库,包括其数据,并拥有 房间 生成新数据库。不幸的是,您的用户已开始保存 类别 应用程序中的数据,如果必须重新输入他们的列表,可能不会很高兴 类别。你可以使用一个 移民 在保留用户数据时更新数据结构。

就像在冬季迁移到较温暖的潮流的鸟类一样,您的数据库可以在添加,删除和完善数据结构时迁移到您的应用程序的更好位置。

在开始创建迁移之前,有一个导出的版本很重要 数据库架构 在改变之前。这允许您测试迁移并确保正常工作。要在新项目中启用,它需要更改您的应用程序级别 build.gradle. 文件。你的初学项目已经有了这个。

在你的项目中,打开 build.gradle. 文件归档 (模块:应用程序) annotation:

build.gradle.文件

您将看到一个如下所示的部分:

android {
  ...
  defaultConfig {
      ...
      javaCompileOptions {
          annotationProcessorOptions {
              arguments = ["room.schemaLocation":
                           "$projectDir/schemas".toString()]
          }
      }
  }
}

接下来,通过选择来编译应用程序 建造 菜单选项,然后是 Reduild项目。现在将存储当前模式的版本 app / schemas / com.raywenderlich.listmater.appdatabase 项目文件夹。在Android Studio Project View中,它在您的资产包下显示:

当前的架构

现在您拥有第一个版本的数据库保存,是时候设置新列表项和迁移了。这 listcategory. 对象将与您的一对多关系进行一对多关系 项目清单 对象。当你完成时,这种关系将如下所示:

关系图

这是指各自 listcategory. 将有多个 项目清单 对象和每一个 项目清单 只会与一个相关联 listcategory. 目的。 项目清单 将有以下字段:

  • 商品描述:列表项的内容。
  • Itempriority.:该项目的优先事项有助于组织物品。
  • listcategoryid.:对项目与关系相关联的类别的唯一ID的引用。
  • ID :数据库中记录的唯一ID。

当你打开时 项目清单 底部的对象 项目清单 包,您将看到包含所有这些字段的数据类。

用以下内容替换它:

@Entity(
    tableName = "list_items",
    foreignKeys = [ForeignKey(
        entity = ListCategory::class,
        parentColumns = [" ID "],
        childColumns = ["list_category_id"],
        onDelete = CASCADE)])
data class ListItem(
  @ColumnInfo(name = " 物品 _description") var itemDescription: String,
  @ColumnInfo(name = " 物品 _priority") var itemPriority: Int,
  @ColumnInfo(name = "list_category_id") var listCategoryId: Long,
  @ColumnInfo(name = " ID ") @PrimaryKey(autoGenerate = true) var id: Long = 0)

大多数新注释与您的内容类似 listcategory. but, in the @Entity annotation, you will notice that you’ve added a 外国人s argument. A 外国人 不是另一个国家的一些秘密地点的关键,而是一个采用以下参数的对象来建立你的对象 一对多 relationship:

  • entity:包含外键的实体。
  • parentColumns:包含密钥的父实体对象的列。
  • childColumns:指定父密钥的当前实体的列,它是子项的。
  • onDelete: You are setting this to CASCADE meaning that if a parent category is deleted, all of the children will be as well. It’s similar to going back in time and changing events so that the parent was never born and, as a result, the children would never be born because of the change in the space-time continuum. :]

现在,您将需要创建一个 为了 项目清单 实体。要做到这一点,请右键单击 项目清单 包裹。选择 新▸kotlin文件/类。接下来,命名它 项目清单dao. 并按 好的 。然后粘贴以下内容:

@Dao
interface ListItemDao {

  @Query("SELECT * FROM list_items")
  fun getAll(): LiveData<List<ListItem>>

  @Query("SELECT * FROM list_items WHERE list_category_id = :listCategoryId")
  fun getAllByListCategoryId(listCategoryId: Long): LiveData<List<ListItem>>

  @Insert
  fun insertAll(vararg listItems: ListItem)
}

getAll()insertAll() queries are performed the same way that they are for the listcategory. 对象在 数据持久性与房间 教程,添加了一个 LiveData. 对象作为返回值。

笔记 :如果你是新的 Android建筑组件,你可能会想知道这一点 LiveData. 对象是。简短的答案:它是执行多个查询任务中的替代方案 背景螺纹。你会在这里看到它的行动,但是,对于更深的潜水,你可以看到教程 Android架构组件:入门.

For the getAllByListCategory() query, there is a parameter named listcategoryid. 并参考它 SQL. 与A. : 附加到它的前面。您正在使用此将参数传递给 SQL. 指挥在 方法。您添加要传递给函数定义的参数,然后在其中引用 SQL. 通过附加 : to it.

现在,现在是时候通过创造迁移翻转你的翅膀了。为此,添加一个 迁移 通过右键单击您的包装 com.raywenderlich.listmaster. 包,选择 新▸套餐。接下来,输入 迁移 对于包名称,然后按 好的 。然后,右键单击您的 迁移 包,选择 新▸kotlin文件/类。给它一个名字 迁移1to2. 并按 好的 。当文件打开时,请粘贴以下内容:

@VisibleForTesting
class Migration1To2 : Migration(1, 2) {

  override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL("CREATE TABLE IF NOT EXISTS list_items" +
        "('item_description' TEXT NOT NULL, 'item_priority' INTEGER NOT NULL," +
        "'list_category_id' INTEGER NOT NULL, 'id' INTEGER NOT NULL, PRIMARY KEY(id)," +
        "FOREIGN KEY('list_category_id') REFERENCES list_categories('id') ON DELETE CASCADE)")
  }
}

这个迁移做了两件事:

  1. 它延伸了 移民 类在您正在迁移的数据库版本中, 1以及您迁移到的数据库的版本, 2.
  2. 它覆盖迁移方法并执行SQL命令以创建属于您的表 项目清单 class.

笔记: You may have noticed a @VisibleForTesting annotation. As the name implies, this tells the compiler to make this component visible to your tests.

最后,打开 appdatabase. 文件并将类更新为以下内容:

//1
@Database(entities = [ListCategory::class, ListItem::class], version = 2)
abstract class AppDatabase : RoomDatabase() {

  abstract fun listCategoryDao(): ListCategoryDao
  //2
  abstract fun listItemDao(): ListItemDao

  companion object {
    //3
    @VisibleForTesting
    val MIGRATION_1_TO_2 = Migration1To2()
  }
}

你更新了 appdatabase. 通过添加以下功能:

  1. Increased the version of your database to 2 and added 项目清单::class to the array of entities.
  2. Added a reference to the 项目清单dao..
  3. 暴露了对迁移方法进行测试的引用。

测试您的迁移

既然您有更新数据库所需的代码,您将需要测试迁移。为此,您可以添加一些代码和 日志 对您的活动的陈述。但更好的方式是写一个 浓咖啡 test.

创建浓缩咖啡测试

要创建浓缩咖啡测试,请右键单击 (Androidtest) 版本的 listmaster. 包裹。选择 新▸kotlin文件/类。说出它 项目清单migrationtest. 并按 好的 :

新的kotlin文件

在打开文件时,请添加以下内容:

@RunWith(AndroidJUnit4::class)
class listitemmigrationtest. {

  private val TEST_DB_NAME = "移民_test"

  private lateinit var database: SupportSQLiteDatabase

  //1
  @Rule
  @JvmField
  val migrationTestHelperRule = MigrationTestHelper(
      InstrumentationRegistry.getInstrumentation(),
      "com.raywenderlich.listmaster..AppDatabase",
      FrameworkSQLiteOpenHelperFactory())

  //2
  @Before
  fun setup(){
    database = migrationTestHelperRule.createDatabase(TEST_DB_NAME, 1)
    database.execSQL("INSERT INTO list_categories (id, category_name) VALUES" +
        " (1, 'Purr编程用品'), (2, '犬编码用品')")
  }

  //3
  @After
  fun teardown(){
    database.execSQL("DROP TABLE IF EXISTS list_categories")
    database.execSQL("DROP TABLE IF EXISTS list_items")
    database.close()
  }
}

测试类中的代码具有以下功能:

  1. 创建规则,初始化浓缩咖啡测试 appdatabase..
  2. 使用数据创建数据库的第1版本,并在每个测试之前运行迁移。
  3. 每次测试后从数据库中删除所有表。

在测试设置中,SQL语句用于将测试数据插入数据库的版本1中。这是因为新的 骗子 对于版本2,不可用,直到执行所有迁移。

作为测试迁移的一部分,您需要获取已迁移的数据库版本。要使测试更可读,请将以下辅助方法粘贴到您的 项目清单migrationtest. class.

private fun getMigratedRoomDatabase(): AppDatabase {
  //1
  val appDatabase = Room.databaseBuilder(
      InstrumentationRegistry.getTargetContext(),
      AppDatabase::class.java, TEST_DB_NAME)
      //2
      .addMigrations(AppDatabase.MIGRATION_1_TO_2)
      //3
      .build()
  //4
  migrationTestHelperRule.closeWhenFinished(appDatabase)
  return appDatabase
}

分解此方法的部分:

  1. 调用房间数据库构建器,通过测试数据库的名称。
  2. 将迁移添加到构建器。
  3. 构建数据库。
  4. 完成后,讲述测试规则关闭数据库。

Livepata浓缩咖啡测试

当您在另一个线程中运行的测试代码时,常见问题就是了解如何在进行时等待线程完成线程完成的测试 断言 结果。

如果是 LiveData.,查询通常运行 背景螺纹,你附上了一个 观察者 处理检索到的值。要在测试中解决这个问题,项目包括一个小型 kotlin. Extension. called blockingObserve in testextensions.kt.。此文件位于root下 listmaster. 包裹在 (Androidtest) 项目的一部分。

打开它,您将看到以下内容:

fun <T> LiveData<T>.blockingObserve(): T? {
  var value: T? = null
  val latch = CountDownLatch(1)
  val innerObserver = Observer<T> {
    value = it
    latch.countDown()
  }
  observeForever(innerObserver)
  latch.await(2, TimeUnit.SECONDS)
  return value
}

它增加了观察者 LiveData. 对象和块,直到返回值,以便在从中返回值之前未完成测试 数据库。

笔记 :如果这是你第一次合作 延期 , 这 kotlin文档 是一个伟大的地方,以了解他们的工作方式。

既然已经建立了测试的脚手架,现在是时候创建一个测试来验证迁移工作。粘贴以下方法 项目清单migrationtest.:

@Test
fun migrating_from_1_to_2_retains_version_1_data() {
  val listCategories =
      getMigratedRoomDatabase().listCategoryDao().getAll().blockingObserve()
  assertEquals(2, listCategories!!.size)
  assertEquals("Purr编程用品",
      listCategories.first().categoryName)
  assertEquals(1, listCategories.first().id)
  assertEquals("犬编码用品",
      listCategories.last().categoryName)
  assertEquals(2, listCategories.last().id)
}

Reading the 断言 Equals statements, you will see verifications for the following things in the list_categories. 表使用它 :

  • 共有两个记录。
  • 第一个记录是 Purr编程用品 with an ID of 1.
  • 第二次记录是 犬编码用品 with an ID of 2.

现在,通过右键单击您的测试来运行测试 项目清单migrationtest. 文件和单击 :

运行测试

您需要选择一个设备或仿真器以运行浓缩咖啡测试。

验证结果是“绿色”(通过):

通过测试

接下来,查看您的资产目录,您将看到调用数据库2版的架构文件 2.json:
架构版本2文件

笔记 :您应该在项目中版本这些文件,以便您可以在增加版本时测试迁移。

既然您在测试槽中,您将在引用现有类别时将记录插入新表中。为此,将以下方法粘贴到您的 项目清单migrationtest. class:

@Test
fun inserting_a_record_into_list_items_after_migrating_from_1_to_2_succeeds() {
  val listCategories =
      getMigratedRoomDatabase().listCategoryDao().getAll().blockingObserve()
  // insert a record in the new table
  val listItemDao = getMigratedRoomDatabase().listItemDao()
  val purrProgrammingListItem = ListItem("桌子垫子", 1,
      listCategories!!.first().id)
  listItemDao.insertAll(purrProgrammingListItem)

  // validate that a record can be added to the new table
  val purrProgrammingList = listItemDao.getAll().blockingObserve()
  assertEquals(1, purrProgrammingList!!.size)
  val firstPurrProgrammingItem = purrProgrammingList.first()
  assertEquals("桌子垫子", firstPurrProgrammingItem.itemDescription)
  assertEquals(1, firstPurrProgrammingItem.itemPriority)
  assertEquals(listCategories.first().id,
      firstPurrProgrammingItem.listCategoryId)
  assertEquals(1, firstPurrProgrammingItem.id)
}

此测试执行以下实体 骗子 :

  • 以优先级为1和项目描述插入记录 桌子垫子 与之相关 Purr编程用品 category.
  • 检查只有一个记录是否实际插入。
  • 验证持久的值是否与我们添加的内容匹配。

现在,通过右键单击您的测试来运行测试 项目清单migrationtest. 文件和单击 。所有测试都应该是绿色的。

应用程序初始化时迁移

伟大的!你有信心你的迁移代码工作。现在,您需要在应用程序中运行此迁移。由于目标是让您的应用更新数据库,因此您将需要它在用户首次打开应用程序时执行它。要做到这一点,打开 listmaster.Application class and replace the onCreate() method with the following:

override fun onCreate() {
  super.onCreate()
  listmasterApplication.database = Room.databaseBuilder(
      this,
      AppDatabase::class.java,
      "list-master-db")
      .addMigrations(AppDatabase.MIGRATION_1_TO_2)
      .build()
}

addmigrations() 呼叫执行以下操作:

  • 检查迁移是否已应用于数据库。如果没有,它运行迁移。
  • 如果已将迁移应用于数据库,则它将无效。

在用户界面中接线

现在你有你的 项目清单 挂在数据库中,是时候将其加入界面时。

要开始,打开 listcategory.Viviewher class and add the following lines below the existing code inside the setListCategoryItem(listCategory: ListCategory) method:

holderListCategoryBinding.categoryName.rootView.setOnClickListener {
  val intent = Intent(listCategoriesActivity, ListItemsActivity::class.java)
  intent.putExtra(ListItemsActivity.LIST_CATEGORY_ID, listCategory.id)    
  intent.putExtra(ListItemsActivity.CATEGORY_NAME, listCategory.categoryName)
  listCategoriesActivity.startActivity(intent)
}

This adds an OnClickListener to each category in the list. The click listener launches a 项目清单activity.,将其传递它的类别ID和名称。

现在,通过单击运行该应用程序 run▸运行'app' (您可能需要将RUN配置从测试类切换回应用程序)。然后,单击类别,例如 Purr编程用品,您将看到一个如下所示的屏幕:

类别项目屏幕

您的应用程序设置为使用 Android架构组件MVVM 图案。在评估放置数据库的位置时 查询/插入,如果你没有使用 MVVM ,您可能倾向于将这些查询放在您的身上 活动。一部分的力量 MVVM 是能够将该逻辑放入专注于数据访问的组件中。

在您的情况下,您将使用两个组件:

  1. A 存储库 专注于与您的交互的对象 以及任何特定于数据库的东西。
  2. A viewmodel. 那是 生命周期 - 通过延伸 AndroidViewModel..

创建一个 项目清单repository. 通过右键单击 项目清单 包裹。接下来,选择 新▸kotlin文件/类。说出它 项目清单repository. 并按 好的 。最后,请粘贴以下内容:

class ListItemRepository {
  //1
  private val listItemDao = listmasterApplication.database!!.listItemDao()
  
  //2
  fun insertAll(vararg listItems: ListItem) {
    AsyncTask.execute {
      listItemDao.insertAll(*listItems)
    }
  }
} 

你在这课上做两件事:

  1. 参考你的 .
  2. 提供插入功能 listItems. 在背景线程。

接下来,您将创建一个 生命周期-管理 viewmodel. 这将摘要与您的存储库一起使用的详细信息。为此,创建一个新的 kotlin. 班上 项目清单 通过右键单击包名称,选择 新的 接着 kotlin文件/类。说出它 项目清单sviewmodel. 并按 好的 。最后,请粘贴以下内容:

//1
class ListItemsViewModel(application: Application) : AndroidViewModel(application) {

  //2
  private val listItemRepository: ListItemRepository = ListItemRepository()

  //3
  fun insertAll(vararg listItems: ListItem) {
    listItemRepository.insertAll(*listItems)
  }
}

这对你做了一些事情:

  1. 延伸 AndroidViewModel. 和 takes a reference to the application in its constructor.
  2. 创建存储库的实例并保持引用。
  3. Exposes an insertAll() method from your repository.

笔记 :如果你是新的 查看模型, 退房 本教程 学习关于 MVVM 如何 查看模型 适合模式,以及我们的课程 在Android上的MVVM.

现在你要做一些工作 项目清单sactivity.。要启动,请打开它并为您添加一个属性 项目清单sviewmodel. above onCreate():

private lateinit var listItemsViewModel: ListItemsViewModel

Inside onCreate(), add the following line right above the call to setupAddButton():

listItemsViewModel =
    ViewModelProviders.of(this).get(ListItemsViewModel::class.java)

This line of code initializes listItems.ViewModel by calling viewmodel.Providers. 得到你的一个例子 项目清单sviewmodel..

Next, replace the setupAddButton() method with the following:

private fun setupAddButton() {
  activityListItemsBinding.fab.setOnClickListener {

    // Setup the dialog
    val alertDialogBuilder = AlertDialog.Builder(this).setTitle("Title")
    val dialogAddItemBinding = DialogAddItemBinding.inflate(layoutInflater)
    // 1
    val listItemViewModel = ListItemViewModel(ListItem("", 0, listCategory.id))
    dialogAddItemBinding.listItemViewModel = listItemViewModel

    alertDialogBuilder.setView(dialogAddItemBinding.root)

    /**
     * Setup the positive and negative buttons.
     * When the user clicks ok, a record is added to the db,
     * the db is queried and the RecyclerView is updated.
     */
    alertDialogBuilder.setPositiveButton(android.R.string.ok)
    { _: DialogInterface, _: Int ->
      // 2
      listItemsViewModel.insertAll(listItemViewModel.listItem)
    }
    alertDialogBuilder.setNegativeButton(android.R.string.cancel, null)
    alertDialogBuilder.show()
  }
}

这挂了起来 + 按下按钮:

  1. 创建一个你的一个例子 项目清单viewModel. 并将其绑定到对话框中。注意 项目清单viewModel. 不同于 项目清单sviewmodel..
  2. Calls insertAll() on 项目清单sviewmodel. 当用户点击时 好的 .

通过选择来运行应用程序 run▸运行'app' on the menu:

运行该应用程序

如果您单击一个类别,例如 Purr编程用品,然后,您可以单击 + 按钮,添加一个 物品 优先, 点击 好的 ,它将带您回到未显示任何内容的列表。此时,您的第一个响应可能是:

和数据一样

那是因为你没有联系你的 LiveData. 对象查看这些项目,所以时间解决这个问题!开始开放 项目清单repository. 和 add a new method named getAllByListCategoryId():

fun getAllByListCategoryId(listCategoryId: Long): LiveData<List<ListItem>> {
  return listItemDao.getAllByListCategoryId(listCategoryId)
}

This method takes a listcategoryid. 和 returns a LiveData. object from listItemDao. Next, open 项目清单sviewmodel. 和 add a method also named getAllByListCategoryId() with different implementation:

fun getAllByListCategoryId(listCategoryId: Long): LiveData<List<ListItem>> {
  return listItemRepository.getAllByListCategoryId(listCategoryId)
}

This method takes same listcategoryid. but returns a LiveData. object from your listItemRepository.

现在,打开你的 项目清单sactivity. 并粘贴以下方法:

private fun setupRecyclerAdapter() {
  val recyclerViewLinearLayoutManager = LinearLayoutManager(this)

  contentListItemsBinding = activityListItemsBinding.listItemsViewInclude!!
  contentListItemsBinding.listItemRecyclerView.layoutManager =
      recyclerViewLinearLayoutManager
  listItemAdapter = ListItemAdapter(listOf(), this)
  listItemsViewModel.getAllByListCategoryId(listCategory.id).observe(
      this, Observer { listItems: List<ListItem>? ->
    listItems?.let {
      listItemAdapter.itemList = it
      listItemAdapter.notifyDataSetChanged()
    }
  })
  contentListItemsBinding.listItemRecyclerView.adapter = listItemAdapter
}

这会让你的 recyclerview. 在你的观察者中放置观察者 LiveData. object returned by getAllByListCategoryId() to update the recyclerview. 什么时候 项目清单 对象被添加到类别中。

Now, it’s time to add a call to your setupRecyclerAdapter() method at the end of onCreate:

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  ...

  setupRecyclerAdapter()
}

是时候点击再次运行应用程序了 选项并单击 运行'app'。您现在应该看到您之前添加的项目。

点击时 Purr编程用品,你会看到的 猫咬 你以前添加了。现在,点击 + 按钮,输入 猫床 优先 2 , 轻敲 好的 您将在您的列表中看到它:

物品清单

索引

当数据库表开始获取大量记录时,查询通常可以开始减速。要使用SQLite缓解,您可以添加 指数 对字段您经常查询以加快这些查询。在引擎盖下,数据库会使您在数据结构中索引的字段的副本,该字段是更有效的查询。

笔记 :看看这个 维基百科 文章了解更多信息 索引.

In your app, there can be several list_category records, and each list_category can have multiple list_item records. More importantly, you are regularly performing queries on the list_category_id field to query for them by for selected list_category IDs. Because of that, you are going to add an index to this field.

To add an index, start by replacing the @Entity annotation of the 项目清单 具有以下内容的实体:

@Entity(
    tableName = "list_items",
    foreignKeys = [ForeignKey(
        entity = ListCategory::class,
        parentColumns = [" ID "],
        childColumns = ["list_category_id"],
        onDelete = CASCADE)],
    indices = [Index(value = ["list_category_id"],
        name = " 指数 _list_category_id")])

Here, you’ve added an indices property to the entity that is passing in an array of 指数 对象。在那个阵列中,你就是创建 指数 with two fields:

  • 价值 :您要索引的哪个字段。
  • 姓名:索引的唯一名称。

笔记 :索引名称用于迁移等事物。执行查询时,您仍然在添加索引之前根据您的字段查询。

既然您拥有索引,您将需要为具有以前版本的架构的用户创建迁移。为此,创建一个名为 迁移2.to3. 在迁移包和粘贴如下:

@VisibleForTesting
class Migration2To3 : Migration(2, 3) {

  override fun migrate(database: SupportSQLiteDatabase) {
    database.execSQL(
        "CREATE INDEX 'index_list_category_id' ON list_items('list_category_id')")
  }
}

接下来,更换你的 appdatabase. with the following:

//1
@Database(entities = [ListCategory::class, ListItem::class], version = 3)
abstract class AppDatabase : RoomDatabase() {

  abstract fun listCategoryDao(): ListCategoryDao

  abstract fun listItemDao(): ListItemDao

  companion object {

    @VisibleForTesting
    val MIGRATION_1_TO_2 = Migration1To2()
    //2
    @VisibleForTesting
    val MIGRATION_2_TO_3 = Migration2To3()
  }
}

在这方面,你做了两件事:

  1. 将架构的版本递增2到3。
  2. 添加了对新迁移的引用。

Now, you need to tell your app database builder to use the new migration by replacing the onCreate() method in listmaster.Application with:

  override fun onCreate() {
    super.onCreate()
    listmasterApplication.database = Room.databaseBuilder(
        this,
        AppDatabase::class.java,
        "list-master-db")
        .addMigrations(AppDatabase.MIGRATION_1_TO_2)
        .addMigrations(AppDatabase.MIGRATION_2_TO_3)
        .build()
  }

要确保它仍然在索引后仍然运行正确的应用程序。

索引缺点

虽然索引可能非常有帮助,但有一些缺点要了解。一个大缺点源于向另一个数据结构添加记录的需要,这意味着插入一个具有索引的表中可能需要更长时间的执行。另一个缺点是索引增加了数据库所需的存储量。

读取或插入

常见查询字段索引可能是有益的一些方案:

  • 您通常比写入桌面读取数据。
  • 查询速度比插入速度更重要。

更新测试

由于您对代码进行了更改,您可能希望重新运行您的单元测试以确保您没有损坏任何内容。运行它们,您将看到以下内容:

失败的测试

哎呀!这发生了,因为您实例化了您自己的迁移版本 房间 database in your getMigratedRoomDatabase() method in 项目清单migrationtest.。目前,它只将数据库迁移到版本 2 , 但 房间 除非您的数据库迁移到当前版本,否则将无法使用,这是 3.

To fix this, add .addMigrations(AppDatabase.MIGRATION_2_TO_3) after the .addMigrations(AppDatabase.MIGRATION_1_TO_2) in getMigratedRoomDatabase(). Your method will now look like this:

  private fun getMigratedRoomDatabase(): AppDatabase {
    //1
    val appDatabase = Room.databaseBuilder(
        InstrumentationRegistry.getTargetContext(),
        AppDatabase::class.java, TEST_DB_NAME)
        //2
        .addMigrations(AppDatabase.MIGRATION_1_TO_2)
        .addMigrations(AppDatabase.MIGRATION_2_TO_3)
        //3
        .build()
    //4
    migrationTestHelperRule.closeWhenFinished(appDatabase)
    return appDatabase
  }

由于您的测试现在正在测试版本 3 你的迁移而不是版本 2,重命名下面的现有测试,

@Test
fun migrating_from_1_to_2_retains_version_1_data() {
  ...
}

@Test
fun inserting_a_record_into_list_items_after_migrating_from_1_to_2_succeeds() {
  ...
}

到:

@Test
fun migrating_from_1_to_3_retains_version_1_data() {
  ...
}

@Test
fun inserting_a_record_into_list_items_after_migrating_from_1_to_3_succeeds() {
  ...
}

由于索引不会影响数据的结构或您访问它的结构,因此您不需要更改任何其他代码。

现在,运行您的测试,所有这些都会通过!

通过测试

然后去哪儿?

您可以使用使用的最终项目 下载材料 本教程顶部或底部的按钮。

当您开始使用更复杂的数据时,可以使用 类型转换器 将数据库类型映射到您自己的自定义类型。如果您对Android上的数据持久性是新的,并且想要更多的背景,您可以查看 在Android上保存数据 视频课程,涵盖 共享偏好,保存到文件,sqlite和迁移。

作为挑战,您也可以尝试:

  • 添加删除列表项的功能。
  • 创建一个 onclick. 每个列表项的事件允许您编辑项目并将更新保存到数据库。

随意分享您的反馈或调查结果,请在下面的评论部分或论坛中发布您的问题。我希望你在高级数据持久性上享受了这个教程!

平均评级

4.5/5

为此内容添加评级

4 ratings

更像这样的

贡献者

评论