Room Kotlin API的使用入门教程

Room 是 SQLite 的封装,它使 Android 对数据库的操作变得非常简单,也是迄今为止我最喜欢的 Jetpack 库。在本文中我会告诉大家如何使用并且测试 Room Kotlin API,同时在介绍过程中,我也会为大家分享其工作原理。

我们将基于 Room with a view codelab 为大家讲解。这里我们会创建一个存储在数据库的词汇表,然后将它们显示到屏幕上,同时用户还可以向列表中添加单词。

定义数据库表

在我们的数据库中仅有一个表,就是保存词汇的表。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

为了测试 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 官方文档,如果想通过实践学习,可以访问 Room with a view codelab

以上就是Room Kotlin API使用入门教程的详细内容,更多关于Room Kotlin API使用的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android架构组件Room指南

    一.简介 Room是Google推出的Android架构组件库中的数据持久化组件库, 也可以说是在SQLite上实现的一套ORM解决方案. Room主要包含三个部分: Database : 持有DB和DAO Entity : 定义POJO类,即数据表结构 DAO(Data Access Objects) : 定义访问数据(增删改查)的接口 其关系如下图所示: Room Architecture Diagram 二.基本使用 1. 创建Entity 1.1 一个简单的Entitiy 一个简单Ent

  • JavaScript实现的图片3D展示空间(3DRoom)

    程序就是模拟这样一个三维空间,里面的图片会根据三维坐标显示在这个空间.很久以前就看过一个3DRoom效果,是用复杂的计算实现的.在上一篇图片变换研究过css3的transform之后,就想到一个更简单的方法来实现.兼容:ie6/7/8, firefox 3.6.8, opera 10.6, safari 5.0.1, chrome 5.0 效果预览 3DRoom程序说明 [实现原理] 3D效果的关键,是深度的实现.把3D容器看成一个由多个不同深度的层组成的空间,这些层的尺寸默认跟容器一样.层里面

  • Android Room数据库多表查询的使用实例

    Android-Room数据库(介绍) 前言 在SQLite数据库中,我们可以指定对象之间的关系,因此我们可以将一个或多个对象与一个或多个其他对象绑定.这就是所谓的一对多和多对多的关系. 既然要多表查询,所以表之间就得有关联.这时候我们就得使用新的注解符@ForeignKey 接下来的内容,就需要上节的内容了 @Entity public class Company { @PrimaryKey(autoGenerate = true) private int id; private String

  • Android kotlin+协程+Room数据库的简单使用

    Room Room是Google为了简化旧版的SQLite操作专门提供的 1.拥有了SQLite的所有操作功能 2.使用简单(类似于Retrofit),通过注解的方式实现相关功能.编译时自动生成实现类impl 3.LiveData,LifeCycle,Paging天然融合支持 导入 ... plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-android-extensions' id 'kotlin-kap

  • 详细介绍Android-Room数据库的使用

    前言 Google终于发布了一个和SQLite相关的库了.之前一直都是在SQLite.XUtils.greenDao.Realm这些数据库之间来回折腾.现在终于有一个更"正统"数据库了. Room是什么? Room是一个持久性数据库. Room持久性库提供了SQLite的抽象层,以便在充分利用SQLite的同时允许流畅的数据库访问. 为什么会选择Room? 前面我也说到了现在也有不少开源的数据库给大家使用,那为什么我们还要去学习使用这个库呢?当然不是我前面说的"正不正统&qu

  • Android架构组件Room的使用详解

    Room其实就是一个orm,抽象了SQLite的使用,但是它作为Android的亲儿子orm,并且原生支持LiveData和Rxjava嵌套使用,学习一下还是不错的. Room有3个主要组件 Database :数据库 Entity : 代表数据库一个表结构 Dao : 包含访问数据库的方法 简单使用 添加Google Maven仓库 allprojects { repositories { jcenter() google() } } 添加依赖 dependencies { // Room i

  • The Saltwater Room 中英文对照歌词

    中文名:深海空间 通称:咸水屋.泪水伤心地 歌手:Owl City 所属专辑:<Maybe I'm Dreaming>(早期版,2008) <Ocean Eyes>(增强版,2009) 早期版歌词 男: I opened my eyes last night and saw you in the low light. 昨夜我睁开眼睛,看见你在昏暗的灯光下. Walking down by the bay, on the shore, staring up at the planes

  • Room Kotlin API的使用入门教程

    Room 是 SQLite 的封装,它使 Android 对数据库的操作变得非常简单,也是迄今为止我最喜欢的 Jetpack 库.在本文中我会告诉大家如何使用并且测试 Room Kotlin API,同时在介绍过程中,我也会为大家分享其工作原理. 我们将基于 Room with a view codelab 为大家讲解.这里我们会创建一个存储在数据库的词汇表,然后将它们显示到屏幕上,同时用户还可以向列表中添加单词. 定义数据库表 在我们的数据库中仅有一个表,就是保存词汇的表.Word 类代表表中

  • Yii2框架制作RESTful风格的API快速入门教程

    先给大家说下什么是REST restful REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移. 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一. 他在论文中提到:"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强.性能好.适宜通信的架构.REST指的是一组架构约束条件和原则." 如

  • android studio 新手入门教程(二)项目的导入教程图解

    上篇文章介绍了AS的一些常用设置方法,当工具调教妥当后,自然就要开始项目的开发啦.从零开始新建一个项目,这个简单,不必多说,这篇博客会分享我从旧平台eclipse导入项目到AS的过程,以及遇到的一些问题并如何解决.开篇先粗略的提一些需要注意的地方. ##结构目录 和eclipse不同,在android 视图下的项目目录分为java,res和manifests. manifests目录存放清单文件,不必多说. java目录会默认生成三个文件夹,其中test为在本机执行单元测试代码的目录, andr

  • ABP(现代ASP.NET样板开发框架)系列之二、ABP入门教程详解

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源项目:https://github.com/aspnetboilerplate ABP 的由来 "DRY--避免重复

  • Vuejs第一篇之入门教程详解(单向绑定、双向绑定、列表渲染、响应函数)

    什么是组件? 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展. 接下来给大家介绍vuejs单向绑定.双向绑定.列表渲染.响应函数基础知识,具体详情如下所示: (一)单向绑定 <div id="app"> {{ message }} </div> <sc

  • Three.js快速入门教程

    引言 本文主要是讲解Three.js的相关概念,帮助读者对Three.js以及相关知识形成比较完整的理解. 近年来web得到了快速的发展.随着HTML5的普及,网页的表现能力越来越强大.网页上已经可以做出很多复杂的动画,精美的效果. 但是,人总是贪的.那么,在此之上还能做什么呢?其中一种就是通过WebGL在网页中绘制高性能的3D图形. OpenGL,WebGL到Three.js OpenGL大概许多人都有所耳闻,它是最常用的跨平台图形库. WebGL是基于OpenGL设计的面向web的图形标准,

  • mybatis原理概述入门教程

    本文我将要介绍一下mybatis的框架原理,以及mybatis的入门程序,实现用户的增删改查,她有什么优缺点以及mybatis和hibernate之间存在着怎么样的关系,希望对小伙伴们有帮助,不足之处,还请多多指教. 什么是mybatis? MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis .2013年11月迁移到Github. MyBatis 是支持定

  • OpenStack云计算快速入门教程(1)之OpenStack及其构成简介

    该教程基于Ubuntu12.04版,它将帮助读者建立起一份OpenStack最小化安装.我是五岳之巅,翻译中多采用意译法,所以个别词与原版有出入,请大家谅解.我不是英语专业,我觉着搞技术最重要的就是理解,而不是四级和考研中那烦人的英译汉,所以我的目标是忠于原意.通俗表达,Over.英文原文在这里(http://docs.openstack.org/es@***/openstack-compute/starter/content/ ,请将ex@***中的@去掉,CU屏蔽的F词),下面步入正题: 第

  • ReactJs快速入门教程(精华版)

    现在最热门的前端框架有AngularJS.React.Bootstrap等.自从接触了ReactJS,ReactJs的虚拟DOM(Virtual DOM)和组件化的开发深深的吸引了我,下面来跟我一起领略ReactJs的风采吧~~ 文章有点长,耐心读完,你会有很大收获哦~   一.ReactJS简介 React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站.做出来以后,发现这套东西

  • python爬虫入门教程--优雅的HTTP库requests(二)

    前言 urllib.urllib2.urllib3.httplib.httplib2 都是和 HTTP 相关的 Python 模块,看名字就觉得很反人类,更糟糕的是这些模块在 Python2 与 Python3 中有很大的差异,如果业务代码要同时兼容 2 和 3,写起来会让人崩溃. 好在,还有一个非常惊艳的 HTTP 库叫 requests,它是 GitHUb 关注数最多的 Python 项目之一,requests 的作者是 Kenneth Reitz 大神. requests 实现了 HTTP

随机推荐