一、Room简介

Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。该库可帮助您在运行应用的设备上创建应用数据的缓存。此缓存充当应用的单一可信来源,使用户能够在应用中查看关键信息的一致副本,无论用户是否具有互联网连接。

二、依赖

def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"

如果你是使用Java语言开发,同时还要添加下面这项依赖:

 annotationProcessor "androidx.room:room-compiler:$room_version"

如果是 使用Kotlin语言开发,那么则是将上面Java环境下的依赖换成下面这项:

 kapt "androidx.room:room-compiler:$room_version"

并同时添加下面这个插件:

apply plugin: 'kotlin-kapt'

上面加粗的部分依赖一定要注意,否则恭喜入坑。

三、Room库的三个基础但是很重要的概念:

  1. Entity
    实体类,也就是数据库的一张表结构。使用注解@Entity标记。
  2. Dao
    包含一系列访问数据库的方法。使用注解@Dao标记。
  3. Database
    数据库持有者,是与应用持久化相关数据的底层连接的主要接入点。使用注解@Database标记。

使用**@Database注解需满足三个条件**:
1、定义的类必须是继承于RoomDatabase的抽象类。
2、在注解中需要定义与数据库相关联的实体类列表。
3、包含一个没有参数的抽象方法并且返回一个带有注解的 @Dao。

四、几个注解

Entity:

使用@Entity注解定义的类会被映射为数据库中的一张表。默认表名为实体类的类名,字段名为表名,可以修改。

@Entity(tableName = "JinGuangBuDaiXi")
data class Role(/*** roleId字段* 必须* 指定,系统会自动生产这个字段的值。请注意上面的<!-----“必须”----->二字* */@PrimaryKey(autoGenerate = true) var roleId: Long,@ColumnInfo(name = "userName") var name: String,@ColumnInfo(name = "SHIHAO", defaultValue = "jinguangbudaixi") var shihao: String,@ColumnInfo(name = "kongfu") var wugong: String
) {// 实际使用中,上面的方法里面,roleId字段必须指定,添加了Ignore的字段也必须指定,但是按理说这两个都不应该必须指定的。
// roleId是id,每次添加的时候自增1,不指定的话,按其默认的规则来。但实际使用的时候,必须指定ID。
// Ignore:既然都说可以忽略了,那么我没有指定你系统忽略就是了,为什么还必须要指定?
}

上面我们看到,又出现了几个新的注解:@Entity、@PrimaryKey、@ColumnInfo和@Ignore,它们的含义如下:

1、@Entity注解中我们传入了一个参数 tableName用来指定表的名称。
2、@PrimaryKey注解用来标注表的主键,并且使用autoGenerate = true 来指定了主键自增长。
3、@ColumnInfo注解用来标注表对应的列的信息比如表名、默认值等等。
4、@Ignore 注解顾名思义就是忽略这个字段,使用了这个注解的字段将不会在数据库中生成对应的列信息。也可以使用@Entity注解中的 ignoredColumns 参数来指定,效果一样。

几种注解的源码如下:

Entity注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Entity {String tableName() default "";Index[] indices() default {};boolean inheritSuperIndices() default false;String[] primaryKeys() default {};ForeignKey[] foreignKeys() default {};String[] ignoredColumns() default {};
}

PrimaryKey注解:

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface PrimaryKey {boolean autoGenerate() default false;
}

ColumnInfo注解:

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface ColumnInfo {String name() default INHERIT_FIELD_NAME;@SuppressWarnings("unused") @SQLiteTypeAffinity int typeAffinity() default UNDEFINED;boolean index() default false;@Collate int collate() default UNSPECIFIED;String defaultValue() default VALUE_UNSPECIFIED;String INHERIT_FIELD_NAME = "[field-name]";int UNDEFINED = 1;int TEXT = 2;int INTEGER = 3;int REAL = 4;int BLOB = 5;@IntDef({UNDEFINED, TEXT, INTEGER, REAL, BLOB})@Retention(RetentionPolicy.CLASS)@interface SQLiteTypeAffinity {}int UNSPECIFIED = 1;int BINARY = 2;int NOCASE = 3;int RTRIM = 4;@RequiresApi(21)int LOCALIZED = 5;@RequiresApi(21)int UNICODE = 6;@IntDef({UNSPECIFIED, BINARY, NOCASE, RTRIM, LOCALIZED, UNICODE})@Retention(RetentionPolicy.CLASS)@interface Collate {}

Ignore注解:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR})
@Retention(RetentionPolicy.CLASS)
public @interface Ignore {}

