一、前言

使用 Room 库存储应用数据时,通过定义数据访问对象(DAOs)与存储的数据进行交互。每一个 DAO 包含用来访问应用数据库的抽象方法,在编译时, Room 会自动生成并实现在 DAO 中定义的访问方法(如果不了解 Room 库,请先阅读:Android Room 库基础入门)。

用数据访问对象(而不是使用查询构建起或者直接查询)访问数据库,可以拆分数据库架构中的不同组件。另外,数据访问对象也使得应用在测试阶段可以轻松模拟试数据库访问。

二、Room 数据访问对象详解

2.1 定义数据访问对象类

数据访问对象类(DAO)可以是接口,也可以是抽象方法(一般情况下,使用接口定义DAO)。定义数据访问对象类时,必须使用 @Dao 注解标示,数据访问对象类(DAO)没有属性,内部定义一个或者多个与应用的数据库进行交互的方法。如下示例所示:

@Dao
interface UserDao {@Query("SELECT * FROM user")fun getAll(): List<User>@Query("SELECT * FROM user WHERE name LIKE :name")fun findByName(name: String): List<User>@Insertfun insertAll(vararg users: User)@Deletefun delete(user: User)

注意事项:DAO 类可以是接口,也可以是抽象方法,并且 DAO 类不能有成员属性。

2.2 数据访问对象的方法分类

数据访问对象类中定义的与数据库交互的方法可以分为两种类型:

  • 便利类型方法(Convenience methods):便利类型方法可以在不需要编写任何 SQL 语句的情况下,对数据库进行插入、更新和删除行数据。
  • 查询类型方法(Query methods):查询类型方法允许开发者编写自定义的 SQL 查询语句与数据库进行交互。

2.2.1 DAO 便利类型方法

Room 提供便利型的注解来定义便利方法,这些方法不需要编写任何的 SQL 语句就可以执行简单的插入、更新和删除行数据,与之对应的注解分别是 @Insert@Update@Delete

说明:如果需要定义更复杂的插入、更新和删除,或者需要从数据库中查询数据,请使用 DAO 查询型方法

2.2.1.1 插入

使用 @Insert 注解标注定义的便利方法,可以将方法参数传入的对象数据写入到数据库对应的表中。 传入 @Insert 方法的参数必须是用 @Entity 注解标注的 Room 数据实体类,或者它的集合,当调用 @Insert 方法时,Room 就会将传入的每个数据实体类对象数据插入到对应的表中。如下示例所示:

@Dao
interface UserDao {@Insert(onConflict = OnConflictStrategy.REPLACE)fun insertUser(user: User)@Insert(onConflict = OnConflictStrategy.REPLACE)fun insertAll(vararg users: User)
}

如果 @Insert 方法传入参数是单个数据实体类,调用 @Insert 方法时返回新的行id,如果 @Insert 方法传入的参数是数据实体类集合,调用 @Insert 方法时返回插入的行id数组。

注意事项:
1. 传入 @Insert 方法的参数必须是用 @Entity 注解标注的 Room 数据实体类,或者它的集合;
2. @Insert 方法可通过 onConflict 参数指定插入是发生冲突的解决方案(默认值:OnConflictStrategy.ABORT(终止操作,抛出异常))

2.2.1.2 更新

使用 @Update 注解标注定义的便利方法,可以更新数据库表中的指定行的数据。跟 @Insert 类似, 传入 @Update 方法的参数必须是用 @Entity 注解标注的 Room 数据实体类,或者它的集合。如下示例代码所示:

@Dao
interface UserDao {@Updatefun updateUser(user: User)@Updatefun updateAll(vararg users: User)
}

@Update 方法执行完成后可以返回一个表示成功更新行数的整形数值。

注意事项:
1. Room 根据主键将传入的数据实体类实例匹配数据库表中的行,如果传入的数据实体类实例在数据库表中找不到匹配的主键值,Room 对该数据不做任何操作。
2. @Update 方法可通过 onConflict 参数指定插入是发生冲突的解决方案(默认值:OnConflictStrategy.ABORT(终止操作,抛出异常))

2.2.1.3 删除

使用 @Delete 注解标注定义的便利方法,可以删除数据库表中的指定行的数据。跟 @Insert 类似, 传入 @Delete 方法的参数必须是用 @Entity 注解标注的 Room 数据实体类,或者它的集合。如下示例代码所示:

@Dao
interface UserDao {@Deletefun delete(user: User)@Deletefun deleteAll(vararg users: User)
}

@Delete 方法执行完成后可以返回一个表示成功更新行数的整形数值。

注意事项:
1. Room 根据主键将传入的数据实体类实例匹配数据库表中的行,如果传入的数据实体类实例在数据库表中找不到匹配的主键值,Room 对该数据不做任何操作。

2.2.2 DAO 查询型方法

Room 提供 @Query 注解定义查询型方法,查询型方法可以在注解中通过 value 参数自定义 SQL 语句,该方法调用时执行 SQL 语句定义的查询操作,查询型方法可用于查询数据库中的数据,或者执行更复杂的插入、更新和删除操作。查询型方法能够实现什么样的操作,完全看 SQL 语句。

注意事项:Room 会在编译时校验 SQL 查询语句,如果 SQL 查询语句存在问题,在编译期间就会出现错误(编译失败),而不是等到运行时才报错。

2.2.2.1 简单查询操作

一个简单的查询就是查询数据库中所有列的数据。

