Room
是 Google 官方出品的 ORM(Object-relational mapping) 框架,当然市面上也有很多 ORM 框架,例如 GreenDao
、 OrmLite
、 Litepal
等。个人并没有了解过其它框架,因此也无法比较其优缺点,相对而言, 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
简单来说可以分为以下几个部分:
- Model
- DAO(Data Access Object)
- DataBase 类
- 入口类 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 的大部分东西都隐藏起来了,但如果我们想写出更为准确和高效的东西,我们依旧需要继续升入,看看我们写的每一行代码具体都做了些什么,这个我们下一篇博文再详细介绍。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 『互联网架构』软件架构-分布式架构(14)
- 『互联网架构』软件架构-电商系统架构(上)(69)
- 『互联网架构』软件架构-电商系统架构(中)(70)
- 『互联网架构』软件架构-电商系统架构(下)(71)
- 『互联网架构』软件架构-电商系统架构发展历程(68)
- 阿里P8架构师谈:淘宝技术架构从1.0到4.0的架构变迁!附架构资料
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。