当您使用 Room 持久性库存储应用数据时,您可以通过定义数据访问对象 (DAO) 来与存储的数据进行交互。每个 DAO 都包含提供对应用数据库进行抽象访问的方法。在编译时,Room 会自动生成您定义的 DAO 的实现。
通过使用 DAO 而非查询构建器或直接查询来访问应用数据库,您可以保持关注点分离这一重要的架构原则。DAO 还能让您在测试应用时更轻松地模拟数据库访问。
DAO 的剖析
您可以将每个 DAO 定义为接口或抽象类。对于基本用例,您通常使用接口。无论哪种情况,都必须始终使用 @Dao
注解您的 DAO。DAO 没有属性,但它们定义了一个或多个用于与应用数据库中的数据进行交互的方法。
以下代码是一个简单 DAO 的示例,它定义了在 Room 数据库中插入、删除和选择 User
对象的方法
Kotlin
@Dao interface UserDao { @Insert fun insertAll(vararg users: User) @Delete fun delete(user: User) @Query("SELECT * FROM user") fun getAll(): List<User> }
Java
@Dao public interface UserDao { @Insert void insertAll(User... users); @Delete void delete(User user); @Query("SELECT * FROM user") List<User> getAll(); }
有两种类型的 DAO 方法用于定义数据库交互
- 便捷方法,可让您在不编写任何 SQL 代码的情况下插入、更新和删除数据库中的行。
- 查询方法,可让您编写自己的 SQL 查询来与数据库进行交互。
以下部分演示了如何使用这两种类型的 DAO 方法来定义应用所需的数据库交互。
便捷方法
Room 提供了便捷注解,用于定义执行简单插入、更新和删除操作的方法,无需您编写 SQL 语句。
如果您需要定义更复杂的插入、更新或删除操作,或者需要查询数据库中的数据,请改用查询方法。
插入
@Insert
注解可让您定义将参数插入数据库中相应表的方法。以下代码显示了有效的 @Insert
方法的示例,这些方法将一个或多个 User
对象插入数据库
Kotlin
@Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertUsers(vararg users: User) @Insert fun insertBothUsers(user1: User, user2: User) @Insert fun insertUsersAndFriends(user: User, friends: List<User>) }
Java
@Dao public interface UserDao { @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
方法的每个参数必须是标有 @Entity
注解的 Room 数据实体类的实例,或者是数据实体类实例的集合,每个实例都指向一个数据库。当调用 @Insert
方法时,Room 会将每个传递的实体实例插入到相应的数据库表中。
如果 @Insert
方法接收单个参数,则可以返回 long
值,这是插入项的新 rowId
。如果参数是数组或集合,则返回一个数组或 long
值的集合,其中每个值是插入项之一的 rowId
。要详细了解返回 rowId
值,请参阅 @Insert
注解的参考文档和 SQLite 关于 rowid 表的文档。
更新
@Update
注解可让您定义更新数据库表中特定行的方法。与 @Insert
方法一样,@Update
方法接受数据实体实例作为参数。以下代码显示了 @Update
方法的示例,该方法尝试更新数据库中的一个或多个 User
对象
Kotlin
@Dao interface UserDao { @Update fun updateUsers(vararg users: User) }
Java
@Dao public interface UserDao { @Update public void updateUsers(User... users); }
Room 使用主键将传递的实体实例与数据库中的行匹配。如果不存在具有相同主键的行,则 Room 不会进行任何更改。
一个 @Update
方法可以选择返回一个 int
值,指示成功更新的行数。
删除
@Delete
注解可让您定义从数据库表中删除特定行的方法。与 @Insert
方法一样,@Delete
方法接受数据实体实例作为参数。以下代码显示了 @Delete
方法的示例,该方法尝试从数据库中删除一个或多个 User
对象
Kotlin
@Dao interface UserDao { @Delete fun deleteUsers(vararg users: User) }
Java
@Dao public interface UserDao { @Delete public void deleteUsers(User... users); }
Room 使用主键将传递的实体实例与数据库中的行匹配。如果不存在具有相同主键的行,则 Room 不会进行任何更改。
一个 @Delete
方法可以选择返回一个 int
值,指示成功删除的行数。
查询方法
@Query
注解可让您编写 SQL 语句并将其公开为 DAO 方法。使用这些查询方法来查询应用数据库中的数据,或者在需要执行更复杂的插入、更新和删除操作时使用。
Room 在编译时验证 SQL 查询。这意味着如果您的查询有问题,将会出现编译错误,而不是运行时失败。
简单查询
以下代码定义了一个使用简单 SELECT
查询返回数据库中所有 User
对象的方法
Kotlin
@Query("SELECT * FROM user") fun loadAllUsers(): Array<User>
Java
@Query("SELECT * FROM user") public User[] loadAllUsers();
以下部分演示了如何针对典型用例修改此示例。
返回表列的子集
大多数情况下,您只需要返回查询表中的列的子集。例如,您的 UI 可能只显示用户的姓和名,而不是该用户的全部详细信息。为了节省资源并简化查询执行,只查询您需要的字段。
只要可以将结果列集映射到返回的对象,Room 就允许您从任何查询中返回一个简单对象。例如,您可以定义以下对象来保存用户的姓和名
Kotlin
data class NameTuple( @ColumnInfo(name = "first_name") val firstName: String?, @ColumnInfo(name = "last_name") val lastName: String? )
Java
public class NameTuple { @ColumnInfo(name = "first_name") public String firstName; @ColumnInfo(name = "last_name") @NonNull public String lastName; }
然后,您可以从查询方法中返回该简单对象
Kotlin
@Query("SELECT first_name, last_name FROM user") fun loadFullName(): List<NameTuple>
Java
@Query("SELECT first_name, last_name FROM user") public List<NameTuple> loadFullName();
Room 知道查询会返回 first_name
和 last_name
列的值,并且这些值可以映射到 NameTuple
类中的字段。如果查询返回的列未映射到返回对象中的字段,Room 会显示警告。
向查询传递简单参数
大多数情况下,您的 DAO 方法需要接受参数以便执行过滤操作。Room 支持在查询中使用方法参数作为绑定参数。
例如,以下代码定义了一个方法,用于返回特定年龄以上的所有用户
Kotlin
@Query("SELECT * FROM user WHERE age > :minAge") fun loadAllUsersOlderThan(minAge: Int): Array<User>
Java
@Query("SELECT * FROM user WHERE age > :minAge") public User[] loadAllUsersOlderThan(int minAge);
您还可以向查询传递多个参数或多次引用同一个参数,如下面代码所示
Kotlin
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") fun loadAllUsersBetweenAges(minAge: Int, maxAge: Int): Array<User> @Query("SELECT * FROM user WHERE first_name LIKE :search " + "OR last_name LIKE :search") fun findUserWithName(search: String): List<User>
Java
@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);
向查询传递参数集合
您的一些 DAO 方法可能需要您传递一个在运行时才知道的变长参数列表。Room 理解何时参数表示一个集合,并根据提供的参数数量在运行时自动展开它。
例如,以下代码定义了一个方法,用于返回来自部分地区的所有用户的信息
Kotlin
@Query("SELECT * FROM user WHERE region IN (:regions)") fun loadUsersFromRegions(regions: List<String>): List<User>
Java
@Query("SELECT * FROM user WHERE region IN (:regions)") public List<User> loadUsersFromRegions(List<String> regions);
查询多个表
您的一些查询可能需要访问多个表来计算结果。您可以在 SQL 查询中使用 JOIN
子句来引用多个表。
以下代码定义了一个方法,该方法将三个表连接起来,以返回当前借给特定用户的图书
Kotlin
@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" ) fun findBooksBorrowedByNameSync(userName: String): List<Book>
Java
@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);
您还可以定义简单对象来返回多个连接表中的列的子集,如返回表列的子集部分所述。以下代码定义了一个 DAO,其中包含一个返回用户姓名和他们借阅的图书姓名的方法
Kotlin
interface UserBookDao { @Query( "SELECT user.name AS userName, book.name AS bookName " + "FROM user, book " + "WHERE user.id = book.user_id" ) fun loadUserAndBookNames(): LiveData<List<UserBook>> // You can also define this class in a separate file. data class UserBook(val userName: String?, val bookName: String?) }
Java
@Dao public interface UserBookDao { @Query("SELECT user.name AS userName, book.name AS bookName " + "FROM user, book " + "WHERE user.id = book.user_id") public LiveData<List<UserBook>> loadUserAndBookNames(); // You can also define this class in a separate file, as long as you add the // "public" access modifier. static class UserBook { public String userName; public String bookName; } }
返回多重映射 (multimap)
在 Room 2.4 及更高版本中,您还可以通过编写返回多重映射 (multimap) 的查询方法,在不定义额外数据类的情况下查询多个表中的列。
考虑查询多个表部分中的示例。您无需返回包含 User
和 Book
实例配对的自定义数据类实例列表,而是可以直接从查询方法返回 User
和 Book
的映射
Kotlin
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" ) fun loadUserAndBookNames(): Map<User, List<Book>>
Java
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" ) public Map<User, List<Book>> loadUserAndBookNames();
当您的查询方法返回一个多重映射时,您可以编写使用 GROUP BY
子句的查询,从而利用 SQL 在高级计算和过滤方面的能力。例如,您可以修改 loadUserAndBookNames()
方法,使其仅返回借出三本或更多图书的用户
Kotlin
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" + "GROUP BY user.name WHERE COUNT(book.id) >= 3" ) fun loadUserAndBookNames(): Map<User, List<Book>>
Java
@Query( "SELECT * FROM user" + "JOIN book ON user.id = book.user_id" + "GROUP BY user.name WHERE COUNT(book.id) >= 3" ) public Map<User, List<Book>> loadUserAndBookNames();
如果您不需要映射整个对象,您还可以通过在查询方法的 @MapInfo
注解中设置 keyColumn
和 valueColumn
属性,来返回查询中特定列之间的映射
Kotlin
@MapInfo(keyColumn = "userName", valueColumn = "bookName") @Query( "SELECT user.name AS username, book.name AS bookname FROM user" + "JOIN book ON user.id = book.user_id" ) fun loadUserAndBookNames(): Map<String, List<String>>
Java
@MapInfo(keyColumn = "userName", valueColumn = "bookName") @Query( "SELECT user.name AS username, book.name AS bookname FROM user" + "JOIN book ON user.id = book.user_id" ) public Map<String, List<String>> loadUserAndBookNames();
特殊返回类型
Room 提供了一些特殊的返回类型,用于与其他 API 库集成。
使用 Paging 库进行分页查询
Room 通过与 Paging 库集成来支持分页查询。在 Room 2.3.0-alpha01 及更高版本中,DAO 可以返回 PagingSource
对象,用于 Paging 3。
Kotlin
@Dao interface UserDao { @Query("SELECT * FROM users WHERE label LIKE :query") fun pagingSource(query: String): PagingSource<Int, User> }
Java
@Dao interface UserDao { @Query("SELECT * FROM users WHERE label LIKE :query") PagingSource<Integer, User> pagingSource(String query); }
有关为 PagingSource
选择类型参数的详细信息,请参阅选择键和值类型。
直接游标访问
如果您的应用逻辑需要直接访问返回的行,您可以编写 DAO 方法来返回 Cursor
对象,如以下示例所示
Kotlin
@Dao interface UserDao { @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") fun loadRawUsersOlderThan(minAge: Int): Cursor }
Java
@Dao public interface UserDao { @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") public Cursor loadRawUsersOlderThan(int minAge); }
其他资源
要详细了解如何使用 Room DAO 访问数据,请参阅以下其他资源