Jetpack 架构组件:Room 数据库应用
简介
Room 是一个 ORM (Object Relational Mapping)对象关系映射数据库、其底层还是封装的 SQLite 的能力。它赋予了一个强大的功能,就是可以用面向对象的思维来和数据库进行交互,绝大数情况下不用再和SQL语句打交道了,同时也不用担心操作数据库的逻辑会让项目的整体代码变混乱。
Room 主要是由Entity、Dao 和Database 这3部分组成:
- Entity
用于定义封装实际数据的实体类,每个实体类都会在数据库中有一张对应的表,并且表中的列是根据实体类中的字段自动生成的。
- Dao
Dao 是数据访问对象的意思,通常会在这里对数据库的各项操作进行封装,在实际编程的时候,逻辑层就不需要和底层数据库打交道了,直接和Dao层进行交互即可。
- Database
用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提供Dao层的访问实例。
依赖
apply plugin: 'kotlin-kapt'dependencies {def roomVersion = "2.2.0"// Roomimplementation("androidx.room:room-runtime:$roomVersion")// java -RoomannotationProcessor("androidx.room:room-compiler:$roomVersion")// kotlin -Roomkapt("androidx.room:room-compiler:$roomVersion")// 支持协程implementation("androidx.room:room-ktx:$roomVersion")
}
具体请看可选依赖
用法
定义 Entity
对于每个实体,系统会在关联的Database对象中创建一个表,以存储这些项。您必须通过 Database 类中的 entities 数组引用实体类。
@Entity(tableName = "book")
data class Book(@PrimaryKey(autoGenerate = true)var id:Int=1,@ColumnInfo(name = "book_name") // 书名var bookName:String,@ColumnInfo(name = "category_name")// 类别var categoryName:String,@ColumnInfo(name = "book_price") // 价格var bookPrice:Int
)
- @PrimaryKey
每个实体必须将至少1个字段定义为主键。即使只有1个字段,仍然需要为该字段添加@PrimaryKey注释。此外,如果想让Room为实体分配自动 ID,则可以设置@PrimaryKey的autoGenerate 属性。
- tableName
默认情况下,Room将类名称用作数据库表名称。如果希望表具有不同的名称,请设置 @Entity 注释的 tableName 属性。
注意: SQLite 中的表名称不区分大小写。
- @ColumnInfo
默认情况下,Room将字段名称用作数据库中的列名称。如果您希望列具有不同的名称,请将 @ColumnInfo 注释添加到字段
DAO(数据访问对象)
DAO 既可以是接口,也可以是抽象类。如果是抽象类,则该 DAO 可以选择有一个以RoomDatabase为唯一参数的构造函数。Room 会在编译时创建每个 DAO 实现。
插入
当您创建 DAO 方法并使用 @Insert 对其进行注释时,Room 会生成一个实现,该实现在单个事务中将所有参数插入数据库中。
@Dao
interface BookDao {@Insert(onConflict = OnConflictStrategy.REPLACE)fun insertBook( book: Book):Long
}// Activity 代码
tv_insert.setOnClickListener {thread {bookDao.insertBook(book)}}
如果 @Insert 方法只接收 1 个参数,则它可以返回 long,这是插入项的新rowId。如果参数是数组或集合,则应返回 long[] 或 List
更新
@Dao
interface BookDao {@Updatefun updateBooks( book: Book)
}// Activity 代码
tv_update.setOnClickListener {thread {bookDao.updateBooks(book)}}
可以让此方法返回一个 int 值,以指示数据库中更新的行数。
删除
Delete 便捷方法会从数据库中删除一组以参数形式给出的实体。它使用主键查找要删除的实体
@Dao
interface BookDao {@Deletefun deleteBooks( book: Book)
}// Activity 代码
tv_delete.setOnClickListener {thread {bookDao.deleteBooks(book)}}
可以让此方法返回一个int值,以指示从数据库中删除的行数。
查询
@Query 是 DAO 类中使用的主要注释。它允许您对数据库执行读/写操作。每个@Query方法都会在编译时进行验证,因此如果查询出现问题,则会发生编译错误,而不是运行时失败。
- 简单查询
@Dao
interface BookDao {@Query("SELECT * FROM book ")fun selectAllBooks(): List<Book>
}//Activity 中代码tv_query.setOnClickListener {thread {val books=bookDao.selectAllBooks()}}
在编译时,Room 知道它在查询用户表中的所有列。如果查询包含语法错误,或者数据库中没有用户表格,则 Room 会在您的应用编译时显示包含相应消息的错误。
- 条件查询
@Query("SELECT * FROM book WHERE category_name IN (:categorys)")fun selectBooksByCategory(categorys: List<String>): List<Book>@Query("SELECT * FROM book WHERE book_price > :price")fun selectBooksByPrice(price: Int): List<Book>
- 使用 LiveData 进行可观察查询
@Dao
interface BookDao {fun selectBooksByBookName(name: String): LiveData<Book>}// Activity 代码
bookDao.selectBooksByBookName("数据结构与算法").observe(this, { book->tv_show.text="书名${book.bookName}价格:${book.bookPrice}"})
执行查询时,您通常会希望应用的界面在数据发生变化时自动更新。使用 LiveData 类型的返回值。当数据库更新时,Room 会生成更新 LiveData 所必需的所有代码。
- 使用 Flow 进行可观察查询
@Query("SELECT * FROM press WHERE press_name = :name")fun selectPressByPressName(name: String): Flow<Press>
- 使用协程 进行查询
@Query("SELECT * FROM press WHERE press_name = :name")suspend fun selectPressByPressName(name: String): Press// Activity 中代码lifecycle.coroutineScope.launch{val press=pressDao.selectPressByPressName("电子出版社")}
selectPressByPressName() 是一个挂起函数,挂起函数必须在协程中或者挂起函数中使用。
注意: 除非已对构建器调用allowMainThreadQueries()(正式开发环境不建议使用此方法),否则 Room 不支持在主线程上访问数据库,因为它可能会长时间锁定界面。异步查询(返回 LiveData 或 Flowable 实例的查询)无需遵守此规则,因为此类查询会根据需要在后台线程上异步运行查询。
数据库 Database
@Database(entities = [Book::class], version = 1)
abstract class AppDatabase : RoomDatabase() {abstract fun getBookDao(): BookDaocompanion object {private var instance: AppDatabase? = nullfun getDatabase(mContext: Context): AppDatabase {instance?.let {return it}return Room.databaseBuilder(mContext.applicationContext,AppDatabase::class.java, "app_database").build().apply {instance = thisinstance}}}
}
注意: 如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循单例设计模式。每个 RoomDatabase 实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。
如果您的应用在多个进程中运行,请在数据库构建器调用中包含 enableMultiInstanceInvalidation()。这样,如果您在每个进程中都有一个AppDatabase实例,可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中 AppDatabase 的实例。
数据库升级
现有表增加新列
- Book 数据类增加了bookAuthor(作者)属性
@Entity(tableName = "book")
data class Book(@PrimaryKey(autoGenerate = true)var id:Int=1,@ColumnInfo(name = "book_name") // 书名var bookName:String,@ColumnInfo(name = "category_name")// 类别var categoryName:String,@ColumnInfo(name = "book_price") // 价格var bookPrice:Int,@ColumnInfo(name = "book_author") // 作者var bookAuthor:String
)
- 修改AppDatabase代码,升级数据库中表
@Database(entities = [Book::class], version = 2)
abstract class AppDatabase : RoomDatabase() {abstract fun getBookDao(): BookDaocompanion object {private var instance: AppDatabase? = null// 实现Migration 的匿名类val MIGRATION_1_2=object :Migration(1,2){override fun migrate(database: SupportSQLiteDatabase) {database.execSQL("alter table book add column book_author text not null default 'unknown' ")}}fun getDatabase(mContext: Context): AppDatabase {instance?.let {return it}return Room.databaseBuilder(mContext.applicationContext,AppDatabase::class.java, "app_database.db")// 升级表.addMigrations(MIGRATION_1_2).build().apply {instance = thisinstance}}}
}
在@Database 注解中,将版本号升级成了2,在companion object结构体中实现了Migration的匿名类。
增加新表
- 新增一个数据类Press
@Entity(tableName = "press")
data class Press(@PrimaryKeyvar id:Int,@ColumnInfo(name = "press_name") // 名字var name:String ,@ColumnInfo(name = "press_address") // 地址var address:String)
- 创建新表
@Database(entities = [Book::class,Press::class], version = 3)
abstract class AppDatabase : RoomDatabase() {abstract fun getBookDao(): BookDaoabstract fun getPressDao(): PressDaocompanion object {private var instance: AppDatabase? = nullval MIGRATION_1_2=object :Migration(1,2){override fun migrate(database: SupportSQLiteDatabase) {database.execSQL("alter table book add column book_author text not null default 'unknown' ")}}val MIGRATION_2_3=object :Migration(2,3){override fun migrate(database: SupportSQLiteDatabase) {database.execSQL("create table press(id integer primary key not null,press_name text not null,press_address text not null)")}}fun getDatabase(mContext: Context): AppDatabase {instance?.let {return it}return Room.databaseBuilder(mContext.applicationContext,AppDatabase::class.java, "app_database.db").addMigrations(MIGRATION_1_2, MIGRATION_2_3).build().apply {instance = thisinstance}}}
}
数据库查看数据
- 导出数据库文件app_database.db
打开Device File Explorer,打开目录 data/data/包名/databases/ app_database.db,如图所示:
保存app_database.db 到指定位置:
- 安装 DB Navigator 查看数据
在AndroidStudio中 settings->plugins 下载插件DB Navigator。
安装重启后,会出现如图所示:
连接数据库,如下图所示:
打开导出数据库文件,如下图所示:
查看数据,如下图所示:
参考资料
- Room 官方文档
- Android ROOM 数据库高手秘籍
- Android从零开始搭建MVVM架构(4)————Room(从入门到进阶)
Jetpack 架构组件:Room 数据库应用相关推荐
- Android Jetpack架构组件之 Room(使用、源码篇)
2019独角兽企业重金招聘Python工程师标准>>> 1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发 ...
- android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- ios navigation的返回按钮长按_Android Jetpack架构组件 — Navigation入坑详解 [转]
前言 这是最近看见的觉得比较有意思的文,希望对大家的学习有帮助. Navigation 直接翻译即为导航,它是 Android Jetpack 组件之一,让单 Activity 应用成为首选架构.应用 ...
- Android Jetpack架构组件(一)带你了解Android Jetpack
本文首发于微信公众号「后厂村码农」 前言 Android已经发展了11年,可以说是比较成熟的技术了,一开始时框架很少,也没有什么规范,所有的代码都是要自己写,比如网络请求,数据库请求,数据解析等等.后 ...
- Jetpack架构组件 (一)-- Android Jetpack 简介
前言 Android 已经发展十多年了,可以说是比较成熟的技术了,一开始时框架很少,也没有什么规范,所有的代码都是要自己写,比如网络请求,数据库操作,数据解析等等.后来出现了一些框架来帮助开发者快速进 ...
- Retrofit+kotlin Coroutines(协程)+mvvm(Jetpack架构组件)实现更简洁的网络请求
前言 使用kotlin协程也有一段时间了,给我最大的感受就是完全可以替代Rxjava了,并且写起来更加的简洁. 6月份Retrofit发布的2.6.0版本内部支持了kotlin协程中的挂起(suspe ...
- android置组件下面,Android Jetpack架构组件(十二)之Hilt
一. 依赖注入简介 依赖注入(英文Dependency Injection,简写DI)是一种被广泛使用的编程技术,主要的作用代码解耦. 借助依赖注入,我们可以轻松的管理类之间的依赖,并最终建立高可维护 ...
- Android Jetpack架构组件之Navigation
Navigation 导航是指支持用户导航.进入和退出应用中不同内容片段的交互.Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模 ...
- Android Jetpack 架构组件之 Room
一.Room简介 Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制.该库可帮助您在运行应用的设备上创建应用 ...
最新文章
- 通俗易懂----尾递归
- (转)python协程2:yield from 从入门到精通
- mac安装python3
- Drupal的高速缓存配置APC
- 实现Unity编辑器模式下的旋转
- Android多媒体开发:照相机
- 最近工程师怎么都在写公众号?这玩意到底能赚多少钱?
- qt之qml开发优缺点_linux配置vlc-qt
- C++happy number开心数的实现算法(附完整源码)
- Android 添加 *.arr
- CVPR2015深度学习回顾
- 微信公众平台开发教程第21篇-“可信网址”白名单
- 9 CO配置-控制-成本中心会计-在成本控制范围激活成本中心会计核算
- 【springboot 踩坑记录】拦截器中无法注入bean
- unity三维地图的经纬度如何在二维地图上表示_安全数据分析:数据点—地图—线性回归...
- 同事乱用分页 MySQL 卡爆,我真是醉了...
- Android APP压力测试 之Monkey日志自动分析脚本
- C#控件编程详解之文本框(TextBox)
- 慢慢说计网:HTTP 长短连接
- 一款科幻题材基地建设策略游戏——太空避难所中文版 附游戏玩法
热门文章
- Thymeleaf从后端取数据到js中 js中字符串转数字的函数应用
- js查找数组元素位置
- SparkSQL的入门实践教程
- 一个中东外贸业务员分享的干货
- Qt 5.12.10 用Design ui 设计界面,连续输入中文时,变成英文输入
- 嘉兴 机器人仓库 菜鸟_揭秘菜鸟自动化仓库 机器人物联网+大数据(附视频)...
- persistence.cpp:2197:ann.xml(1): There should be space between attributes in function icvXMLParseTag
- 从Darry Ring看奢侈品该如何玩转互联网思维
- linux下安装录制视频软件Simple Screen Recorder
- python数据结构与算法 20 递归和递归三定律