Android --- Room数据库(Java)

概念 

Room 是一个持久性库,属于 Android Jetpack 的一部分。Room 是 SQLite 数据库之上的一个抽象层。SQLite 使用一种专门的语言 (SQL) 来执行数据库操作。Room 并不直接使用 SQLite,而是负责简化数据库设置和配置以及与数据库交互方面的琐碎工作。此外,Room 还提供 SQLite 语句的编译时检查。

添加 Room 库

  1. 打开模块级 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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值