Android 架构之美 - Room(上)

栏目: 数据库 · 发布时间: 5年前

Room 是 Google 官方出品的 ORM(Object-relational mapping) 框架,当然市面上也有很多 ORM 框架,例如 GreenDaoOrmLiteLitepal 等。个人并没有了解过其它框架,因此也无法比较其优缺点,相对而言, Room 毕竟是官方出品,能够更好的与 LiveData 等框架结合使用,如果是新项目的话,建议使用。

引入

// 这里以 androidx 最新版为例
implementation 'androidx.room:room-runtime:2.1.0-alpha01'  
kapt 'androidx.room:room-compiler:2.1.0-alpha01'

简单使用

Room 在 Google 的另一个框架 WorkManager 中得到使用,所以这里我就简单的以它为例来简单介绍下 Room 的使用。

Room 简单来说可以分为以下几个部分:

  1. Model
  2. DAO(Data Access Object)
  3. DataBase 类
  4. 入口类 Room

首先我们需要建立 Model 对象, 添加 @Entity 注解

// 用 @Index 来标示索引
@Entity(indices = {@Index(value {"schedule_requested_at"})})
public class WorkSpec {  
    // 用 @ColumnInfo 来标明数据库表的列名, 用 @PrimaryKey 来标示 主键
    @ColumnInfo(name = "id")
    @PrimaryKey
    @NonNull
    public String id;

    @ColumnInfo(name = "state")
    @NonNull
    public State state = ENQUEUED;

    @ColumnInfo(name = "worker_class_name")
    @NonNull
    public String workerClassName;

    // ...

    // 用 @Embedded 来聚合字段,这里 Constraints 的多个字段,在 数据库表里与 workerClassName 等字段平级
    @Embedded
    @NonNull
    public Constraints constraints = Constraints.NONE;

    //...

    public WorkSpec(@NonNull String id, @NonNull String workerClassName) {
        //...
    }

    public WorkSpec(@NonNull WorkSpec other) {
        //...
    }
}

// 通过 @ForeignKey 来指明外键, 以及在父 Model delete 与 update 时的行为(NO_ACTION, RESTRICT, SET_NULL, SET_DEFAULT, CASCADE)
@Entity(foreignKeys = {
        @ForeignKey(
                entity = WorkSpec.class,
                parentColumns = "id",
                childColumns = "work_spec_id",
                onDelete = ForeignKey.CASCADE,
                onUpdate = ForeignKey.CASCADE)},
        primaryKeys = {"tag", "work_spec_id"},
        indices = {@Index(value = {"work_spec_id"})})
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public class WorkTag {

    @NonNull
    @ColumnInfo(name = "tag")
    public final String tag;

    @NonNull
    @ColumnInfo(name = "work_spec_id")
    public final String workSpecId;

    public WorkTag(@NonNull String tag, @NonNull String workSpecId) {
        this.tag = tag;
        this.workSpecId = workSpecId;
    }
}

一般而言,每个 Model 都会有其对应的 DAO 类,集成了所有对这个 Model 的操作,如 WorkSpec 对应的 WorkSpecDao , Room 的 DAO 类一般都声明为 interface , 然后加上 @Dao 注解,具体的实现类则由代码自动生成。

@Dao
public interface WorkSpecDao {  
    @Insert(onConflict = IGNORE)
    void insertWorkSpec(WorkSpec workSpec);

    // @Query 并不是指查询数据库,而是执行数据库语句
    @Query("DELETE FROM workspec WHERE id=:id")
    void delete(String id);

    @Query("SELECT * FROM workspec WHERE id=:id")
    WorkSpec getWorkSpec(String id);

    //...

    // 使用 @Transaction 标示使用 transition
    @Transaction
    @Query("SELECT id, state, output FROM workspec WHERE id=:id")
    WorkSpec.WorkStatusPojo getWorkStatusPojoForId(String id);

    // 可以返回 LiveData, 当数据变动后,重新执行查询,获取新数据
    @Transaction
    @Query("SELECT id, state, output FROM workspec WHERE id IN (:ids)")
    LiveData<List<WorkSpec.WorkStatusPojo>> getWorkStatusPojoLiveDataForIds(List<String> ids);
}

当准备好所有的 Model 和 DAO 后,我们就需要把它放入 DataBase 的管理中:

// 我们需要把所有的 model 对象 全都方式 @Database 的 entities 中,增删改 model 后,我们应该更新 version
// sqlite 只支持 NULL、INTEGER、REAL、TEXT、BLOB 这些类型,如果是 Date 或者自定义的枚举等类型,则需要声明 @TypeConverters 来做类型转换了
@Database(entities = {
        Dependency.class,
        WorkSpec.class,
        WorkTag.class,
        SystemIdInfo.class,
        WorkName.class},
        version = 4)
