AndroidRoom使用详解原创手记

Room提供了SQLite之上的一层抽象,既允许流畅地访问数据库,也充分利用了SQLite.

处理大量结构化数据的应用,能从在本地持久化数据中极大受益.最常见的用例是缓存有关联的数据碎片.以这种方式,在设备不能访问网络的时候,用户依然能够浏览离线内容.任何用户发起的改变,都应该在设备重新在线之后同步到服务器.

因为Room为你充分消除了这些顾虑,使用Room而非SQLite是高度推荐的.

添加依赖

Room的依赖添加方式如下:

Room有3个主要构件:

在运行时,你可以通过调用Room.databaseBuilder()或者Room.inMemoryDatabaseBuilder()方法请求Database实例.

这些构件,以及它们与app余下内容的关系,如下图:

UserDao.java

AppDatabase.java

1@Database(entities={User.class},version=1)2publicabstractclassAppDatabaseextendsRoomDatabase{3publicabstractUserDaouserDao();4}

在创建了以上文件之后,你能够使用以下代码来创建一个database实例:

1AppDatabasedb=Room.databaseBuilder(getApplicationContext(),2AppDatabase.class,"database-name").build();

备注:在实例化AppDatabase对象的时候,你应该使用单例模式,因为每一个RoomDatabase实例都是非常耗时的,而且你也应该很少访问多个实例.

使用Room实体定义数据

默认情况下,Room会为实体中定义的每个域创建一个列.如果实体中有你不想持久化的域,可以使用@Ignore来注解掉.在Database类中,你必须通过entities数据来引用实体类.

下面的代码片断展示了如何定义一个实体:

在持久化一个域,Room必须能够访问它.你可以将域设置为public,或者你可以提供该的getter/setter.如果你使用了getter/setter的方式,一定要记住:在Room里面,它们是基于JavaBeans转换的.

备注:实体要么有个空的构造器(如果相应的DAO类能够访问每一个持久化域的话),要有构造器里面的参数,数据类型和名字跟实体里面定义的域相匹配.Room也能够使用包含全部或者部分域的构造器,例如,一个构造器只能获取所有域中的几个.

使用主键

每一个实体必须定义至少1个主键.即使只有一个域,你依然需要使用@PrimaryKey来注解它.而且,如果你想Room分配自动ID给实体的话,你需要设置@PrimaryKey的autoGenerate属性.如果实体有一个复合主键的话,你需要使用注解@Entity的primaryKeys属性,示例代码如下:

默认情况下,Room使用实体类的名字作为数据库表的名字.如果你想要表拥有一个不同的名字,设置@Entity注解的tableName属性,示例代码如下:

1@Entity(tableName="users")2publicclassUser{3...4}

注意:SQLite中表名是大小写敏感的.

跟tableName属性相似的是,Room使用域的名字作为数据库中列的名字.如果你想要列有一个不同的名字的话,给域添加@ColumnInfo注解,示例代码如下:

注解索引和唯一性

依赖于你如何访问数据,你也许想要在数据库中建立某些域的索引,以加速查询速度.要给实体添加索引,需要在@Entity中引入indices属性,并列出你想要在索引或者复合索引中引入的列的名字.下列代码说明了注解的处理过程:

有些时候,数据库中的某些域或几组域必须是唯一的.你可以通过将注解@Index的unique属性设置为true,强制完成唯一的属性.下面的代码示例防止表有两行数据在列firstName和lastName拥有相同值:

定义对象之间的关系

尽管你不能使用直接的对象关系,Room仍然允许你在实体之间定义外键约束.

比如,如果有一个实体类Book,你可以使用@ForeignKey注解定义它和实体User的关系,示例代码如下:

备注:SQLite将@Insert(onConflict=REPLACE)作为REMOVE和REPLACE的集合来操作,而非单独的UPDATE操作.这个取代冲突值的方法能够影响你的外键约束.

创建嵌套对象

有些时候,在数据库逻辑中,你想将一个实体或者POJO表示为一个紧密联系的整体,即使这个对象包含几个域.在这些情况下,你能够使用@Embedded注解来表示一个对象,而你想将这个对象分解为表内的子域.然后你可以查询这些嵌套域,就像你查询其它的独立列一样.

