简介
Room
在SQLite之上提供了一层抽象,能让我们在使用SQLite全部功能的同时还能流畅的对数据库进行访问。Room
基于注解对SQLite进行了大量封装,用法十分简洁且功能强大并能与RxJava/LiveData
等无缝结合使用,因此官方强烈推荐使用Room
来替代SQLite。
导入依赖
导入依赖只需要前2行就够了,可以选择是否需要搭配RxJava
使用:
1 | implementation "android.arch.persistence.room:runtime:1.1.1" |
Room的架构
在Room
里面主要有3个比较重要的组件:
- Database: 数据库的持有者,它是底层数据库连接的主要接入点。
- Entity: 表示数据库中的表。
- DAO: 包含用于访问数据库的方法。
这些组件与程序中其他部分的调用关系如下所示:
基本使用
下面将介绍Room
中3个重要组件的基本使用,Room
中大量使用注解来帮我们生成实现代码,能让我们更加专注于业务而不是写各种模板代码。有用过Retrofit
的童鞋应该对这深有体会~
Entity
在Room
中,对于每个Entity对象,最终都对应数据库中的一个表。
(1)下面的代码展示了如何定义一个基本的Entity:
1 | "users") (tableName = |
- 使用
@Entity
标记这个类为Room
中的Entity,可以通过tableName
属性来指定表名,如果不指定则默认使用类名 - 默认情况下,
Room
会为每个实体对象的成员生成数据列,可以用注解@Ignore
进行排除 - 可以通过
@PrimaryKey
指定主键,使用autoGenerate
属性可指定是否需要自动生成主键值 - 可以通过
@ColumnInfo
中的name
属性指定列名,如果不指定则默认使用变量名
(2)通过@ForeignKey
注解可以实现在Entity之间定义外键约束,其中entity
属性指定了需要关联的父Entity,parentColumns
指定了需要关联到父Entity的字段名,childColumns
则是当前Entity对应外键的字段名。我们还可以通过onDelete
和onUpdate
属性来指定当前Entity要如何响应父Entity的变化。
1 | .class, (foreignKeys = (entity = UserInfo |
(3)通过@Embedded
注解,我们可以在Entity中加入嵌套对象,这样子我们就可以在Entity中直接引用整个POJO了。如下面的例子,在users表中,会新增street, state, city
这3个数据列。当我们插入或查询数据的时候,Room
将会帮我们处理好字段与数据表的映射关系。
1 | public class Address { |
DAO(Data Access Object)
在Room
中,每个DAO对象都包含提供对数据库访问的抽象方法,且DAO对象只能是接口或者抽象类并用@Dao
注解进行修饰。下面我们来看一段典型的实现代码,定义好增删查改的接口,就像Retrofit
中通过注解声明接口一样:
1 |
|
(1)通过@Insert
注解可以把方法中所有的参数当做一次事务批量插入到数据库中,通过onConflict
属性我们可以指定冲突出现时Room
该如何操作数据。如果insert()
方法只有一个参数,返回值可以修改为long
,此时将会返回插入对象的rowId
。若是插入多个对象,则返回值应该是long[]
或者List<Long>
。
(2)@Update
和@Delete
比较类似,可以更新/删除多个对象。返回值可以是int
,代表了更新/删除Item的总数。
(3)@Query
是最重要的DAO注解,它允许你对数据库执行读/写操作。并且每个query方法都在编译时进行验证,如果出现SQL语法问题则会直接编译错误。在高版本的Android Studio
中,还提供了表名/字段名的智能补全功能,十分强大。
(4)加入可观察的查询
使用
LiveData
可使我们的数据在更新后能及时的通知到UI,只需简单包装下方法的返回值即可。1
2
3
4
5
public interface UserDao {
"SELECT first_name, last_name FROM users WHERE region IN (:regions)") (
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}使用
RxJava
进行响应式查询,并且可以结合Rx强大的操作符做完成各种骚操作~1
2
3
4
5
public interface UserDao {
"SELECT * from users where id = :id LIMIT 1") (
public Flowable<User> loadUserById(int id);
}
注意:在Room中,只支持
Maybe
,Single
和Flowable
三种返回类型,Observable
暂时不支持,会在编译期报错Error: Not sure how to convert a Cursor to this method's return type
。关于三种返回类型的具体使用场景,可见官方推荐的这篇文章:Room and RxJava。
- 返回原始的
Cursor
对象(官方不推荐使用,因为他不保证行是否存在或者行包含了什么数据)1
2
3
4
5
public interface UserDao {
"SELECT * FROM users WHERE age > :minAge LIMIT 5") (
public Cursor loadRawUsersOlderThan(int minAge);
}
Database
Entity与DAO都准备好了,剩下工作就是初始化Database
了。在Room
中声明我们自定义的Database
有几个比较关键的地方:
- 继承
RoomDatabase
并且需要是抽象类。 - 通过
@Database
注解声明数据库类,并且通过entities
属性指定包含哪些数据表,version
属性可以指定数据库版本号,用于后续数据库升级。 - 返回
@DAO
标记的DAO对象的方法必须是0参数的抽象方法。
一切就绪,调用Room.databaseBuilder()
方法即可生成我们的Database
实例。一般情况下建议采用单例模式初始化Database
对象,因为初始化的代价十分高,并且很少会用到多个实例的情况,参考代码如下:
1 | .class}, version = 1) (entities = {UserInfo |
注意:在DAO中所有的数据库操作都不能在主线程中进行,否则
Room
会直接抛异常。当然,你也可以通过RoomDatabase.Builder.allowMainThreadQueries()
方法来解除这个限制,但建议还是遵循官方的建议,采用异步的方式进行或者通过LiveData
及RxJava
等异步框架实现。