翻译自android官网,可直接去官网观看
Android的Jetpack的一部分。
处理大量数据的应用程序可以从本地持久存储这些数据中受益匪浅。最常见的用例是缓存相关数据,以便当设备无法访问网络时,用户仍可以在脱机时浏览该内容。设备重新连接到网络后,用户发起的所有内容更改都会同步到服务器。
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。Room特别提供以下好处:
由于 Room 负责为您处理这些问题,因此我们强烈建议您使用 Room(而不是 SQLite)。不过,如果您想直接使用 SQLite API,请参阅使用 SQLite 保存数据。
如需在应用中使用 Room,请将以下依赖项添加到应用的 build.gradle 文件。
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// optional - Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
Room 包含 3 个主要组件:
数据库类为您的应用程序提供了与该数据库关联的DAO实例。反过来,应用程序可以使用DAO作为关联数据实体对象的实例从数据库检索数据。该应用程序还可以使用定义的数据实体来更新对应表中的行,或创建要插入的新行。图1说明了Room的不同组件之间的关系。
图1.Room 架构图
本节介绍了具有单个数据实体和单个DAO的Room数据库的示例实现。
以下代码定义了一个User数据实体。每个的实例User 代表user应用程序数据库中表中的一行。
@Entity
public class User {
@PrimaryKey
public int uid;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
要了解有关Room中数据实体的更多信息,请参阅使用Room实体定义数据。
以下代码定义了一个名为UserDao的DAO 。UserDao提供应用程序的其余部分用来与user表中的数据进行交互的方法。
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
List<User> getAll();
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
List<User> 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(User... users);
@Delete
void delete(User user);
}
以下代码定义了一个AppDatabase用于保存数据库的类。 AppDatabase定义数据库配置,并用作应用程序对持久数据的主要访问点。数据库类必须满足以下条件:
@Database(entities = {
User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
注意:如果您的应用程序在单个进程中运行,则在实例化AppDatabase 对象时应遵循单例设计模式。每个RoomDatabase实例都非常昂贵,您几乎不需要在单个进程中访问多个实例。
如果您的应用程序在多个进程中运行,请在数据库构建器调用中包含 enableMultiInstanceInvalidation()。这样,当AppDatabase 在每个进程中都有一个实例时,可以在一个进程中使共享数据库文件无效,并且这种无效性会自动传播到AppDatabase其他进程中的实例 。
定义数据实体,DAO和数据库对象之后,可以使用以下代码创建数据库实例:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
然后,您可以使用AppDatabase中的抽象方法获得DAO的实例。反过来,您可以使用DAO实例中的方法与数据库进行交互:
UserDao userDao = db.userDao();
List<User> users = userDao.getAll();
使用 Room 持久性库时,您可以将相关字段集定义为实体。对于每个实体,系统会在关联的 Database 对象中创建一个表,以存储这些项。您必须通过 Database 类中的 entities 数组引用实体类。
注意:如需在应用中使用实体,请向应用的 build.gradle 文件添加架构组件工件。
以下代码段展示了如何定义实体:
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
}
要保留某个字段,Room 必须拥有该字段的访问权限。您可以将某个字段设为公开字段,也可以为其提供 getter 和 setter。如果使用 getter 和 setter 方法,请注意,这些方法需遵循 Room 中的 JavaBeans 规范。
注意:实体可以具有空的构造函数(如果相应的 DAO 类可以访问保留的每个字段),也可以具有其参数包含的类型和名称与该实体中字段的类型和名称匹配的构造函数。Room 还可以使用完整或部分构造函数,例如仅接收部分字段的构造函数。
每个实体必须将至少 1 个字段定义为主键。即使只有 1 个字段,您仍然需要为该字段添加 @PrimaryKey 注释。此外,如果您想让 Room 为实体分配自动 ID,则可以设置 @PrimaryKey 的 autoGenerate 属性。如果实体具有复合主键,您可以使用 @Entity 注释的 primaryKeys 属性,如以下代码段所示:
@Entity(primaryKeys = {
"firstName", "lastName"})
public class User {
public String firstName;
public String lastName;
}
默认情况下,Room 将类名称用作数据库表名称。如果您希望表具有不同的名称,请设置 @Entity 注释的 tableName 属性,如以下代码段所示:
@Entity(tableName = "users")
public class User {
// ...
}
注意:SQLite 中的表名称不区分大小写。
与 tableName 属性类似,Room 将字段名称用作数据库中的列名称。如果您希望列具有不同的名称,请将 @ColumnInfo 注释添加到字段,如以下代码段所示:
@Entity(tableName = "users")
public class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
默认情况下,Room 会为实体中定义的每个字段创建一个列。如果某个实体中有您不想保留的字段,则可以使用 @Ignore 为这些字段添加注释,如以下代码段所示:
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
@Ignore
Bitmap picture;
}
如果实体继承了父实体的字段,则使用 @Entity 属性的 ignoredColumns 属性通常会更容易:
@Entity(ignoredColumns = "picture")
public class RemoteUser extends User {
@PrimaryKey
public int id;
public boolean hasVpn;
}
Room 支持多种类型的注释,可让您更轻松地搜索数据库表中的详细信息。除非应用的 minSdkVersion 低于 16,否则请使用全文搜索。
如果您的应用需要通过全文搜索 (FTS) 快速访问数据库信息,请使用虚拟表(使用 FTS3 或 FTS4 SQLite 扩展模块)为您的实体提供支持。要使用此功能(在2.1.0版或更高版本的Room中可用),请将 @Fts3 或 @Fts4 注释添加到给定实体,如以下代码段所示:
// Use `@Fts3` only if your app has strict disk space requirements or if you
// require compatibility with an older SQLite version.
@Fts4
@Entity(tableName = "users")
public class User {
// Specifying a primary key for an FTS-table-backed entity is optional, but
// if you include one, it must use this type and column name.
@PrimaryKey
@ColumnInfo(name = "rowid")
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
}
注意:启用 FTS 的表始终使用 INTEGER 类型的主键且列名称为“rowid”。如果是由 FTS 表支持的实体定义主键,则必须使用相应的类型和列名称。
如果表支持以多种语言显示的内容,请使用 languageId 选项指定用于存储每一行语言信息的列:
@Fts4(languageId = "lid")
@Entity(tableName = "users")
public class User {
// ...
@ColumnInfo(name = "lid")
int languageId;
}
Room提供了其他一些用于定义FTS支持的实体的选项,包括结果排序,标记符类型和作为外部内容管理的表。如需详细了解这些选项,请参阅 FtsOptions 参考文档。
如果您的应用必须支持不允许使用由 FTS3 或 FTS4 表支持的实体的 SDK 版本,您仍可以将数据库中的某些列编入索引,以加快查询速度。如需为实体添加索引,请在 @Entity 注释中添加 indices 属性,列出要在索引或复合索引中包含的列的名称。以下代码段演示了此注释过程:
@Entity(indices = {
@Index("name"),
@Index(value = {
"last_name", "address"})})
public class User {
@PrimaryKey
public int id;
public String firstName;
public String address;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
有时,数据库中的某些字段或字段组必须是唯一的。您可以通过将 @Index 注释的 unique 属性设为 true,强制实施此唯一性属性。以下代码示例可防止表格具有包含 firstName 和 lastName 列的同一组值的两行。
@Entity(indices = {
@Index(value = {
"first_name", "last_name"},
unique = true)})
public class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
注意:此功能仅在基于Java的实体中使用。如需在基于 Kotlin 的实体中实现相同的功能,最好改用数据类。
在 Room 2.1.0 及更高版本中,您可以将基于 Java 的不可变值类(使用 @AutoValue 进行注释)用作应用数据库中的实体。此支持在实体的两个实例被视为相等(如果这两个实例的列包含相同的值)时尤为有用。
将带有 @AutoValue 注释的类用作实体时,您可以使用 @PrimaryKey、@ColumnInfo、@Embedded 和 @Relation 为该类的抽象方法添加注释。但是,您必须在每次使用这些注释时添加 @CopyAnnotations 注释,以便 Room 可以正确解释这些方法的自动生成实现。
以下代码段展示了一个使用 @AutoValue 进行注释的类(Room 将其标识为实体)的示例:
User.java
@AutoValue
@Entity
public abstract class User {
// Supported annotations must include `@CopyAnnotations`.
@CopyAnnotations
@PrimaryKey
public abstract long getId();
public abstract String getFirstName();
public abstract String getLastName();
// Room uses this factory method to create User objects.
public static User create(long id, String firstName, String lastName) {
return new AutoValue_User(id, firstName, lastName);
}
}
使用Room持久性库存储应用程序的数据时,可以通过定义数据访问对象或DAO与存储的数据进行交互。每个DAO都包含提供对应用程序数据库的抽象访问的方法。在编译时,Room会自动生成您定义的DAO的实现。
通过使用 DAO 类(而不是查询构建器或直接查询)访问数据库,您可以拆分数据库架构的不同组件。此外,借助 DAO,您可以在测试应用时轻松模拟数据库访问。
您可以将每个DAO定义为接口或抽象类。对于基本用例,通常应使用一个接口。无论哪种情况,都必须始终使用@Dao注释DAO。DAO没有属性,但是它们确实定义了一种或多种与应用程序数据库中的数据进行交互的方法。
以下代码是一个简单DAO的示例,该DAO定义了User在Room数据库中插入,删除和选择对象的方法:
@Dao
public interface UserDao {
@Insert
void insertAll(User... users);
@Delete
void delete(User user);
@Query("SELECT * FROM user")
List<User> getAll();
}
定义数据库交互的DAO方法有两种:
以下各节说明如何使用两种DAO方法来定义应用程序需要的数据库交互。
Room提供了方便的注释,用于定义执行简单插入,更新和删除的方法,而无需您编写SQL语句。
如果需要定义更复杂的插入,更新或删除,或者需要查询数据库中的数据,请改用查询方法。
该@Insert注释允许您定义插入其参数到数据库中相应的表的方法。以下代码显示@Insert了将一个或多个User对象插入数据库的有效方法的示例:
@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 注释的参考文档以及rowid表的SQLite文档。
该@Update注释允许您定义方法,在数据库表更新特定行。与@Insert方法类似 ,@Update方法接受数据实体实例作为参数。以下代码显示了@Update尝试更新User数据库中一个或多个对象的方法的示例:
@Dao
public interface UserDao {
@Update
public void updateUsers(User... users);
}
Room使用主键将传递的实体实例与数据库中的行进行匹配。如果没有行具有相同的主键,则Room不会进行任何更改。
@Update方法可任选地返回一个int指示成功更新的行的数目的值。
该@Delete注释允许您定义的方法,从一个数据库表中删除特定行。与@Insert方法类似 ,@Delete方法接受数据实体实例作为参数。以下代码显示了@Delete一种尝试User从数据库中删除一个或多个对象的方法的示例:
@Dao
public interface UserDao {
@Delete
public void deleteUsers(User... users);
}
Room使用主键将传递的实体实例与数据库中的行进行匹配。如果没有行具有相同的主键,则Room不会进行任何更改。
@Delete方法可任选地返回一个int指示已成功删除的行数的值。
@Query注释允许您编写SQL语句将它们公开为DAO方法。当您需要执行更复杂的插入,更新和删除操作时,可以使用这些查询方法从应用程序数据库中查询数据。
Room会在编译时验证SQL查询。这意味着,如果查询存在问题,则会发生编译错误,而不是运行时错误。
以下代码定义了一种方法,该方法使用simple SELECT query来返回User数据库中的所有对象:
@Query("SELECT * FROM user")
public User[] loadAllUsers();
以下各节演示了如何针对典型用例修改此示例。
大多数情况下,您只需要从查询表中返回列的子集。例如,您的UI可能仅显示用户的名字和姓氏,而不是有关该用户的所有详细信息。为了节省资源并简化查询的执行,您只应查询所需的字段。
Room允许您从任何查询中返回简单对象,只要您可以将结果列集映射到返回的对象上即可。例如,您可以定义以下对象来保存用户的名字和姓氏:
public class NameTuple {
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
@NonNull
public String lastName;
}
然后,您可以从查询方法中返回该简单对象:
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
Room知道查询返回first_name和last_name列的值,并且这些值可以映射到NameTuple类中的字段上。如果查询返回的列未映射到返回对象的字段上,则Room将显示警告。
大多数时候,您的DAO方法需要接受参数,以便它们可以执行过滤操作。Room支持在查询中使用方法参数作为绑定参数。
例如,以下代码定义了一种返回特定年龄以上所有用户的方法:
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
您还可以在查询中多次传递多个参数或引用同一参数,如以下代码所示:
@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会了解参数何时表示集合,并会在运行时根据提供的参数数量自动扩展它。
例如,以下代码定义了一种方法,该方法从区域子集中返回有关所有用户的信息:
@Query("SELECT * FROM user WHERE region IN (:regions)")
public List<User> loadUsersFromRegions(List<String> regions);
您的某些查询可能需要访问多个表才能计算结果。您可以在SQL查询中使用JOIN子句来引用多个表。
以下代码定义了一种方法,该方法将三个表连接在一起,以将当前借出的书返回给特定用户:
@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,该方法可返回用户名和他们借用的书名:
@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;
}
}
Room提供了一些特殊的返回类型以与其他API库集成。
Room通过与分页库集成来支持分页查询。在Room 2.3.0-alpha01和更高版本中,DAO可以返回 PagingSource用于Paging 3的对象。
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE label LIKE :query")
PagingSource<Integer, User> pagingSource(String query);
}
有关为分页源选择类型参数的详细信息,请参见选择键和值类型。
如果您的应用程序逻辑需要直接访问返回行,则可以编写DAO方法以返回Cursor 对象,如以下示例所示:
@Dao
public interface UserDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}
警告:强烈建议不要使用Cursor API,因为它不能保证行存在或行包含哪些值。仅当您已经有需要游标且无法轻松重构的代码时,才使用此功能。