举个例子,User类包含一个Address类的域,这个域表示的是street,city,state,postCode这几个域的复合.为了在表中单独存储复合的列,在User类里面,引入一个注解了@Embedded的Address域,就像如下代码片断展示的一样:

这个表表示User对象包含如下几列:id,firstName,street,state,city和post_code.

备注:嵌套的域同样可以包含其它的嵌套域.

如果实体拥有多个相同类型的嵌套域,你可以通过设置prefix属性保留每一列唯一.然后Room给嵌套对象的每一个列名的起始处添加prefix设置的给定值.

通过RoomDAO访问数据

要通过Room持久化库访问应用的数据,你需要使用数据访问对象(dataaccessobjects,即DAOs).Dao对象集形成了Room的主要构成,因为每一个DAO对象都引入了提供了抽象访问数据库的方法.

使用DAO对象而非查询构造器或者直接查询来访问数据库,你可以分开不同的数据库架构组成.此外,DAO允许你轻易地模拟数据库访问.

DAO要么是接口,要么是抽象类.如果DAO是抽象类的话,它可以随意地拥有一个将RoomDatabase作为唯一参数的构造器.Room在运行时创建DAO的实现.

方便地定义方法

使用DAO类,可以非常方便地表示查询.

插入

当你创建了一个DAO方法并注解了@Insert的时候,Room生成了一个实现,在单个事务中将所有的参数插入数据库.下面的代码片断展示了几个示例查询:

如果@Insert方法只接收了一个参数,它可以返回一个long,表示新插入项的rowId;如果参数是数组或者集合,同时地,它应该返回long[]或者List.

更新

按照惯例,在数据库中,Update方法修改了作为参数传递的实体集合.它使用查询来匹配每一个实体的主键.下面的代码片断展示了如何定义这个方法:

1@Dao2publicinterfaceMyDao{3@Update4publicvoidupdateUsers(User...users);5}尽管通常情况下并不需要,但是依然可以将这个方法返回int值,表示在数据库中被修改的行数.

删除

按照惯例,Delete方法从数据库中删除了作为参数传递的实体集合.它使用主键找到要删除的实体.下面的代码片断展示了如何定义这个方法:

1@Dao2publicinterfaceMyDao{3@Delete4publicvoiddeleteUsers(User...users);5}尽管通常情况下并不需要,但是依然可以将这个方法返回int值,表示从数据库中删除的行数.

查询

@Query是在DAO类中使用的主要的注解.它允许你在数据库中执行读写操作.每一个@Query方法都在编译时被证实,因为,如果查询有问题出现的话,会出现编译错误而非运行失败.Room也证实查询的返回值,以确定返回对象的域的名字是否跟查询响应中对应列的名字匹配,Room使用如下两种方式提醒你:

简单查询

1@Dao2publicinterfaceMyDao{3@Query("SELECT*FROMuser")4publicUser[]loadAllUsers();5}这是一个非常简单的查询,加载了所有User.在编译时,Room知晓这是在查询user表中所有列.

如果查询语句包含语法错误,或者user表在数据库中并不存在,Room会在编译时展示恰当的错误信息.

查询语句中传参

大多数时候,你需要向查询语句中传参,以执行过滤操作,比如,只展示大于某个年龄的user.

要完成这个任务,在Room注解中使用方法参数,如下所示:

1@Dao2publicinterfaceMyDao{3@Query("SELECT*FROMuserWHEREage>:minAge")4publicUser[]loadAllUsersOlderThan(intminAge);5}当这个查询在编译时处理的时候,Room匹配到:minAge,并将它跟方法参数minAge绑定.Room使用参数名来执行匹配操作.如果不匹配的话,app编译时会发生错误.

你也可以在查询中传递多个参数,或者将参数引用多次,如下所示:

返回列的子集

大多数情况下,你只需要实体中的几个域.比如,UI中只需要展示用户的姓和名,而非用户的每一个细节.通过只查询UI中展示的列,将节省宝贵的资源,查询也更快.