Dao

Dao类是一个接口interface,其中定义了一系列的操作数据库的方法。通常我们操作数据库无非就是增、删、改、查。Room也为我们的提供了相关的注解,有@Insert、@Delete、@Update 和 @Query

@query:查询注解,参数是String类型,我们直接写SQL语句进行执行,而且编译的时候可以进行语法检查。

@Query("select * from role where userId = :id")
fun getRoleById(id: Long): Role

SQL语句引用传递的参数直接使用 :符号进行引用。
上面Query后面的sql语句中,user是指我们所建的实体类;userId则是我们创建实体类时指定的那个Long型数据的Id。我们还可以这么改:

@Query("select * from Role where name = :mName")
fun getRoleById(mName: String): Role

@Insert:如果我们需要向表中插入一条数据,我们直接定义一个方法并用 @Insert注解标注就可以:

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addRole (mRole: Role)

我们看到直接中有个参数onConflict,表示当插入的数据已经存在时候的处理逻辑,有三种操作逻辑:REPLACEABORTIGNORE。如果不指定则默认为ABORT终止插入数据。这里我们将其指定为REPLACE替换原有数据。
@Delete:如果需要删除表的数据则使用 @Delete注解:

@Delete
fun deleteRoleByRole(mRole: Role): 使用主键来查找要删除的实体。

@Update: 如果需要修改某一条数据则使用 @Update注解,和@Delete一样也是根据主键来查找要删除的实体。

@Update
fun updateRoleByRole (mRole: Role)

上面说的 @Query 查询接受的参数是一个字符串,所以像删除或者更新我们也可以使用 @Query 注解来使用SQL语句来直接执行。比如根据userid来查询某个用户或者根据userid更新某个用户的姓名:

@Query("delete from role where userId = :id ")
fun deleteRoleById(id:Long)@Query("update role set userName = :updateName where userID =  :id")
fun update(id: Long, updateName: String)

最终Dao的结构如下:

@Dao
interface RoleDao {@Insert(onConflict = OnConflictStrategy.REPLACE)fun addUser(mRole:Role)//Insert 可以单独插入,也可以批量插入@Insertfun insertUser(mRole:Role)@Insertfun insertRoles(mRole:List<Role>)//Query的值即使要执行的sql语句@Query("SELECT * FROM JinGuangBuDaiXi")fun getAll():List<Role>@Query("SELECT * FROM JinGuangBuDaiXi WHERE userName in (:names)")fun getRolesByMames(names:List<String>):List<Role>//注意点:1、在Room使用中,模糊匹配的格式为" like '%' || :userName || '%' ",即在要匹配的参数值前后要加上 “||”,并且“%”要区分出来//       2、下面查询用到的变量的名字要跟实体类(比如本demo中的Role.kt)中的变量要保持一致,否则查询失败//       3、WHERE后面的变量(比如本例中的“userName”)要用实体类(比如本demo中的Role.kt)中注解后面的“name”字段的值,否则查询失败。@Query("SELECT * FROM JinGuangBuDaiXi WHERE userName like '%'|| :name ||'%' LIMIT 1")fun getRoleInfoByName(name:String):Role@Query("SELECT * FROM JinGuangBuDaiXi WHERE kongfu like '%'|| :wugong ||'%' LIMIT 1")fun getRoleInfoBykONGfU(wugong:String):Role@Query("SELECT * FROM JinGuangBuDaiXi WHERE SHIHAO like '%'|| :shihao ||'%' LIMIT 1")fun getRoleInfoByShiHao(shihao:String):Role@Updatefun updateRoleByRole(user: Role)@Deletefun deleteRoleByRole(mRole: Role)
}

Database

首先定义一个抽象类继承RoomDatabase类,并添加注解 @Database 来标识:

@Database(version = 1, entities = [Role::class])
abstract class RoleDatabase : RoomDatabase() {abstract fun roleDao():RoleDaocompanion object {private var instance: RoleDatabase? = nullfun getInstance(context: Context): RoleDatabase {if (instance == null) {instance = Room.databaseBuilder(context, RoleDatabase::class.java, "Role.db" ).allowMainThreadQueries().build()//"Role.db" :数据库名称}return instance as RoleDatabase}}
}

使用entities来映射相关的实体类,version来指明当前数据库的版本号。这里使用了单例模式来返回Database,以防止新建过多的实例造成内存的浪费。

Room.databaseBuilder(context,klass,name).build()来创建Database,其中第一个参数为上下文,第二个参数为当前Database的class字节码文件,第三个参数为数据库名称。

默认情况下Database是不可以在主线程中进行调用的。因为大部分情况,操作数据库都还算是比较耗时的动作。如果需要在主线程调用则使用allowMainThreadQueries进行说明。

具体使用:

几个关键的步骤都完成后,接下来我们自然是开始进入使用阶段:

class RoomSqlActivity : BaseActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_room_sql)var roleDao = RoleDatabase.getInstance(this).roleDao()var name = "任缥缈"var shihao = "风满楼,卷黄沙,舞剑春秋,名震天下。雨飘渺,倦红尘,还君明珠,秋水浮萍。"var wugong = "飘渺剑法:虚无飘渺,无迹可寻,精妙绝伦,变幻莫测。无形无相"btnAdd.setOnClickListener {var role01 = Role(1L, name, shihao, wugong)roleDao.addUser(role01)name = "俏如来"shihao = "红尘轮回众生顾,因果循环有定数,放下屠刀虽成佛,愿坠三途灭千魔。"wugong ="圣印六式;诛魔之利:止戈流·「鬼破、星流、星陨、星痕、月痕、日殒、破日、天誓」;止戈流·真阵·「初式·十剑山河荡狼烟、继式·十面江海靖尘嚣、终式·十万沙劫漫云天」"var role02 = Role(2L, name, shihao, wugong)roleDao.addUser(role02)name = "藏镜人"shihao = "踏烽火,折兵锋,正邪无用;斩敌颅,杀魍魉,天地不容"wugong = "以纯阳金刚体强行逆练纯阴功法:飞瀑掌;纯阳掌;纯阳行左·飞瀑走右·阴行阳招·阳行阴招·阴阳融合·袭地贯天;啸问岁月·天狼影"var role03 = Role(3L, name, shihao, wugong)roleDao.addUser(role03)name = "遥星公子别小楼"shihao = "沉刀埋霜小楼庭,回首江湖风云轻。君有才能纵捭阖,清溪仰望有遥星"wugong ="纵横诀·『吴钩霜月明、飒沓如流星、千里不留行、十步杀一人、赵客缦胡缨、银鞍照白马;星月合招:十方萧索无涯·千古夕阳有主·诗仙纵横·刀剑茫茫去不还』、碧海定涛掌"var role04 = Role(4L, name, shihao, wugong)roleDao.addUser(role04)println("TAGTAG:执行了添加操作")}btnQuery.setOnClickListener {roleDao.getAll().forEach() {println("TAGTAG:执行了查询操作:${it.toString()}")}}btnInsert.setOnClickListener {name = "宫本总司"shihao = "萧无名、曲无名、声幽幽、声悲鸣,心何闷?情何困?眉深锁、孤独行。"wugong = "无常无定。神魔一念:以魔入心,以神出招,心法极意,不在魔心控杀,而在神意止杀,忘情忘仇,亦神亦魔,神魔非我。一剑无极、一剑无尽、一剑无声、一剑无悔"var roleInsert = Role(5L, name, shihao, wugong)roleDao.insertUser(roleInsert)println("TAGTAG:执行了  插入  操作")}btnQuery.setOnClickListener {name = "任缥缈"var role = roleDao.getRoleInfoByName(name);println("TAGTAG:通过${name}查询到的信息:${role?.toString()}")wugong = "飘渺剑法:虚无飘渺,无迹可寻,精妙绝伦,变幻莫测。无形无相"role = roleDao.getRoleInfoBykONGfU(wugong)println("TAGTAG:通过${wugong}查询到的信息:${role?.toString()}")shihao = "萧无名、曲无名、声幽幽、声悲鸣,心何闷?情何困?眉深锁、孤独行。"role = roleDao.getRoleInfoByShiHao(shihao)println("TAGTAG:通过${shihao}查询到的信息:${role?.toString()}")var nameList = listOf<String>("任缥缈", "俏如来")var nameQueryList = roleDao.getRolesByMames(nameList)for (item in nameQueryList){println("通过namelist查询到的结果:${item.toString()}")}}btnUpdate.setOnClickListener {name = "俏如来"shihao = "天堂地狱一道门,道门无扉三朵云。云中难觅五形气,气化心逢七彩君。"wugong = "圣印六式;诛魔之利:止戈流·「鬼破、星流、星陨、星痕、月痕、日殒、破日、天誓」;止戈流·真阵·「初式·十剑山河荡狼烟、继式·十面江海靖尘嚣、终式·十万沙劫漫云天」"var roleUpdate = Role(2L, name, shihao, wugong)roleDao.updateRoleByRole(roleUpdate)roleUpdate = roleDao.getRoleInfoByName(name)println("TAGTAG:更新后的结果:${roleUpdate.toString()}")}btnDelete.setOnClickListener {name = "俏如来"shihao = "天堂地狱一道门,道门无扉三朵云。云中难觅五形气,气化心逢七彩君。"wugong = "圣印六式;诛魔之利:止戈流·「鬼破、星流、星陨、星痕、月痕、日殒、破日、天誓」;止戈流·真阵·「初式·十剑山河荡狼烟、继式·十面江海靖尘嚣、终式·十万沙劫漫云天」"var roleUpdate = Role(2L, name, shihao, wugong)roleDao.deleteRoleByRole(roleUpdate)var list = roleDao.getAll()for (item in list){println("TAGTAG:删除指定数据:俏如来后查询到的结果:${item.toString()}")}}}
}

