金歌 发表于 2024-8-31 09:00:39

Android --- Room数据库(Java)

概念 

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 的功能在于,让在底层长期性层实行数据库操作所涉及的所有复杂性都不波及应用的其余部门。这样就可以独立于使用数据的代码更改数据访问层。
https://img-blog.csdnimg.cn/direct/e1355e0d065948a4990fdd11120bdfb9.png
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。
https://img-blog.csdnimg.cn/direct/59bf5aa111e44cca84a8c18b3adf7362.png
使用

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 是 生命周期库的一部门。
https://img-blog.csdnimg.cn/direct/1f57cf2b7f21495f8740b0e1d9c91a0a.png

在 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);
    }
}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Android --- Room数据库(Java)