Room允许从查询中返回基于Java的对象,只要结果列集合能够映射成返回对象.比如,你创建了一个POJO来获取用户的名和姓:

现在,你可以在查询方法中使用这个POJO了:

1@Dao2publicinterfaceMyDao{3@Query("SELECTfirst_name,last_nameFROMuser")4publicListloadFullName();5}Room明白:查询返回了列first_name和last_name,这些值能够映射到NameTuple为的域中.

由此,Room能够产生适当的代码.如果查询返回了太多列,或者返回了NameTuple类中并不存在的列,Room将展示警告信息.备注:POJO也可以使用@Embedded注解.

传递参数集

一些查询可能要求你传入可变数目的参数,直到运行时才知道精确的参数数量.

比如,你可能想要搜索地区子集下的所有用户.Room明白参数表示集合的时机,并在运行时自动地基于提供了参数数目展开它.

1@Dao2publicinterfaceMyDao{3@Query("SELECTfirst_name,last_nameFROMuserWHEREregionIN(:regions)")4publicListloadUsersFromRegions(Listregions);5}

可观察查询

在执行查询的时候,经常想要在数据发生改变的时候自动更新UI.要达到这个目的,需要在查询方法描述中返回LiveData类型的值.在数据库更新的时候,Room生成所有必要的代码以更新LiveData.

备注:在1.0版本的时候,Room使用查询中访问的表的列表来决定是否更新LiveData实例.

RxJava响应式查询

Room也可以从定义的查询中返回RxJava2中的Publisher和Flowable.

要使用这个功能,在build.gradle文件中添加依赖:android.arch.persistence.room:rxjava2.之后,你可以返回在RxJava2中定义的数据类型,如下所示:

1@Dao2publicinterfaceMyDao{3@Query("SELECT*fromuserwhereid=:idLIMIT1")4publicFlowableloadUserById(intid);5}游标直接访问

如果你的应用逻辑要求直接访问返回的行,你可以从查询中返回Cursor对象,如下所示:

1@Dao2publicinterfaceMyDao{3@Query("SELECT*FROMuserWHEREage>:minAgeLIMIT5")4publicCursorloadRawUsersOlderThan(intminAge);5}注意:十分不推荐使用CursorAPI.因为它并不保证行是否存在以及行包含什么值.

除非你有需要Cursor的代码并且并不轻易的修改它的时候,你才可以使用这个功能.

查询多表

有些查询可能要求访问多个表以计算结果.Room允许你写任何查询,所以你也可以联接表.此外,如果响应是可观测数据类型,诸如Flowable/LiveData,Room观察并证实查询中引用的所有表.

下面的代码片段展示了如何执行表联接,以合并包含借书用户的表和包含在借书数据的表的信息:

你也可以从这些查询中返回POJO.比如,你可以写查询加载用户和它的宠物名:

迁移Room数据库

当应用中添加或者改变特性的时候,需要修改实体类以反映出这些改变.当用户升级到最新版本的时候,你不想用户失去所有数据,尤其是如果你还不能从远程服务器恢复这些数据的时候.

Room持久化库允许写Migration类来保留用户数据.每一个Migration类指定了startVersion和endVersion.在运行时,Room运行每一个Migration类的migrate()方法,使用正确的顺序迁移数据库到最新版本.

注意:如果你不提供必要的迁移,Room会重建数据库,这意味着你会失去原有数据库中的所有数据.

注意:要保证迁移逻辑按照预期进行,需要使用全查询而非引用表示查询的常量.在迁移完成之后,Room会证实这个计划,以确保迁移正确在发生了.如果Room发现了问题,它会抛出包含不匹配信息的异常.

迁移测试

写Migration并不是没有价值的,不能恰当的写Migration会在应用中引起崩溃.在保持应用的稳定性,你应该事先测试Migration.Room提供了一个Maven测试工具.但是,如果要使这个工具工作,你需要导出数据库schema.

导出schema

在编译的时候,Room会导出数据库schem信息,形成一个Json文件.要导出schema,需要在build.gradle文件中设置room.schemaLocation注解处理器属性,如下所示:build.gradle:

你应该保存导出的Json文件--这些文件表示了数据库schema的历史--在你的版本控制体系中,因为它允许Room创建老版本数据库用于测试.

要测试这些Migration,需要在测试需要的依赖中添加anroid.arch.persistence.room:testing,并在资产文件夹下添加schema地址,如下所示:build.gradle:

测试包提供了MigrationTestHelper类,它能够读取这些schema文件.它也实现了JUnit4TestRule接口,所有它能够管理已创建的数据库.

示例Migration测试如下:

测试数据库

在使用Room持久化库创建数据库的时候,证实应用数据库和用户数据的稳定性非常重要.

有两种方式测试你的数据库:

备注:在运行应用的测试的时候,Room允许你创建模拟DAO类的实例.使用这种方式的话,如果不是在测试数据库本身的话,你不必创建完成的数据库.这个功能是可能的,因为DAO并不泄露任何数据库细节.

真机测试

测试数据库实现的推荐途径是在真机上运行JUnit测试.因为这些测试并不创建Activity,它们应该比UI测试执行地更快.

在设置测试的时候,你应该创建内存版本数据库,以确保测试更加地密封.如下所示:

虚拟机测试

Room使用了SQLite支持库,后者提供了在AndroidFramework类里面匹配的接口.这个支持允许你传递自定义的支持库实现来测试数据库查询.备注:尽管这个设置允许测试运行地很快,但它并不是值得推荐的,因为运行在自己以及用户真机上面的SQLite版本,可能并不匹配你的虚拟机上面的SQLite版本.

使用Room引用复杂数据

Room提供了功能支持基数数据类型和包装类型之间的转变,但是并不允许实体间的对象引用.

使用类型转换器

有时候,应用需要使用自定义数据类型,该数据类型的值将保存在数据库列中.要添加这种自定义类型的支持,你需要提供TypeConverter,用来将自定义类型跟Room能够持久化的已知类型相互转换.

上述示例定义了2个方法,一个把Date转变成Long,一个把Long转变成Date.因为Room已经知道如何持久化Long对象,它将使用这个转换器持久化Date类型的值.

接下来,添加@TypeConverters注解到AppDatabbase类上,之后Room就能够在AppDatabase中定义的每一个实体和DAO上使用这个转换器.AppDatabase.java

1@Database(entities={User.class},version=1)2@TypeConverters({Converters.class})3publicabstractclassAppDatabaseextendsRoomDatabase{4publicabstractUserDaouserDao();5}使用这些转换器,你之后就能够在其它的查询中使用自定义的类型,就像你使用基本数据类型一样,如下所示:User.java

1@Entity2publicclassUser{3...4privateDatebirthday;5}UserDao.java

1@Dao2publicinterfaceUserDao{3...4@Query("SELECT*FROMuserWHEREbirthdayBETWEEN:fromAND:to")5ListfindUsersBornBetweenDates(Datefrom,Dateto);6}你也可以限制@TypeConverters的使用范围,包括单个实体,DAO和DAO方法.

理解为什么Room不允许对象引用

要点:Room不允许实体类间的对象引用.相反,你必须显式地请求应用需要的数据.

从数据库到对应对象模型的映射关系是通用最佳实践,在服务器端也运行良好.即使是在程序加载它们正在访问的域的时候,服务器依然执行良好.

1authorNameTextView.setText(book.getAuthor().getName());

然后,这个貌似无辜的改变引起Author表在主线程被查询.

如果你提前查询作者信息,而在你不再需要这个数据之后,将很难改变加载的方式.比如,UI不再需要展示Author信息,而应用依然高效地加载不同展示的数据,浪费了宝贵的内存空间.应用的效率将会降级,如果Author类引用了其它的表,如Books.

要使用Room同时引用多个实体,需要创建包含每个实体的POJO类,之后写联接了相应表的查询语句.这个结构良好的模型,结合了Room鲁棒的查询证实能力,允许应用在加载资源时消耗更少的资源,提升了应用的性能和用户体验.