运行结果:

数据库的升级和降级:

在使用数据库的时候避免不了的就是数据库的更新。数据库的升级或者降级使用addMigrations方法进行操作, 然后完整的Database应该是这样的:

@Database(version = 1, entities = [Role::class])
abstract class RoleDatabase : RoomDatabase() {abstract fun roleDao():RoleDaocompanion object {private var instance: RoleDatabase? = null/***startVersion:表示的是升级开始的版本* endVersion:表示要升级到的目标版本,endVersion>startVersion*/fun getInstance(context: Context, isUpdateVersion:Boolean, startVersion:Int, endVersion:Int): RoleDatabase {if (instance == null) {if (isUpdateVersion){ //"Role.db" :数据库名称instance = Room.databaseBuilder(context,RoleDatabase::class.java,"Role.db" ).allowMainThreadQueries().build()}else{/*** startVersion表示的是升级开始的版本,* endVersion表示要升级到的目标版本,endVersion>startVersion。* 同时需要将@Database注解中的version的值修改为和endVersion相同。* 数据库的升级或者降级使用addMigrations方法进行操作*/instance = Room.databaseBuilder(context,RoleDatabase::class.java,"Role.db").addMigrations(object : Migration(startVersion,endVersion){override fun migrate(database: SupportSQLiteDatabase) {database.execSQL("ALTER TABLE user ADD age INTEGER Default 0 not null ")}}).allowMainThreadQueries().build()}}return instance as RoleDatabase}}
}

Migration方法有两个参数:

startVersion:表示的是升级开始的版本。
endVersion:表示要升级到的目标版本,endVersion>startVersion。同时需要将@Database注解中的version的值修改为和endVersion相同。

当在升级或者降级的过程中出现版本未匹配到的情况的时候,默认情况下会直接抛异常出来。当然我们也可以处理异常。

升级的时候可以添加fallbackToDestructiveMigration方法,当未匹配到版本的时候就会直接删除表然后重新创建。

降级的时候添加fallbackToDestructiveMigrationOnDowngrade方法,当未匹配到版本的时候就会直接删除表然后重新创建。

Android Jetpack 架构组件之 Room相关推荐

  1. Android Jetpack架构组件之 Room(使用、源码篇)

    2019独角兽企业重金招聘Python工程师标准>>> 1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发 ...

  2. android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  3. android置组件下面,Android Jetpack架构组件(十二)之Hilt

    一. 依赖注入简介 依赖注入(英文Dependency Injection,简写DI)是一种被广泛使用的编程技术,主要的作用代码解耦. 借助依赖注入,我们可以轻松的管理类之间的依赖,并最终建立高可维护 ...

  4. Android Jetpack架构组件(一)带你了解Android Jetpack

    本文首发于微信公众号「后厂村码农」 前言 Android已经发展了11年,可以说是比较成熟的技术了,一开始时框架很少,也没有什么规范,所有的代码都是要自己写,比如网络请求,数据库请求,数据解析等等.后 ...

  5. Android Jetpack架构组件之Navigation

    Navigation 导航是指支持用户导航.进入和退出应用中不同内容片段的交互.Android Jetpack 的导航组件可帮助您实现导航,无论是简单的按钮点击,还是应用栏和抽屉式导航栏等更为复杂的模 ...

  6. ios navigation的返回按钮长按_Android Jetpack架构组件 — Navigation入坑详解 [转]

    前言 这是最近看见的觉得比较有意思的文,希望对大家的学习有帮助. Navigation 直接翻译即为导航,它是 Android Jetpack 组件之一,让单 Activity 应用成为首选架构.应用 ...

  7. Android JetPack架构篇,一个实战项目带你学懂JetPack

    第五届世界互联网大会昨日开幕,来自76个国家的1500余位嘉宾出席大会.腾讯公司董事会主席兼首席执行官马化腾在大会开幕式演讲中表示,全球产业都在进行数字化,在此期间机遇挑战并存,产业互联网机会巨大. ...

  8. Android Jetpack导航组件——Navigation的使用

    概述 Navigation是采用一个Activity和多个Fragment形式设计的Ui架构模式,但是众所周知,Fragment的管理一直是个麻烦事,需要通过FragmentManager和Fragm ...

  9. Android 官方架构组件 Navigation 使用详解

    前言 前段时间,我在做项目开发的时候对Fragment的管理遇到几个小问题,总觉得在现阶段封装好的Fragment管理器不太优雅.这成为我下决心学习Jetpack在很早之前推出的Navigation库 ...

最新文章

  1. MaxCompute2.0新功能介绍
  2. R-CNN系列学习笔记
  3. 没有required_springboot-使用OpenAPI之后我再也没有写过接口文档
  4. 段错误、内存泄漏、内存溢出、堆溢出、栈溢出
  5. .Net Core中依赖注入服务使用总结
  6. Linux gitpush错误,linux – GIT:无法推送(奇怪的配置问题)
  7. 阿里云李飞飞:什么是云原生数据库
  8. 一步一步学WF系列(四)——工作流模拟登陆
  9. linux内核定时器死机,浅析linux内核中timer定时器的生成和sofirq软中断调用流程
  10. ApacheCN 深度学习译文集 20201229 更新
  11. LeetCode刷题——91. 解码方法
  12. 【心电信号】基于matlab心电信号去除基线漂移【含Matlab源码 955期】
  13. 【干货分享】大话团队的GIT分支策略进化史
  14. eventlet绿化和patch原理
  15. 离线语音控制系统 唤醒词.命令字
  16. 基于单片机仿指针显示的电子时钟设计(毕业设计资料)
  17. Mybatis多表新增
  18. SpringBoot2核心技术-笔记
  19. java实验目的_Java实验报告(实验一)
  20. 不只是coding_不只是外表

热门文章

  1. 网络技术大讲堂:什么是IPv6+?
  2. 能否用计算机证明数学定理大全,那些教材中未给出证明的定理,到底能否使用?...
  3. 【Win 10 应用开发】MIDI 音乐合成——乐理篇
  4. IFR02红外雨量传感器
  5. html5 音乐播放进度条,js实现音乐播放控制条
  6. BZOJ 1127 [POI2008]KUP 最大子矩阵
  7. Java输入1~12之间的整数,显示该月份的英语单词及这个月属第几季度。
  8. #!/bin/bash和#!/bin/sh是什么意思以及区别
  9. 203.为用户定义的数据类型绑定默认值案例
  10. JDK源码解析---Short