@TypeConverters(value = {Data.class, WorkTypeConverters.class})
public abstract class WorkDatabase extends RoomDatabase {

    // 获取 WorkSpecDao
    public abstract WorkSpecDao workSpecDao();

   // ...
}

剩下的就是如何使用这个 DataBase 类了,它是一个抽象类,我们真正需要的是由代码生成的子类,那如何获取呢?这个时候 Room 这个类就该出场了。也不得不感叹下,通过注解来做代码生成真好,一堆复杂可重复的东西都被隐藏在水下了。

Room 构造 DataBase 实例是通过 Builder 的方式来构建的,我们来看看 WorkDatabase 的构建:

public static WorkDatabase create(Context context, boolean useTestDatabase) {  
    RoomDatabase.Builder<WorkDatabase> builder;
    if (useTestDatabase) {
        // 可以通过 inMemoryDatabaseBuilder 来构建内存Db,可用于测试
        builder = Room.inMemoryDatabaseBuilder(context, WorkDatabase.class)
                .allowMainThreadQueries();
    } else {
        builder = Room.databaseBuilder(context, WorkDatabase.class, DB_NAME);
    }

    return builder.addCallback(generateCleanupCallback())
            .addMigrations(WorkDatabaseMigrations.MIGRATION_1_2)
            .addMigrations(
                    new WorkDatabaseMigrations.WorkMigration(context, VERSION_2, VERSION_3))
            .addMigrations(MIGRATION_3_4)
            .fallbackToDestructiveMigration()
            .build();
}

通过 builder, 我们可以添加 Callback,可以添加每个版本的升级降级策略, 可以启用 WAL 模式等。一般应用构建好 DataBase 应该以单例的形式存在于应用中。

DataBase 的实例化

实现我们看看 RoomDataBase$Builder 的 build 方法:

public static class Builder<T extends RoomDatabase> {

    public T build() {

        //...

        if (mQueryExecutor == null) {
            // 如果使用者没有提供 Executor,则使用框架默认的 IOThreadExecutor, 所以默认所有通过 DAO 执行的操作都会在子线程执行
            mQueryExecutor = ArchTaskExecutor.getIOThreadExecutor();
        }

        // Migration 相关

        if (mFactory == null) {
            mFactory = new FrameworkSQLiteOpenHelperFactory();
        }
        DatabaseConfiguration configuration =
        new DatabaseConfiguration(mContext, mName, mFactory, mMigrationContainer,
        mCallbacks, mAllowMainThreadQueries, mJournalMode.resolve(mContext),
        mQueryExecutor,
        mMultiInstanceInvalidation,
        mRequireMigration,
        mAllowDestructiveMigrationOnDowngrade, mMigrationsNotRequiredFrom);
        // 真正构造 DataBase 实例
        T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
        // 初始化 DataBase
        db.init(configuration);
        return db;
    }
}

注解生成器会为我们生成一个带 _Impl 后缀的类,我们的 DB 名为 WorkDatabase ,那么生成类就为 WorkDatabase_Impl 。 所以真正构造实例时通过反射去构造的。

static <T, C> T getGeneratedImplementation(Class<C> klass, String suffix) {  
    final String fullPackage = klass.getPackage().getName();
    String name = klass.getCanonicalName();
    final String postPackageName = fullPackage.isEmpty()
    ? name
    : (name.substring(fullPackage.length() + 1));
    final String implName = postPackageName.replace('.', '_') + suffix;
    try {
        final Class<T> aClass = (Class<T>) Class.forName(
                fullPackage.isEmpty() ? implName : fullPackage + "." + implName);
        return aClass.newInstance();
    } catch (Exception e) {
        // 各种 rethrow
    }
}

至此,对于业务开发者而言,了解到此已经足够了, Room 已经将 sqlite 的大部分东西都隐藏起来了,但如果我们想写出更为准确和高效的东西,我们依旧需要继续升入,看看我们写的每一行代码具体都做了些什么,这个我们下一篇博文再详细介绍。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

REST实战

REST实战

Jim Webber、Savas Parastatidis、Ian Robinson / 李锟、俞黎敏、马钧、崔毅 / 东南大学出版社 / 2011-10 / 78.00元

为何典型的企业项目无法像你为web所开发的项目那样运行得如此平滑?对于建造分布式和企业级的应用来说,rest架构风格真的提供了一个可行的替代选择吗? 在这本富有洞察力的书中,三位soa专家对于rest进行了讲求实际的解释,并且通过将web的指导原理应用到普通的企业计算问题中,向你展示了如何开发简单的、优雅的分布式超媒体系统。你将会学习到很多技术,并且随着一家典型的公司从最初的小企业逐渐成长为......一起来看看 《REST实战》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具