  • 示例:
@Query("SELECT * FROM users")fun getAll(): List<User>

2.2.2.2 查询包含数据表部分列的数据

在某些情况下,我们只需要查询包含数据库表中某些列的值,而不是返回所有列的值。

  • 示例:
// 这里的数据类不是数据实体类,无需给 Room 解析对应数据表,所以无需添加 @Entity 注解。
// 数据类的属性名如果跟数据库表中列名不一致,需要使用 @ColumnInfo 注解指定对应列名的名称。
data class NameAge(@ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "age") val age: Int )// 在 DAO 中定义查询方法,返回的结果类型必须是根据 SQL 语句中指定列定义的实体类
@Query("SELECT name, age FROM users")
fun findAllWithNameAge(): List<NameAge>

解析:上面的例子中返回只包含数据库表中 nameage 这两个字段值的结果。

注意事项:
1. 必须根据需要查询的列定义一个拥有相同属性的实体类,这个实体类不是数据实体类,无需添加 @Entity 注解。
2. 数据类的属性名如果跟数据库表中列名不一致,需要使用 @ColumnInfo 注解指定对应列名的名称。

2.2.2.3 传入简单参数的查询

如果根据不定的条件查询数据,那么可以将此条件作为 DAO 方法的参数传入,在 @Query 注解中通过 “冒号 + 参数名称” 的形式引用 DAO 方法中的参数。带条件的插叙可以使用 SQL 的 WHERE 语法实现。

  • 示例:
@Query("SELECT * FROM users WHERE name LIKE :name")
fun findByName(name: String): List<User>

注意事项:在 @Query 注解中通过 “冒号 + 参数名称” 的形式引用 DAO 方法中的参数

2.2.2.3 传入集合参数的查询

如果根据不定的条件查询数据,并且查询条件是一个集合类型的条件,同样可以将此集合条件作为 DAO 方法的参数传入,在 @Query 注解中通过 “冒号 + 参数名称” 的形式引用 DAO 方法中的参数。带条件的插叙可以使用 SQL 的 WHERE 语法实现,集合条件使用 SQL de IN 、 语法

  • 示例:
@Query("SELECT * FROM users WHERE name IN (:names)")
fun findByNames(vararg names: String): List<User>

注意事项:在 @Query 注解中通过 “冒号 + 参数名称” 的形式引用 DAO 方法中的参数,in 语句语法需要使用括号将集合包裹起来。

