Room数据库的使用方法
目录
- 1、添加Room数据库的依赖
- 2、Entity——定义实体类 
  - 2.1 定义主键——PrimaryKey
- 2.2 字段注解——ColumnInfo
 
- 3、Dao——定义数据访问对象
- 4、Database——数据库 
  - 4.1 通过回调观察数据库是否创建成功
 
- 5、使用时注意点
- 6、编写异步 DAO 查询 
  - 6.1 写异步单次查询
- 6.2 编写可观察查询
 
参考文档:
[1] 使用 Room 实体定义数据
[2] 使用 Android Jetpack 的 Room 部分将数据保存到本地数据库。
[3] 实体类介绍
[4] RoomAPI、依赖
[5] 编写异步Dao查询
1、添加Room数据库的依赖
//Room
implementation "androidx.room:room-runtime:2.6.1"
annotationProcessor "androidx.room:room-compiler:2.6.1"
//Rxjava
implementation "androidx.room:room-rxjava3:2.6.1"
Room是由三大部分组成的:
- Entity:数据库中表对应的Java实体
- DAO:操作数据库的方法
- Database:创建数据库

2、Entity——定义实体类
@Entity:
- 用于定义数据库表结构。
- 包含以下常用属性: 
  - tableName: 指定表名。
- primaryKeys: 指定主键字段。
- indices: 定义索引。
- foreignKeys: 定义外键关系
 
默认情况下,Room 将类名称用作数据库表名称。如果您希望表具有不同的名称,请设置 @Entity 注解的 tableName 属性。
同样,Room 默认使用字段名称作为数据库中的列名称。
@Entity(tableName = "users")
public class User {
    @PrimaryKey(autoGenerate = true)
    public int id;
    @ColumnInfo(name = "first_name")
    public String firstName;
    @ColumnInfo(name = "last_name")
    public String lastName;
}
2.1 定义主键——PrimaryKey
每个 Room 实体都必须定义一个主键,用于唯一标识相应数据库表中的每一行。
- @PrimaryKey: 
  - 用于标记主键字段。
- 包含以下常用属性: 
    - autoGenerate: 是否自动生成主键值。
- 注意:自增主键必须为int型。
 
 
2.2 字段注解——ColumnInfo
- @ColumnInfo: 
  - 用于定义表字段。
- 包含以下常用属性: 
    - name: 指定字段名,也就是表的列名
- typeAffinity: 指定字段类型。
- defaultValue:设置默认值,未指定值时的默认值
 
 
通过 typeAffinity 属性,可以指定字段的数据类型,如 TEXT、INTEGER 等。
📌注意数据需要均为Public
@Entity
public class HistoryData {
    @PrimaryKey(autoGenerate = true)
    public int id;
    @ColumnInfo(typeAffinity = ColumnInfo.TEXT)
    public LocalDate birthDate;
    @ColumnInfo(name = "Name")
    public String name;
    @ColumnInfo(defaultValue = "18")
    public int age;
}
在这个例子中,birthDate 字段在数据库中会被存储为 TEXT 类型。
3、Dao——定义数据访问对象
常用注解包括:
- @Query: 
  - 用于定义数据库查询语句。
- 可以返回 Flowable、Observable、Single、Maybe等 RxJava 类型。
 
- @Insert、@Update、@Delete: 
  - 用于定义数据库增、改、删操作。
- 可以返回 long、int、void等类型,表示受影响的行数。
 
@Dao
public interface HistoryDao {
    /**
     * 向数据库添加数据
     *
     * @param data
     */
    @Insert
    void insertData(HistoryData data);
    /**
     * 删除数据库所有数据
     */
    @Query("DELETE FROM HistoryData")
    void deleteDataAll();
}
4、Database——数据库
以下代码定义了用于保存数据库的 HistoryDatabase 类。 HistoryDatabase 定义数据库配置,并作为应用对持久性数据的主要访问点。数据库类必须满足以下条件:
- 该类必须带有 @Database 注解,该注解包含列出所有与数据库关联的数据实体的 entities 数组。
- 该类必须是一个抽象类,用于扩展 RoomDatabase。
- 对于与数据库关联的每个 DAO 类,数据库类必须定义一个具有零参数的抽象方法,并返回 DAO 类的实例。
@Database(entities = HistoryData.class, version = 1)
public abstract class HistoryDatabase extends RoomDatabase {
    public abstract HistoryDao historyDao();
}
请注意:
📌如果您的应用在单个进程中运行,在实例化
HistoryDatabase对象时应遵循单例设计模式。每个RoomDatabase实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。
用法举例:
@Database(entities = HistoryData.class, version = 1)
public abstract class HistoryDatabase extends RoomDatabase {
    private static volatile HistoryDatabase historyDB = null;
    //单例模式双检锁
    public static HistoryDatabase getInstance(Context context) {
        if (historyDB == null) {
            synchronized (HistoryDatabase.class) {
                if (historyDB == null) {
                    historyDB = Room.databaseBuilder(context.getApplicationContext(), HistoryDatabase.class, "location_History").build();
                }
            }
        }
        return historyDB;
    }
    public abstract HistoryDao historyDao();
}
📌如果您的应用在多个进程中运行,请在数据库构建器调用中包含
enableMultiInstanceInvalidation()。这样,如果您在每个进程中都有一个AppDatabase实例,可以在一个进程中使共享数据库文件失效,并且这种失效会自动传播到其他进程中AppDatabase的实例。
用法举例:
@Database(entities = HistoryData.class, version = 1)
public abstract class HistoryDatabase extends RoomDatabase {
    public static HistoryDatabase getDatabase(Context context) {
        return Room.databaseBuilder(context.getApplicationContext(),
                        HistoryDatabase.class, "chat_database")
                .enableMultiInstanceInvalidation()
                .build();
    }
    public abstract HistoryDao historyDao();
}
4.1 通过回调观察数据库是否创建成功
通过RoomDatabase提供的Callback()回调方法观测数据库状态。
Callback提供了三个回调方法:分别是数据库第一次被创建时调用,数据库打开时调用,数据库被销毁迁移后调用

我们在创建数据库时添加上这个回调方法的实现类即可:
@Database(entities = HistoryData.class, version = 1,exportSchema = false)
public abstract class HistoryDatabase extends RoomDatabase {
    public abstract HistoryDao historyDao();
    private static volatile HistoryDatabase historyDB = null;
    public static HistoryDatabase getInstance(Context context) {
        if (historyDB == null) {
            synchronized (HistoryDatabase.class) {
                if (historyDB == null) {
                    historyDB = Room.databaseBuilder(context, HistoryDatabase.class, "locationHistory")
                            .addCallback(roomCallback)
                            .build();
                }
            }
        }
        return historyDB;
    }
    // 回调函数,可在数据库创建、打开和关闭时执行操作
    private static RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
        @Override
        public void onCreate(@NonNull SupportSQLiteDatabase db) {
            super.onCreate(db);
            Log.d("HistoryDatabase", "Database created successfully");
        }
    };
}
5、使用时注意点
为防止查询阻止界面,Room 不允许在主线程上访问数据库。 此限制意味着您必须将DAO 查询设为异步。Room 库包含与多个不同框架的集成,以提供异步查询执行功能。
// 创建一个ExecutorService
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交删除操作到ExecutorService中执行
executor.submit(() -> {
    HistoryData data = new HistoryData();
    data.setLocationName("天津");
    HistoryDatabase.getInstance(getContext()).historyDao().insertData(data);
    
    //在主线程中更改UI
    runOnUiThread(() -> {
        
    });
});
// 关闭ExecutorService
executor.shutdown();
6、编写异步 DAO 查询
Java 与 RxJava
如果您的应用使用 Java 编程语言,则您可以使用 RxJava 框架的专用返回类型编写异步 DAO 方法。Room 支持以下 RxJava 2 返回值类型:
- 对于单次查询,Room 2.1 及更高版本支持 Completable、Single<T> 和 Maybe<T> 返回值类型。
- 对于可观察查询,Room 支持 Publisher<T>、Flowable<T> 和 Observable<T> 返回值类型。
此外,Room 2.3 及更高版本支持 RxJava 3。
📌注意:如需将 RxJava 与 Room 搭配使用,您必须在
build.gradle文件中添加room-rxjava2工件或room-rxjava3工件。如需了解详情,请参阅声明依赖项。
6.1 写异步单次查询
单次查询是指仅执行一次并在执行时获取数据快照的数据库操作。以下是异步单次查询的一些示例:
@Dao
public interface UserDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public Completable insertUsers(List<User> users);
    @Update
    public Completable updateUsers(List<User> users);
    @Delete
    public Completable deleteUsers(List<User> users);
    @Query("SELECT * FROM user WHERE id = :id")
    public Single<User> loadUserById(int id);
    @Query("SELECT * from user WHERE region IN (:regions)")
    public Single<List<User>> loadUsersByRegion(List<String> regions);
}
6.2 编写可观察查询
可观察查询是指在查询引用的任何表发生更改时发出新值的读取操作。
您可能需要用到可观察查询的一种情形是,帮助您在向底层数据库中插入项或者更新或移除其中的项时及时更新显示的列表项。下面是可观察查询的一些示例:
@Dao
public interface UserDao {
    @Query("SELECT * FROM user WHERE id = :id")
    public Flowable<User> loadUserById(int id);
    @Query("SELECT * from user WHERE region IN (:regions)")
    public Flowable<List<User>> loadUsersByRegion(List<String> regions);
}



















