Android Developers官方文档翻译:Room(三)

使用Room的DAOs获取数据

要获取使用Room持久化库中保存的app的数据,你需要使用data access objects,或者称为DAOs。这组Dao对象是Room的主要组件,因为每个DAO都包含了对app数据库的抽象访问的方法。

通过DAO类来访问数据库而不是通过直接查询或者查询builder,你可以解耦数据库架构中的不同组件。此外,DAO允许你在测试app时轻松模拟数据库的访问。

提醒:开始把DAO类添加到你的应用前,请先添加Adding Components to your Project相关依赖到你app module目录的build.gradle文件中。

一个DAO可以以一个接口或者抽象类的形式存在。如果以一个抽象类形式存在,可以定义一个只有RoomDatabase作为参数的构造方法。Room将在编译的时候为每个DAO生成具体的实现代码。

提醒:Room默认不支持在主线程中调用,因为这样可能会造成UI锁住很长时间,如果你非要在主线程中调用,你需要在builder中调用allowMainThreadQueries())方法。而异步查询,返回LiveData或Flowable实例不受这个规则的约束,因为他们本身就是在后台线程上异步运行查询。

定义抽象方法

(插一句:原文是Define methods for convenience,直译:定义简单方法/定义便利的方法?嗯。。。是不是我打开方式不对)

你可以使用DAO类进行简单的查询,接下来的文档中会展示几个例子。

Insert

当你在DAO中新建一个@Insert注解的方法,Room将会在编译时自动生成一个实现,这个实现会插入你在方法中传入的所有参数。

简单的示例如下:

1
2
3
4
5
6
7
8
9
10
11
@Dao
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);

@Insert
public void insertBothUsers(User user1, User user2);

@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}

如果使用@Insert注解的方法只接收一个参数,这个方法可以返回一个long型返回值,这个返回值是新插入的item对应的rowId(数据表中的行下标)。如果参数是一个数组或者集合,那么应该返回一个long[]或者List

想了解更多信息,可以查阅@Insert注解的文档,另外可以查阅SQLite documentation for rowid tables

Update

根据提供的一组的实体参数,Update方法可以修改这组实体数据在数据库中的值。在修改的过程中使用到实体定义的主键进行匹配查询。

示例代码如下:

1
2
3
4
5
@Dao
public interface MyDao {
@Update
public void updateUsers(User... users);
}

一般来说update方法不需要返回值,但如果有需要的话,你仍然可以返回一个int型返回值,这个值表示影响了数据库多少行。

Delete

根据提供的一组实体参数,Delete方法可以在数据库中删除这组实体数据。在删除的过程中使用到实体定义的主键进行匹配查询。

简单的示例如下:

1
2
3
4
5
@Dao
public interface MyDao {
@Delete
public void deleteUsers(User... users);
}

一般来说delete方法不需要返回值,但如果有需要的话,你仍然可以返回一个int型返回值,这个值表示影响了数据库多少行。

查询数据

@Query在DAO类中是一个主要的注解。通过使用了这个注解的方法可以读写数据库。每一个@Query方法在编译的时候都会经过合法性校验,所以如果一个查询语句有问题的时候会在编译阶段反映出来,而不会等到运行时才出现。

Room同样会校验返回值的合法性,如果一个返回对象中属性的名称与查询中相对应的列名称不匹配,则Room会以以下两种方式之一来提示:

  • 警告:仅有部分属性名称匹配
  • 错误:没有匹配的属性名称

简单查询

1
2
3
4
5
@Dao
public interface MyDao {
@Query("SELECT * FROM user")
public User[] loadAllUsers();
}

这是一个查询所有用户的非常简单的例子。在编译时,Room知道这个方法将要在user表中查询所有字段。如果查询语句包含语法错误,或者数据库中不存在user表,Room将在你的app编译的时候显示相应的信息来提醒你。

按传递进来的参数作为条件查询

大多数情况下,你可能需要通过特定的参数来过滤查询的结果,例如查询超过特定年龄的用户,要执行这个操作,需要在Room的注解中使用到你传进来的参数,下面是简单的代码示例:

1
2
3
4
5
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}

在编译处理上面这个查询时,Room会将:minAge与传进来的参数minAge进行绑定,Room直接使用名称作为参数的绑定依据,如果注解的参数名称与方法中传递进来的参数名称不匹配,就会在编译的时候报错。

上面是传递一个参数的例子,你同样可以在查询的时候传递多个参数来作为查询中的筛选条件,diamante示例如下:

1
2
3
4
5
6
7
8
9
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);

@Query("SELECT * FROM user WHERE first_name LIKE :search "
+ "OR last_name LIKE :search")
public List<User> findUserWithName(String search);
}

查询一个实体中的字段

多数情况下,你只是需要查询一个实体类中的部分属性。举个例子,你的UI伤可能只需要展示用户的姓和名,而不是用户的所有信息。这种只查询你需要展示在UI上的字段方式不仅可以节省宝贵的内存,而且查询速度也更快。

Room允许您从查询中返回任何Java Bean的对象,只要结果集可以映射到返回的对象就可以了。例如,你可以创建以下Java Bean来获取用户姓和名,组成姓名的形式返回。

下面是简单的代码示例:

1
2
3
4
5
6
7
public class NameTuple {
@ColumnInfo(name="first_name")
public String firstName;

@ColumnInfo(name="last_name")
public String lastName;
}

然后你可以在查询中返回含有你定义的Java Bean形式:

1
2
3
4
5
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
}

Room会知道要查询的是first_name和last_name的值,因为这些值可以映射到NameTuple类的属性中,所以,Room内部会生成正确的查询语句代码。如果查询返回太多列(应该是想表达没一一对应),或者返回在NameTuple中不存在的一列,Room在编译的时候将会显示警告。

提醒:这些POJOs(也就是前面说的Java Bean)还可以通过使用@Embedded关键字来嵌入父类entity中达到相同的查询效果

以一个集合作为查询参数

有时候,你可能会有一些查询需要用到可变数量的参数,并且在运行之前(也就是写代码的时候)是不知道确切的参数数量的。例如,你希望在一个集合范围内查询相关联的所有用户的信息,Room可以感知到集合的参数并在运行的时候动态来生成SQL语句展开这些参数的值。示例如下:

1
2
3
4
5
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public List<NameTuple> loadUsersFromRegions(List<String> regions);
}

可感知的查询

当执行一次查询的时候,你肯定希望你的UI自动在查询到数据有改动的时候进行更新。要实现这个的话,可以利用LiveData来实现,在查询的时候返回的类型为LiveData即可,在数据库数据更新的时候,Room会自动完成LiveData所需的更新。示例代码如下:

1
2
3
4
5
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}

提醒:从1.0版本开始,Room使用访问的表的列表来决定是否要更新LiveData实例。

使用RxJava执行响应式查询

Room查询中同样可以返回RxJava2中的PublisherFlowable类型。要使用这个特性,需要添加android.arch.persistence.room:rxjava2扩展到你的build.gradle中的依赖中去。这样你就可以直接返回RxJava2定义的类型了,示例代码如下:

1
2
3
4
5
@Dao
public interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);
}

想了解更多信息可以查看Google Developers中的Room and RxJava这篇文章。

直接获取游标(Cursor)

如果你的app的逻辑要求直接返回一行原始数据,你可以从查询中返回一个Cursor对象,如下代码所示:

1
2
3
4
5
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}

注意:尽量不要使用Cursor的API,因为他不能保证所查询的行是否存在或者一行里面是否包含有某个值。请只有在你需要Cursor且历史代码难以重构的情况下再考虑使用这个API。

多表查询

有时候你的查询可能需要联合多张数据表来查询出最终的结果,Room允许你做表连接查询。此外,如果所查询的数据是可观察的类型的话,比如Flowable或者LiveData,Room将观察查询中涉及到的所有表。

接下来的示例代码将展示如何用一个表连接查询来查出借阅图书的用户(user表中的数据)对应的所借阅(loan表中的数据)的图书(book表中的数据)的信息。

1
2
3
4
5
6
7
8
@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<Book> findBooksBorrowedByNameSync(String userName);
}

你也可以返回POJO对象(Java Bean)。比如下面代码所示,你可以写一个查询语句来查询用户和他们宠物名称对应关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Dao
public interface MyDao {
@Query("SELECT user.name AS userName, pet.name AS petName "
+ "FROM user, pet "
+ "WHERE user.id = pet.user_id")
public LiveData<List<UserPet>> loadUserAndPetNames();


// You can also define this class in a separate file, as long as you add the
// "public" access modifier.
static class UserPet {
public String userName;
public String petName;
}
}

原文链接:https://developer.android.com/training/data-storage/room/accessing-data

文章作者: Kevin Wu
文章链接: https://kevinwu.cn/p/717ed5d8/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 KevinWu的博客
支付宝打赏