2.2.2.4 跨表查询

有时候我们需要跨表查询,跨表查询使用 SQL 的 JOIN 语法,JOIN 语法的格式是 “JOIN 表名 ON 表间连接条件”。

@Entity(tableName = "users")
data class User(@PrimaryKey val uid: Int, @ColumnInfo(name = "name") val name: String, val age: Int, val tid: Int)@Entity
data class Teacher(@PrimaryKey val tid: Int, @ColumnInfo val name: String) {@ColumnInfo  var subject: String? = null
}@Dao
interface UserDao {@Query("SELECT * FROM users INNER JOIN teacher ON users.tid = teacher.tid WHERE teacher.name like :teacherName")fun findUserByTeacher(teacherName: String): List<User>
}

注意事项:跨表查询是有条件的,那就是这些表之间是有相互关联的(可以通过某个列的值关联起来)。

2.3 编写异步的 DAO 查询

为了防止数据查询操作阻断 UI,造成 UI 卡顿,Room 不允许在 UI 主线程中访问数据库。这就意味着开发者必须将 DAO 中的查询定义成异步的。Room 库包含与多阿哥不同的框架进行集成,提供异步查询支持。

数据库查询可分为三类

  • 一次性写查询:往数据库中插入、更新或者删除数据;
  • 一次性读查询:单次从数据库读取数据,并且返回查询执行时数据库快照对应的结果;
  • 观察者读查询:每次底层数据库表发生变更时,从数据库中读取新数据,并用新数据体现这些变更。

2.3.1 编程语言和框架选项

Room 为与特定编程语言的功能和库的相互操作提供集成支持。下表是基于查询类型和框架返回的适用的类型。

查询类型 Kotlin 语言功能 RxJava框架 Guava语言 Jetpack Lifecycle
一次性写查询 Coroutines(suspend) Single, Maybe, Completable ListenableFuture
一次性读查询 Coroutines(suspend) Single, Maybe ListenableFuture
观察者读查询 Flow Flowable, Publisher, Observable LiveData

2.3.1.1 Kotlin 的 Flow 和协程

Kotlin 提供语言功能允许你无需使用第三方框架实现异步的写查询操作。

  • 在 Room 2.2 及更高版本,你可以使用 Kotlin 的 Flow 功能编写可观察查询。
  • 在 Room 2.1 及更高版本,你可以使用 Kotlin 的协程的 suspend 关键字让你的 DAO 实现异步查询。

注意事项:
1. 在 Room 中使用 Kotlin 的 Flow 或者协程,必须在项目程序模块的 build.gradle 脚本中添加 room-ktx 工件模块。

// optional - Kotlin Extensions and Coroutines support for Roomimplementation("androidx.room:room-ktx:2.3.0")

2. 使用 Kotlin 在 Android 上开发是 JVM 环境,在不使用 Flow 和协程的情况下,依然可以使用 Thread 实现异步,但是不建议这么做,在 Kotlin 中,使用协程更加轻量级。

2.3.1.2 Java 与 RxJava

如果你的应用使用 Java 开发语言,你可以使用 RxJava 框架来编写异步 DAO 方法并返回专门的类型。Room 支持以下 RxJava2 返回类型(Room 2.3 及更高版本支持RxJava3.)。

  • 对于一次性查询,Room 2.1 以及更高的版本支持 CompletableSingle<T>Maybe<T> 返回值类型。
  • 对于观察者查询,Room 支持 Publisher<T>Flowable<T>、 and Observable<T> 返回值类型。

注意事项:在 Room 中使用 RxJava,必须在项目程序模块的 build.gradle 脚本中添加 room-rxjava2 或者 room-rxjava3 工件模块。

// optional - RxJava2 support for Roomimplementation "androidx.room:room-rxjava2:2.3.0")// optional - RxJava3 support for Roomimplementation "androidx.room:room-rxjava3:2.3.0")

2.3.1.3 Java 与 LiveData 和 Guava

如果你的应用程序使用 Java 编程语言,并且你不想使用 RxJava 框架,则可以使用以下替代方案来编写异步查询:

  • 使用 Jetpack 的 LiveData 包装类来编写异步观察者查询。
  • 使用 Guava 的 ListenableFuture<T> 包装类来编写异步一次性查询。

注意事项:在 Room 中使用 Guava,必须在项目程序模块的 build.gradle 脚本中添加 room-guava 工件模块。

// optional - Guava support for Room, including Optional and ListenableFutureimplementation "androidx.room:room-guava:2.3.0")

2.3.2 编写异步一次性查询

笔者注:笔者使用 Kotlin 语言,这里就使用 Kotlin 的 Flow 和协程进行演示,读者有兴趣的话可以自己使用RxJava 和 Guava 尝试。

一次性查询是只运行一次,并且在运行时获取数据当前时刻的快照的数据。以下是一次性查询的示例,使用协程实现异步查询,只需要在 DAO 方法中使用 suspend 关键字即可。DAO 方法添加 suspend 关键字之后,就不能在主线程中调用(编译时就已经会报错),需要在协程中调用。

@Dao
interface UserDao {@Query("SELECT * FROM users")suspend fun getAll(): List<User>@Query("SELECT * FROM users WHERE name LIKE :name")suspend fun findByName(name: String): List<User>@Insertsuspend fun insertAll(vararg users: User)
}// 使用 DAO 中的异步查询方法
runBlocking {val result = userDao.getAll();result.forEach {println("User { userId = ${it.userId}, name = ${it.name}, age = ${it.age}}")}
}

2.3.3 编写异步观察者查询

笔者注:笔者使用 Kotlin 语言,这里就使用 Kotlin 的 Flow 和协程进行演示,读者有兴趣的话可以自己使用RxJava 和 Guava 尝试。

观察者查询是这样一种查询操作,当数据库中被查询方法引用的表发生任何变动,都会重新查询返回最新的数据。这种查询操作通常用于数据库数据插入、更新、删除之后,需要及时显示出来(例如:医院的叫号系统)。在 DAO 查询方法中,使用 Flow 作为返回值,调用 Flowcollect() 方法即可查询结果,该方法参数为一个 FlowCollector 接口实现,首次查询返回当前数据库快照数据,查询结果数据会在 FlowCollector 接口实现的 emit 回调方法中返回,如下示例所示:

@Dao
interface UserDao {@Query("SELECT * FROM users")fun getAll(): Flow<List<User>>
}// 观察者查询
GlobalScope.launch {val flow = userDao.getAll();flow.collect(object : FlowCollector<List<User>> {override suspend fun emit(value: List<User>) {value.forEach {println("User { userId = ${it.userId}, name = ${it.name}, age = ${it.age}}")}}})
}

注意事项:在Kotlin 中,Room 观察者查询 Flow.collect() 必须在协程中调用,且必须是非阻断协程中调用。

三、编后语

在数据访问对象类中定义的查询语句,特别是复杂查询,需要有扎实的 SQL 基础,读者如果 SQL 基础欠缺,可以自行学些下 SQL 相关的内容。