THE END
1.python通过来体现语句之间的逻辑关系这样可以保证程序的鲁棒性,在遇到异常时进行相应的处理,从而保证程序的正常运行。 总结起来,Python通过灵活的条件语句、循环语句的运用,顺序结构的执行,函数的调用与返回,以及异常处理机制等手段来体现语句之间的逻辑关系。这使得Python成为了一款非常适合处理复杂问题的编程语言。https://www.python51.com/jc/123187.html
2.考研英语语句关系分类及区分方法考研英语语句之间关系主要是考查前后句子之间的逻辑关系。 句子与句子之间的关系可能是显性的,也可能是隐性的。显性的语句之间关系有明显的标志词出现,这会给考生理解文章的发展脉络带来很大方便。句际关系主要有以下几种: 一、顺接关系 后句是前句的延续或补充,标识词主要有then,after that,furthermore,also,when(https://yz.chsi.com.cn/kyzx/en/200907/20090706/27495222.html
3.科学网—MySql外键一对一,一对多,多对多表关系外键虽然能够帮你强制建立表关系 但是也会给表之间增加数据相关的约束;改也不好改,删也不好删。 删除的时候可以先删除把绑定关系表的数据,然后再删除被绑定关系表的数据。 现在绑定关系表是emp,被绑定关系表是dep;删除语句为: delete from emp where id = 4; https://wap.sciencenet.cn/blog-3445347-1282998.html
4.Python程序语法元素分析缩进可以嵌套使用什么意思缩进指每一行代码开始前的空白区域,用来表示代码之间的包含和层次关系 代码编写中,缩进可以用Tab键实现,也可以用多个空格**(一般是四个空格)**实现,但两者不能混用。建议使用四个空格的编写方式 除了单层缩进,一个程序的缩进还可以“嵌套”从而形成多层缩进,python语言对语句之间的层次关系没有限制,可以“无限制”嵌https://blog.csdn.net/qq_55016379/article/details/114675213
5.Java错题合集(3)48、一般情况下,选项是关系数据模型与对象模型之间匹配关系? 表对应类 记录对应对象 表的字段对应类的属性 49、J2EE中常用的名词解释正确的是? EJB容器:Enterprise java bean 容器 JMS:JAVA消息服务。主要实现各个应用程序之间的通讯。包括点对点和广播。 https://www.jianshu.com/p/29ec5bb08b71
6.胡敏教授解密考研阅读命题与对策者深层思路上的逻辑关系。从句子与句子,段落与段落之间的过渡可以读出作 者整个的思路及论证过程,才能做出正确的推理、判断或引申。平时训练时, 考生应多注意一些过渡词。 3.积极扩展词汇量,注意动词和名词,训练理解一些难句、复杂句。扩展词汇 量的目的是避免词汇的欠缺造成理解的失误。动词作为语句结构的枢纽,意义http://www.yuloo.com/kaoyan/news/2006-09-30/43221.shtml
7.华为HCIP华为数通工程师刷题日记1116(一个字惨)【答案解析】策略路由(PBR)的操作对象是数据包,所以if-match后面是可以跟ACL,但是不可以跟ip-prefix;机构组成类似于 route-policy:包含多个节点/条目,彼此之间是或的关系;每个节点/条目中,可以有1个条件(if-math),也可以多个条件(if-math),彼此之间与的关系;每个节点/条目中,可以有apply语句,就是修改数据包;如https://developer.aliyun.com/article/1436981
8.埋头刷了大半年Java面试题:如愿拿到众多大厂offer!分享还愿在工作中,SQL语句的优化和注意的事项 哪些库或者框架用到NIO Spring 都有哪几种注入方式,什么情况下用哪种,ioc实现原理 如何定位一个慢查询,一个服务有多条SQL你怎么快速定位 聚集索引和非聚集索引知道吗?什么情况用聚集索引什么情况用非聚集索引 Nosql引擎用的什么存储结构,关系型数据库和NoSQL各自的优劣点是什么https://maimai.cn/article/detail?fid=1735736133&efid=KbtUeCVp4epTWWihu6i76A