概念
Room 是一个持久性库,属于 Android Jetpack 的一部分。Room 是 SQLite 数据库之上的一个抽象层。SQLite 使用一种专门的语言 (SQL) 来执行数据库操作。Room 并不直接使用 SQLite,而是负责简化数据库设置和配置以及与数据库交互方面的琐碎工作。此外,Room 还提供 SQLite 语句的编译时检查。
添加 Room 库
- 打开模块级 gradle 文件
build.gradle (Module: InventoryApp.app)
。在dependencies
块中,为 Room 库添加以下依赖项。
// Room
implementation ("androidx.room:room-runtime:2.2.6")
implementation ("androidx.room:room-ktx:2.2.6")
testImplementation ("androidx.room:room-testing:2.2.6")
Room 三大组件
- 数据实体 Entry 表示应用的数据库中的表。数据实体用于更新表中的行所存储的数据以及创建新行供插入。
- 数据访问对象 (DAO) 提供应用在数据库中检索、更新、插入和删除数据所用的方法。
- 数据库类持有数据库,并且是应用数据库底层连接的主要访问点。数据库类为应用提供与该数据库关联的 DAO 的实例。
创建数据实体 Entry
实体类定义了一个表,该类的每个实例表示数据库表中的一行。实体类以映射告知 Room 它打算如何呈现数据库中的信息并与之交互。
@Entry 注解用于将某个类标记为数据库实体类。对于每个实体类,系统都会创建一个数据库表来保存相关项。除非另行说明,否则实体的每个字段在数据库中都表示为一列(如需了解详情,请参阅实体文档)。
存储在数据库中的每个实体实例都必须有一个主键。主键用于唯一标识数据库表中的每个记录/条目。主键一旦赋值就不能修改,只要它还存在于数据库中,它就表示相应的实体对象。
- 在 数据类声明的上方,为该数据类添加
@Entity
注解。使用tableName
参数为这个实体类指定 SQLite 表的名称。 - 如需将
id
标识为主键,请为id
属性添加@PrimaryKey
注解。将参数autoGenerate
设为true
,让Room
为每个实体生成 ID。这样做可以保证每个商品的 ID 一定是唯一的。 - 为其余属性添加
@ColumnInfo
注解。ColumnInfo
注解用于自定义与特定字段关联的列。例如,使用name
参数时,您可以为字段指定不同的列名称,而不是变量名称。如下所示,使用参数自定义属性名称。此方法类似于使用tableName
为数据库指定不同的名称。
// 账单数据类
@Entity(tableName = "AccountListItemTable")
public class AccountDataItem {
@PrimaryKey(autoGenerate = true)
private int id = 0;
private String money; // 账单金额
private String type; // 消费类别 -餐饮类
private String detail; // 消费详情(备注)
private String data; //消费时间
private int in; // 1.收入 2.支出
public AccountDataItem(String money, String type, String detail, String data, int in) {
this.money = money;
this.type = type;
this.detail = detail;
this.data = data;
this.in = in;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMoney() {
return money;
}
public void setMoney(String money) {
this.money = money;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public int getIn() {
return in;
}
public void setIn(int in) {
this.in = in;
}
@NonNull
@Override
public String toString() {
return getId()+getDetail()+getMoney();
}
}
创建数据访问对象 DAO
数据访问对象 (DAO) 是一种模式,其作用是通过提供抽象接口将持久性层与应用的其余部分分离。这种分离遵循您曾在之前的 Codelab 中接触过的单一责任原则。
DAO 的功能在于,让在底层持久性层执行数据库操作所涉及的所有复杂性都不波及应用的其余部分。这样就可以独立于使用数据的代码更改数据访问层。
public interface AccountDao{
@Insert
void insertAccount(AccountDataItem AccountDataItem);
@Update
void update(AccountDataItem AccountDataItem);
@Query("SELECT * FROM AccountListItemTable")
List<AccountDataItem> getAllData();
@Query("DELETE FROM AccountListItemTable")
void delete();
}
创建数据库实例
在此任务中,您将创建一个 RoomDatabase,它将使用您在上一个任务中创建的 Entity
和 DAO。该数据库类用于定义实体和数据访问对象的列表。它也是底层连接的主要访问点。
- Room 是 SQLite 数据库之上的数据库层。
- Room 可以帮您处理以前需要用 SQLiteOpenHelper 处理的日常任务 SQLiteOpenHelper。
- Room 使用 DAO 向其数据库发出查询。
- 默认情况下,为了避免糟糕的 UI 性能,Room 不允许您在主线程上发出查询。当 Room 查询返回 LiveData 时,查询会自动在后台线程上异步运行。
在LiveData的官方文档中有提到LiveData可以和Room数据库一起使用
也就是说Room查询时可以直接返回一个LiveData对象,给这个LiveData对象添加观察者之后只要数据库数据发生改变都可以收到回调。
- Room 提供 SQLite 语句的编译时检查。
package com.example.accountapp.data;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import com.example.accountapp.data.Dao.AccountListDao;
import com.example.accountapp.data.Entry.AccountDataItem;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Database(entities = {AccountDataItem.class},version = 1,exportSchema = false)
public abstract class AppRoomDataBase extends RoomDatabase {
private static volatile AppRoomDataBase INSTANCE;
public abstract AccountListDao accountListDao();
static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(4);
// 单例模式
public static AppRoomDataBase getDataBase(Context context){
if (INSTANCE == null) {
synchronized (AppRoomDataBase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
AppRoomDataBase.class,
"记账数据库"
)
.build();
}
}
}
return INSTANCE;
}
// public abstract AccountDao accountDao();
}
- Room 数据库类必须是 abstract 并扩展
RoomDatabase
。通常,整个应用程序只需要一个 Room 数据库实例,数据库应该使用单例模式。- 创建一个抽象
RoomDatabase
类,使用@Database
注解将该类注释为 Room 数据库并在其中声明属于数据库的实体并设置版本号。- 每个实体对应于将在数据库中创建的表。数据库迁移超出了本代码实验室的范围,因此我们
exportSchema
在此处将其设置为 false 以避免出现构建警告。在实际应用中,您应该考虑为 Room 设置一个目录以用于导出架构,以便您可以将当前架构签入版本控制系统。- 通过每个@Dao的抽象“getter”方法来公开 DAO。
- 创建了一个
ExecutorService
固定的线程池,您可以使用它在后台线程上异步运行数据库操作。
数据存储库
抽象了对多个数据源的访问。存储库不是架构组件库的一部分,但它是代码分离和架构的最佳实践。
为应用程序其余部分的数据访问提供了干净的 API。
使用
public class DataRepository {
private AccountDao accountDao;
private AccountListDao accountListDao;
private Context context;
AppRoomDataBase appRoomDataBase;
public DataRepository(Context context) {
this.context = context;
appRoomDataBase = AppRoomDataBase.getDataBase(context);
accountDao = appRoomDataBase.accountDao();
accountListDao = appRoomDataBase.accountListDao();
}
public void insert(AccountDataItem accountDataItem) {
AppRoomDataBase.databaseWriteExecutor.execute(new Runnable() {
@Override
public void run() {
accountDao.insertAccount(accountDataItem);
}
});
}
public interface ListDataLoadListener {
void onDataLoaded(List<AccountData> data);
}
public interface DataLoadListener {
void onDataLoaded(List<AccountDataItem> data);
}
public void getData(DataLoadListener listener){
AppRoomDataBase.databaseWriteExecutor.execute(() -> {
List<AccountDataItem> accountDataItems = new ArrayList<>();
accountDataItems.addAll(accountDao.getAllData());
if(listener != null){
listener.onDataLoaded(accountDataItems);
}
});
}
}
- DAO 被传递到存储库构造函数中,而不是整个数据库。这是因为您只需要访问 DAO,因为它包含数据库的所有读/写方法。无需将整个数据库公开给存储库。
- 我们不需要在主线程上运行插入,所以我们使用
ExecutorService
在中创建的WordRoomDatabase
在后台线程上执行插入
LiveData
在数据存储库中的代码可见,每次数据库中的数据发生变化后,我们都需要开启一个工作线程去获取数据库中的内容,这不太方便,因此可以使用LiveData
LiveData,一个 用于数据观察的生命周期库类,解决了这个问题。在方法描述中使用LiveData类型的返回值 ,Room 会在数据库更新时生成所有必要的代码来更新LiveData。
注意:如果您
LiveData
独立于 Room 使用,则必须管理数据更新。LiveData
没有公开可用的方法来更新存储的数据。如果要更新存储在
LiveData
中的数据,则必须使用 MutableLiveData而不是LiveData
。该类MutableLiveData
有两个公共方法允许您设置对象的值LiveData
, setValue(T)和 postValue(T)。通常,MutableLiveData
在 中使用 ViewModel,然后仅向观察者ViewModel
公开不可变对象,LiveData
存储库
public class DataRepository {
private AccountDao accountDao;
private AccountListDao accountListDao;
private Context context;
AppRoomDataBase appRoomDataBase;
public DataRepository(Context context) {
this.context = context;
appRoomDataBase = AppRoomDataBase.getDataBase(context);
accountDao = appRoomDataBase.accountDao();
accountListDao = appRoomDataBase.accountListDao();
}
public void insert(AccountDataItem accountDataItem) {
AppRoomDataBase.databaseWriteExecutor.execute(new Runnable() {
@Override
public void run() {
accountDao.insertAccount(accountDataItem);
}
});
}
public LiveData<List<AccountDataItem>> getData() {
return accountDao.getAllData();
}
}
Dao
@Dao
public interface AccountDao{
@Insert
void insertAccount(AccountDataItem AccountDataItem);
@Update
void update(AccountDataItem AccountDataItem);
@Query("SELECT * FROM AccountListItemTable")
LiveData<List<AccountDataItem>> getAllData();
@Query("DELETE FROM AccountListItemTable")
void delete();
}
使用:
private void initData() {
DataRepository dataRepository = new DataRepository(getContext());
dataRepository.getData().observe(getViewLifecycleOwner(), new Observer<List<AccountDataItem>>() {
@Override
public void onChanged(List<AccountDataItem> accountDataItems) {
System.out.println("数据更新了"+accountDataItems.size());
}
})
ViewModel
什么是 ViewModel?
ViewModel的
作用是向 UI 提供数据并在配置更改后继续存在。ViewModel
充当 Repository 和 UI 之间的通信中心。可以使用ViewModel
在 Fragment 之间共享数据。ViewModel 是 生命周期库的一部分。
在 ViewModel
中,使用LiveData
表示 UI 将使用或显示的可更改数据。使用LiveData
有几个好处:
- 您可以将观察者放在数据上(而不是轮询更改),并且仅在数据实际更改时更新 UI。
- 存储库和 UI 完全由
ViewModel
分开。 - 没有来自的数据库调用
ViewModel
(这一切都在存储库中处理),从而使代码更易于测试。
public class AccountViewModel extends AndroidViewModel {
private DataRepository dataRepository;
private LiveData<List<AccountDataItem>> listLiveData;
public AccountViewModel(@NonNull Application application) {
super(application);
dataRepository = new DataRepository(application);
listLiveData = dataRepository.getData();
}
public LiveData<List<AccountDataItem>> getListLiveData() {
return listLiveData;
}
public void insert(AccountDataItem accountDataItem){
dataRepository.insert( accountDataItem);
}
}