Android Room 数据访问对象(DAO)详解相关推荐

  1. Android实战:CoolWeather酷欧天气(加强版数据接口)代码详解(上)

    -----------------------------------该文章代码已停更,可参考浩比天气(更新于2019/6/25)----------------------------------- ...

  2. Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(一)

    文章大纲 引言 一.Android Storage Access Framework 二.Storage Access Framework 的主要角色成员 1.Document Provider 文件 ...

  3. Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(二)

    文章大纲 引言 一.DirectFragment 1.当选中DirectoryFragment中RecyclerView的Item时 2.选中DirectoryFragment中RecyclerVie ...

  4. php dao类设计,DAO数据访问对象设计 - GoFrame官网 - 类似PHP-Laravel, Java-SpringBoot的Go企业级开发框架...

    关于DAO数据访问对象设计其实是关于GoFrame框架工程化实践中比较重要一块设计. DAO设计结合GoFrame的ORM组件性能和易用性都很强,可以极大提高开发和维护效率.看完本章节内容之后,小伙伴 ...

  5. DAO数据访问对象(Data Access Object)

    持久化是将程序中数据库在瞬时状态和持久状态间转换的机制JDBC是一种持久化的机制,将程序直接保存成文本文件也是持久化机制的一种实现 JDBC的封装: 原因: JDBC将程序中的数据持久化保存到MySQ ...

  6. mysql如何限制数据修改密码_MySQL_MySQL数据库中修改密码及访问限制设置详解,MySQL是一个真正的多用户、多 - phpStudy...

    MySQL数据库中修改密码及访问限制设置详解 MySQL是一个真正的多用户.多线程SQL数据库服务器.MySQL是以一个客户机/服务器结构的实现,它由一个服务器守护程序mysqld和很多不同的客户程序 ...

  7. Android四大组件Service之AIDL详解

    Android四大组件Service之AIDL详解 前言 简介 基础知识 AIDL 服务端 定义AIDL文件规则 创建 .aidl 文件 清单注册 通过 IPC 传递对象 调用 IPC 方法 Andr ...

  8. 基于sqlite的android数据库编程,Android编程之SQLite数据库操作方法详解

    Android编程之SQLite数据库操作方法详解 发布时间:2020-09-07 12:33:04 来源:脚本之家 阅读:85 作者:低调小一 本文实例讲述了Android SQLite数据库操作方 ...

  9. 深入理解Android(一):Gradle详解

    http://www.infoq.com/cn/articles/android-in-depth-gradle/ http://www.infoq.com/cn/articles/android-i ...

  10. Android之TelephonyManager类的方法详解

    林计钦-JAVA java技术群:127834248 博客园   首页   社区   新随笔   联系   订阅   管理 随笔-105  评论-24  文章-0  trackbacks-0 Andr ...

最新文章

  1. Mybatis入门:3(动态sql)
  2. 将多个文件的属性添加到数据库
  3. Spring RestTemplate中文乱码解决方案
  4. vuex状态管理简单入门
  5. SwipeRefreshLayout
  6. Java 程序 ——感想
  7. 收音机磁棒天线4根接法_五六十年代不需要电的收音机,你见过吗?
  8. 122 Python程序中的进程池和线程池
  9. prototype.js学习(3)函数绑定
  10. linux终端清除命令,如何清除 Linux 中的终端?使用 clear 命令清除 Linux 终端
  11. qunee for html5 绘图
  12. 云锁和悬镜服务器哪个好,安全狗、悬镜、云锁、云帮手建议用哪个比较好?
  13. Java网络编程(BIO和NIO)
  14. 网管必修课 中小企业安全路由器配置
  15. 微信小程序识别二维码
  16. Cannot get a text value from a numeric cell
  17. 修改服务器tcp会话数,windows2012服务器TCP连接数
  18. 移动硬盘安装ubuntu16.04
  19. html+css(锚点的使用)
  20. java-集合-set(不重复集合)知识分解——庖丁解牛版

热门文章

  1. java远古部落7723_远古传说之华夏浩劫
  2. MySql + JDBC 数据库基础的操作(防止sql注入的方法)
  3. IObit Unlocker超实用工具,专治各种不服
  4. win10专业版 原版安装教程
  5. 拟推荐全省文物系统先进_文物系统突出贡献先进个人事迹材料
  6. LFMCW雷达测速基础- 多普勒频移和2DFFT
  7. LeetCode 打家劫舍题型 解析
  8. ofo 成了一个披着「退押金」的电商导购平台?
  9. 软件工程--可行性研究过程详解
  10. openwrt utc时区修改