@Ignore
用于告诉Room需要忽略的字段或方法- 建立索引:在
@Entity
注解的indices
属性中添加索引字段。例如:indices = {@Index(value = {"first_name", "last_name"}, unique = true), ...}
,unique = true
可以确保表中不会出现{"first_name", "last_name"}
相同的数据。
1.2 Entitiy间的关系
不同于目前存在的大多数ORM库,Room不支持Entitiy对象间的直接引用。(具体原因可以参考: Understand why Room doesn’t allow object references)
但Room允许通过**外键(Foreign Key)**来表示Entity之间的关系。
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = “id”,
childColumns = “user_id”))
class Book {
@PrimaryKey
public int bookId;
public String title;
@ColumnInfo(name = “user_id”)
public int userId;
}
如上面代码所示,Book对象与User对象是属于的关系。Book中的user_id,对应User中的id。 那么当一个User对象被删除时, 对应的Book会发生什么呢?
@ForeignKey
注解中有两个属性onDelete
和onUpdate
, 这两个属性对应ForeignKey
中的onDelete()
和onUpdate()
, 通过这两个属性的值来设置当User对象被删除/更新时,Book对象作出的响应。这两个属性的可选值如下:
CASCADE
:User删除时对应Book一同删除; 更新时,关联的字段一同更新NO_ACTION
:User删除时不做任何响应RESTRICT
:禁止User的删除/更新。当User删除或更新时,Sqlite会立马报错。SET_NULL
:当User删除时, Book中的userId会设为NULLSET_DEFAULT
:与SET_NULL
类似,当User删除时,Book中的userId会设为默认值
1.3 对
象嵌套
在某些情况下, 对于一张表中的数据我们会用多个POJO类来表示,在这种情况下可以用@Embedded
注解嵌套的对象,比如:
class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = “post_code”)
public int postCode;
}
@Entity
class User {
@PrimaryKey
public int id;
public String firstName;
@Embedded
public Address address;
}
以上代码所产生的User表中,Column 为id, firstName, street, state, city, post_code
2. 创建数据访问对象(DAO)
@Dao
public interface UserDao {
@Query(“SELECT * FROM user”)
List getAll();
@Query(“SELECT * FROM user WHERE uid IN (:userIds)”)
List loadAllByIds(int[] userIds);
@Query("SELECT * FROM user WHERE first_name LIKE :first AND "
- “last_name LIKE :last LIMIT 1”)
User findByName(String first, String last);
@Insert
void insertAll(List users);
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User… users);
@Delete
void delete(User user);
@Update
public void updateUsers(List users);
}
DAO 可以是一个接口,也可以是一个抽象类, Room会在编译时创建DAO的实现。
Tips:
@Insert
方法也可以定义返回值, 当传入参数仅有一个时返回long
, 传入多个时返回long[]
或List<Long>
, Room在实现insert方法的实现时会在一个事务进行所有参数的插入。@Insert
的参数存在冲突时, 可以设置onConflict
属性的值来定义冲突的解决策略, 比如代码中定义的是@Insert(onConflict = OnConflictStrategy.REPLACE)
, 即发生冲突时替换原有数据@Update
和@Delete
可以定义int
类型返回值,指更新/删除的函数
DAO中的增删改方法的定义都比较简单,这里不展开讨论,下面更多的聊一下查询方法。
2.1 简单的查询
Talk is cheap, 直接show code:
@Query(“SELECT * FROM user”)
List getAll();
2.2 查询参数传递
@Query(“SELECT * FROM user WHERE uid IN (:userIds)”)
List loadAllByIds(int[] userIds);
@Query("SELECT * FROM user WHERE first_name LIKE :first AND "
- “last_name LIKE :last LIMIT 1”)
User findByName(String first, String last);
看代码应该比较好理解, 方法中传递参数arg
, 在sql语句中用:arg
即可。编译时Room会匹配对应的参数。
2.3 查询表中部分字段的信息
在实际某个业务场景中, 我们可能仅关心一个表部分字段的值,这时我仅需要查询关心的列即可。
定义子集的POJO类:
public class NameTuple {
@ColumnInfo(name=“first_name”)
public String firstName;
@ColumnInfo(name=“last_name”)
public String lastName;
}
在DAO中添加查询方法:
@Query(“SELECT first_name, last_name FROM user”)
public List loadFullName();
2.3 查询结果的返回类型
Room中查询操作除了返回POJO对象及其List以外, 还支持:
LiveData<T>
:
LiveData是架构组件库中提供的另一个组件,可以很好满足数据变化驱动UI刷新的需求。Room会实现更新LiveData的代码。
@Query(“SELECT first_name, last_name FROM user WHERE region IN (:regions)”)
public LiveData<List> loadUsersFromRegionsSync(List regions);
Flowablbe<T>
Maybe<T>
Single<T>
:
Room 支持返回RxJava2 的Flowablbe
,Maybe
和Single
对象,对于使用RxJava的项目可以很好的衔接, 但需要在gradle添加该依赖:android.arch.persistence.room:rxjava2
。
@Query(“SELECT * from user where id = :id LIMIT 1”)
public Flowable loadUserById(int id);
Cursor
:
返回Cursor是为了支持现有项目中使用Cursor的场景,官方不建议直接返回Cursor.
2.4 联表查询
Room支持联表查询,接口定义上与其他查询差别不大, 主要还是sql语句的差别。
@Dao
public interface MyDao {
@Query("SELECT * FROM book "
- "INNER JOIN loan ON loan.book_id = book.id "
- "INNER JOIN user ON user.id = loan.user_id "
- “WHERE user.name LIKE :userName”)
public List findBooksBorrowedByNameSync(String userName);
}
3. 创建数据库
Room中DataBase类似SQLite API中SQLiteOpenHelper,是提供DB操作的切入点,但是除了持有DB外, 它还负责持有相关数据表(Entity)的数据访问对象(DAO), 所以Room中定义Database需要满足三个条件:
- 继承RoomDataBase,并且是一个抽象类
- 用@Database 注解,并定义相关的entity对象, 当然还有必不可少的数据库版本信息
- 定义返回DAO对象的抽象方法
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
创建好以上Room的三大组件后, 在代码中就可以通过以下代码创建Database实例。
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, “database-name”).build();
三、数据库迁移
3.1 Room数据库升级
在传统的SQLite API中,我们如果要升级数据库, 通常在SQLiteOpenHelper.onUpgrade
方法执行数据库升级的sql语句,这些sql语句的通常根据数据库版本以文件的方式或者用数组来管理。有人说这种方式升级数据库就像在拆炸弹,相比之下在Room中升级数据库简单的就像是按一个开关而已。
Room提供了Migration类来实现数据库的升级:
Room.databaseBuilder(getApplicationContext(), MyDb.class, “database-name”)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE Fruit
(id
INTEGER, "
- "
name
TEXT, PRIMARY KEY(id
))");
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
ation(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE Fruit
(id
INTEGER, "
- "
name
TEXT, PRIMARY KEY(id
))");
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {