一、简单说明

1、官方推荐

Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。谷歌强烈建议使用 Room 而不是 SQLite。

2、主要内容点

Room 包含 3 个主要组件:

(1)数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。

使用 @Database 注释的类应满足以下条件:
是扩展 RoomDatabase 的抽象类。
在注释中添加与数据库关联的实体列表。
包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 的实例。

(2)Entity:表示数据库中的表。

(3)DAO:包含用于访问数据库的方法。

应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。 最后,应用使用实体来获取和设置与数据库中的表列相对应的值。

3、引入
android {
    defaultConfig {
        ... ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments += [
                        "room.schemaLocation"  : "$projectDir/schemas".toString(),
                        "room.incremental"     : "true",
                        "room.expandProjection": "true"]
                /*
                Room 具有以下注释处理器选项:
                room.schemaLocation:配置并启用将数据库架构导出到给定目录中的 JSON 文件的功能。
                                    [创建好表和数据库后编译,会在 app/schemas 目录下生成这个 JSON 文件]
                room.incremental:启用 Gradle 增量注释处理器。
                room.expandProjection:配置 Room 以重写查询,使其顶部星形投影在展开后仅包含 DAO 方法返回类型中定义的列。
                */
            }
        }
    }
}

dependencies {
    ... ...
    // room
    def room_version = "2.2.5"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    // For Kotlin use kapt instead of annotationProcessor
    // 对于Kotlin来说,使用kapt而不是annotationProcessor
    // kapt "androidx.room:room-compiler:$room_version"
}

二、基本使用

1、RoomDatabase
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    private static volatile AppDatabase mAppDatabase;

    // TODO 在实例化 AppDatabase 对象时应遵循单例设计模式。每个 RoomDatabase 实例的成本相当高,几乎不需要在单个进程中访问多个实例。
    static AppDatabase getInstance(Context context) {
        if (mAppDatabase == null) {
            synchronized (AppDatabase.class) {
                if (mAppDatabase == null) {
                    mAppDatabase = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "dbRoomTest.db")
                            .addMigrations()
                            // 默认不允许在主线程中连接数据库
                            // .allowMainThreadQueries()
                            .build();
                }
            }
        }
        return mAppDatabase;
    }

    public abstract UserDao userDao();
}
2、Dao
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;

// TODO Dao 必须是接口或者抽象类,Room 使用注解自动生成访问数据库的代码
@Dao
public interface UserDao {

    @Insert
    void insertUser(User users);
    // TODO 如果插入的数据在数据库表中已经存在,就会抛出异常

    @Insert
    void insertUsers(List<User> users);

    @Insert
    void insertUsers(User... users);
    // 如果 @Insert 方法只接收 1 个参数,则它可以返回 long,这是插入项的新 rowId。如果参数是数组或集合,则应返回 long[] 或 List<Long>。

    @Delete
    void delete(User user);
    // TODO 如果通过 Entity 来删除数据,传进来的参数需要包含主键

    @Delete
    void deleteUsers(User... users);
    // 虽然通常没有必要,但是您可以让此方法返回一个 int 值,以指示从数据库中删除的行数。

    @Update
    void updateUser(User users);
    // TODO 如果通过 Entity 来更新数据,传进来的参数需要包含主键,参数会覆盖旧数据,参数中没有值的字段将置为 null

    @Update
    void updateUsers(User... users);
    // 虽然通常没有必要,但是您可以让此方法返回一个 int 值,以指示数据库中更新的行数

    /*
    @Query 是 DAO 类中使用的主要注释。它允许您对数据库执行读/写操作。
    每个 @Query 方法都会在编译时进行验证,因此如果查询出现问题,则会发生编译错误,而不是运行时失败。
    Room 还会验证查询的返回值,以确保当返回的对象中的字段名称与查询响应中的对应列名称不匹配时,Room 可以通过以下两种方式之一提醒您:
    1、如果只有部分字段名称匹配,则会发出警告。
    2、如果没有任何字段名称匹配,则会发出错误。
    */

    @Query("SELECT * FROM users")
    List<User> loadAll();

    @Query("SELECT * FROM users WHERE uid=(:userId)")
    User findUserById(int userId);

    @Query("SELECT * FROM users WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM users WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1")
    User findUserByName(String first, String last);

    @Query("SELECT * FROM users WHERE first_name LIKE :first AND last_name LIKE :last")
    List<User> loadAllByName(String first, String last);

    /*
    返回列的子集:
    大多数情况下,您只需获取实体的几个字段。
    例如,您的界面可能仅显示用户的名字和姓氏,而不是用户的每一条详细信息。
    通过仅提取应用界面中显示的列,您可以节省宝贵的资源,并且您的查询也能更快完成。
    借助 Room,您可以从查询中返回任何基于 Java 的对象,前提是结果列集合会映射到返回的对象。
    例如,您可以创建以下基于 Java 的普通对象 (POJO) 来获取用户的名字和姓氏。
    Room 知道该查询会返回 first_name 和 last_name 列的值,并且这些值会映射到 NameTuple 类的字段中。因此,Room 可以生成正确的代码。
    如果查询返回的列过多,或者返回 NameTuple 类中不存在的列,则 Room 会显示一条警告。
    */
    @Query("SELECT first_name, last_name FROM users")
    List<NameTuple> loadFullName();

}

列的子集

import androidx.room.ColumnInfo;

public class NameTuple {
    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;
}
3、Entity
import androidx.room.ColumnInfo;
import androidx.room.Embedded;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;

// 默认情况下,Room 将类名称用作数据库表名称
// 如果希望表具有不同的名称,请设置 @Entity 注释的 tableName 属性【个人建议】
@Entity(tableName = "users")
public class User {

    // 主键
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "uid")
    public int uid;

    // 与 tableName 属性类似,Room 将字段名称用作数据库中的列名称
    // 如果希望列具有不同的名称,请将 @ColumnInfo 注释添加到字段【个人建议】
    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;

    // 默认情况下,Room 会为实体中定义的每个字段创建一个列
    // 如果某个实体中有不想保留的字段,则可以使用 @Ignore 为这些字段添加注释
    @Ignore
    Bitmap picture;

    // 嵌套对象
    @Embedded
    public Address address;

}

嵌套对象

import androidx.room.ColumnInfo;

public class Address {
    public String street;
    public String state;
    public String city;

    @ColumnInfo(name = "post_code")
    public int postCode;
}

Android room存数据 安卓room数据库_数据库

三、其他

1、事务

(1) 如果要批量重复操作怎么办?

Room 自动生成的 Dao 实现类会对批量操作添加事务控制:

Android room存数据 安卓room数据库_android_02


(2) 如果要批量不重复操作怎么办?

下面用重复的插入操作来模拟,实际中可以采用复杂的操作组合:androidx.room.RoomDatabase#runInTransaction(java.lang.Runnable)

final ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            User user = new User();
            user.firstName = "No" + i;
            user.lastName = "number" + i;
            users.add(user);
        }

        final AppDatabase db = AppDatabase.getInstance(this);
        db.runInTransaction(new Runnable() {
            @Override
            public void run() {
                for (User user : users) {
                    db.userDao().insertUser(user);
                }
            }
        });
99、复杂用法

根据项目需要,如果涉及到复杂的功能实现,可以参考官方文档:Android 开发者网站关于 Room 的使用文档


参考文章:
1、《使用 Room 将数据保存到本地数据库
2、《Android Room 基础