从Room源码看抽象与封装——数据库的创建

目录

源码解析目录
从Room源码看抽象与封装——SQLite的抽象
从Room源码看抽象与封装——数据库的创建
从Room源码看抽象与封装——数据库的升降级
从Room源码看抽象与封装——Dao
从Room源码看抽象与封装——数据流

前言

上一篇文章讲了Room抽象的前两个层次,接口层以及接口实现层,这篇文章会介绍最后一个层次,Room实现层。

Room抽象的前两个层次

前两个抽象层次其实是与Room无关的,因为它只是对于SQLite的抽象,理论上讲,我们可以基于前两个抽象层次构建出自己的ORM框架。不过,话又说回来,Room的这种抽象必然不是完全独立的,Room实现层操作数据库的一些细节还是体现在了头两个抽象层次中。
Room实现层还是有一些复杂性的,并且细节也很多,这篇文章会专注于数据库的创建流程。(源码版本androidx.room:room-runtime:2.1.0)

数据库的创建

上篇文章讲到,SupportSQLiteOpenHelper是Room与SQLite建立联系的唯一途径,并且SupportSQLiteOpenHelper还把这一过程拆分成了数据库的配置阶段(通过SupportSQLiteOpenHelper.Callback)和数据库的使用阶段(一般是使用SupportSQLiteOpenHelper的实现类FrameworkSQLiteOpenHelper)。数据库的使用阶段已经有默认实现了,不用我们操心,我们要完成的就是如何实现SupportSQLiteOpenHelper.Callback。那就先来回顾一下SupportSQLiteOpenHelper.Callback

public interface SupportSQLiteOpenHelper {
    //...
    
    abstract class Callback {
        public final int version;

        //必须提供数据库版本号
        public Callback(int version) {
            this.version = version;
        }

        /**
         * Called when the database connection is being configured, to enable features such as
         * write-ahead logging or foreign key support.
         */
        public void onConfigure(SupportSQLiteDatabase db) {

        }

        public abstract void onCreate(SupportSQLiteDatabase db);

        public abstract void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion);

        public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
            throw new SQLiteException("Can't downgrade database from version "
                    + oldVersion + " to " + newVersion);
        }

        public void onOpen(SupportSQLiteDatabase db) {

        }

        public void onCorruption(SupportSQLiteDatabase db) {
            
        }
    }
}

可以看出,我们必须实现的是onCreate以及onUpgrade方法,当然Callback实际上是覆盖了数据库配置(指的是数据库打开之前)的全流程的,每个方法都有其回调的时机,也都有适用的场景,虽说我们必须实现onCreate以及onUpgrade方法,但并不是说别的方法就可以置之不理。
想想onCreate方法适合完成什么样的工作,必然是建表工作;onUpgrade方法就更不用说了,必然是数据库升级工作。这仅仅是从这些方法的调用时机上去分析的结果,为了更加明晰各个回调方法的职责,Room对Callback进行了进一步抽象,明确了各个方法应该完成的内容:

//RoomOpenHelper实现了SupportSQLiteOpenHelper.Callback
public class RoomOpenHelper extends SupportSQLiteOpenHelper.Callback {
    //Delegate类的定义就在下方
    @NonNull
    private final Delegate mDelegate;
    
    @Override
    public void onCreate(SupportSQLiteDatabase db) {
        updateIdentity(db);
        //onCreate必须完成的工作,建表
        mDelegate.createAllTables(db);
        //依然为其它需要再onCreate中完成的工作保留回调
        mDelegate.onCreate(db);
    }
    
    @Override
    public void onUpgrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
        //伪代码,具体数据库升级内容之后会介绍
        
        boolean migrated = false;
        //配置了相应的数据库升级方法
        if (has migrations) {
            //升级前回调
            mDelegate.onPreMigrate(db);
            //升级数据库
            migrate(db);
            //验证数据库升级是否正确
            mDelegate.validateMigration(db);
            //升级后回调
            mDelegate.onPostMigrate(db);
            migrated = true;
        }
        
        //没有升级并且允许以重建表的形式升级的话(之前的数据会完全丢失)
        if (!migrated && allowDestructiveMigration) {
            //丢弃原有的所有数据库表
            mDelegate.dropAllTables(db);
            //创建新的表
            mDelegate.createAllTables(db);
        }
    }

    @Override
    public void onDowngrade(SupportSQLiteDatabase db, int oldVersion, int newVersion) {
        //升降级是统一处理的
        onUpgrade(db, oldVersion, newVersion);
    }

    @Override
    public void onOpen(SupportSQLiteDatabase db) {
        super.onOpen(db);
        checkIdentity(db);
        //数据库打开回调
        mDelegate.onOpen(db);
        // there might be too many configurations etc, just clear it.
        mConfiguration = null;
    }
    
    /**
     * 明确了SupportSQLiteOpenHelper.Callback的具体职责
     * 正如前面看到的那样,Delegate中的各个方法会在RoomOpenHelper合适的时机被调用
     */
    public abstract static class Delegate {
        public final int version;

        public Delegate(int version) {
            this.version = version;
        }

        protected abstract void dropAllTables(SupportSQLiteDatabase database);

        protected abstract void createAllTables(SupportSQLiteDatabase database);

        protected abstract void onOpen(SupportSQLiteDatabase database);

        protected abstract void onCreate(SupportSQLiteDatabase database);

        //验证数据库升级的完整性
        protected abstract void validateMigration(SupportSQLiteDatabase db);

        //升级前
        protected void onPreMigrate(SupportSQLiteDatabase database) {

        }

        //升级后
        protected void onPostMigrate(SupportSQLiteDatabase database) {

        }
    }
}

既然SupportSQLiteOpenHelper.CallbackonCreate方法一定是需要建表的;onUpgrade方法一定是需要升级数据库的(包括升级数据库的一些列步骤,升级及升级后的验证等等),那么为何不把这些内容以“接口”的形式规范下来。RoomOpenHelper就是这么做的,SupportSQLiteOpenHelper.Callback仅仅只是对于SQLiteOpenHelper的抽象与封装,SupportSQLiteOpenHelper.Callback强调的是SQLiteOpenHelper在数据库使用之前的回调,而RoomOpenHelper.Delegate则明确了在具体回调方法中的职责。不过也不用担心,RoomOpenHelper.Delegate绝大部分内容Room都会通过注解处理器帮我们实现,并不需要我们完成太多内容。

需要特别注意,RoomOpenHelper这个名字很具有迷惑性,看名字似乎应该是个SupportSQLiteOpenHelper,但是,其实它是个SupportSQLiteOpenHelper.Callback。上篇文章讲过,SupportSQLiteOpenHelper的实现类是FrameworkSQLiteOpenHelper

下面的问题就很明确了,RoomOpenHelper是怎么创建的呢?我们可以拍着胸脯说必然是通过如下的方式创建的:

Room.databaseBuilder(
        applicationContext,
        AppDatabase::class.java,
        "database-name"
    ).build()

这肯定是正确的,因为这是RoomDatabase创建的入口。但是从这个方法到RoomOpenHelper,这中间的跨度似乎有点大。我们就从这个方法作为入口,看看最终是怎么创建出RoomOpenHelper,进而建立起与底层SQLite的联系的。

从RoomDatabase.Builder到RoomOpenHelper

光看这个方法名Room.databaseBuilder().build()也能猜得出来这是Builder(构建者)模式,事实也的确如此:

public class Room {

    @NonNull
    public static <T extends RoomDatabase> RoomDatabase.Builder<T> databaseBuilder(
            @NonNull Context context, @NonNull Class<T> klass, @NonNull String name) {
        //noinspection ConstantConditions
        if (name == null || name.trim().length() == 0) {
            throw new IllegalArgumentException("Cannot build a database with null or empty name."
                    + " If you are trying to create an in memory database, use Room"
                    + ".inMemoryDatabaseBuilder");
        }
        //返回的是RoomDatabase.Builder类,显然是RoomDatabase的构建者
        return new RoomDatabase.Builder<>(context, klass, name);
    }

    //...
}

Room类上的静态方法databaseBuilder主要是方便我们使用,实际上是RoomDatabase的创建时要通过其Builder的。

public abstract class RoomDatabase {
    public static class Builder<T extends RoomDatabase> {
        //...
        
        Builder(@NonNull Context context, @NonNull Class<T> klass, @Nullable String name) {
            mContext = context;
            mDatabaseClass = klass;
            mName = name;
            mJournalMode = JournalMode.AUTOMATIC;
            mRequireMigration = true;
            mMigrationContainer = new MigrationContainer();
        }
        
        //Builder上拥有诸多配置方法,这里统统省略
        
        @NonNull
        public T build() {
            //对于我们没有设置的属性设置默认值
            //对于我们设置的属性进行必要的验证等等
            
            //注意到,我们为RoomDatabase设置的种种属性会汇集到一个叫DatabaseConfiguration的类中
            DatabaseConfiguration configuration =
                    new DatabaseConfiguration(
                            mContext,
                            mName,
                            mFactory,
                            mMigrationContainer,
                            mCallbacks,
                            mAllowMainThreadQueries,
                            mJournalMode.resolve(mContext),
                            mQueryExecutor,
                            mTransactionExecutor,
                            mMultiInstanceInvalidation,
                            mRequireMigration,
                            mAllowDestructiveMigrationOnDowngrade,
                            mMigrationsNotRequiredFrom);
                            
            //这里的类型参数 T 即是我们自己定义的,扩展自RoomDatabase的数据库类型,上文中叫AppDatabase
            T db = Room.getGeneratedImplementation(mDatabaseClass, DB_IMPL_SUFFIX);
            //调用RoomDatabase的init方法,传入的参数是DatabaseConfiguration,包含了我们对于数据库的种种设置
            db.init(configuration);
            return db;
        }
    }
}

RoomDatabase.Builder是典型的Builder模式,通过Builder可以为数据库配置各种属性,这些属性最终被汇集到了一个叫DatabaseConfiguration的类中。一般情况下,我们会在Builder的build方法中通过调用目标对象(这里是RoomDatabase)的构造函数来最终构建出目标对象。然而,这在RoomDatabase.Builder中是不可行的,因为其Builder要构建的是我们自定义的,扩展自RoomDatabase的对象(为了方便以下直接称这个类叫AppDatabase),自然是不能提前确定其类型的,也不能调用它的构造函数(AppDatabase是抽象类,根本不可能被构造出来)。上篇文章说了,Room的使用就是加各种注解,然后注解处理器会帮我们生成一些我们需要的类,这些类有一个特点,就是特别冗长,都是一些套路似的代码,非常适合使用注解处理器来生成。我们定义的AppDatabase是个抽象类(也必须是个抽象类),自然需要一个实现类,这个实现类就是Room注解处理器帮我们生成的(别忘了AppDatabase上是加了@Database注解的),生成的对应的类名是AppDatabase_Impl,就是原名加后缀_Impl,包名跟我们定义的AppDatabase相同。
有了上面这些背景知识,大概也可以猜出Room.getGeneratedImplementation干了些什么。很简单,就是通过反射来创建出AppDatabase_Impl的对象。然后,我们看到调用了其上的init方法完成了初始化。


我们终于来到RoomDatabase了,来看看它的init方法完成了哪些初始化工作:

public abstract class RoomDatabase {

    @CallSuper
    public void init(@NonNull DatabaseConfiguration configuration) {
        //创建SupportSQLiteOpenHelper需要SupportSQLiteOpenHelper.Callback
        //而这个SupportSQLiteOpenHelper.Callback就是RoomOpenHelper
        mOpenHelper = createOpenHelper(configuration);
        //...
    }

    @NonNull
    protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
    
    //...
}

RoomDatabase包含的内容是很多的,但是,如果把内容限定到本文讨论的“主线”中,相关的内容少多了。init方法的最主要内容就是调用createOpenHelper方法来创建SupportSQLiteOpenHelper,而createOpenHelper是个抽象方法,会在AppDatabase_Impl中被实现。


扯了这么多,你可能都已经忘记了我们是怎么来到这里的。我给你缕缕。创建RoomDatabase最关键是要实现它的抽象方法createOpenHelpercreateOpenHelper返回一个SupportSQLiteOpenHelper,Room中SupportSQLiteOpenHelper的唯一实现类是FrameworkSQLiteOpenHelper;而创建FrameworkSQLiteOpenHelper又必须需要传入SupportSQLiteOpenHelper.Callback,万幸Room帮我们实现了SupportSQLiteOpenHelper.Callback,它是RoomOpenHelperRoomOpenHelper通过明确回调方法的职责,又抽象出了RoomOpenHelper.Delegate,也就是说,现在我们不需要再去关心SupportSQLiteOpenHelper.Callback,只需要实现RoomOpenHelper.Delegate就可以了;RoomOpenHelper.Delegate中主要的内容是建表、升级、验证升级等,都是些繁琐且冗长的东西,这些都是注解处理器帮我们生成的;注解处理器生成了AppDatabase_Impl类,是RoomDatabase的具体实现,AppDatabase_Impl在其createOpenHelper方法中实例化了RoomOpenHelper.Delegate

希望没把你绕晕,总之呢,我们看一下AppDatabase_Impl的实现就知道RoomOpenHelper.Delegate是如何实现的。

public final class AppDatabase_Impl extends AppDatabase {
    //...
    
    @Override
    protected SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration configuration) {
        final SupportSQLiteOpenHelper.Callback _openCallback = new RoomOpenHelper(configuration, new RoomOpenHelper.Delegate(1) {
            @Override
            public void createAllTables(SupportSQLiteDatabase _db) {
                //都是类似的建表SQL语句,由于是注解处理器生成的,不必担心出错
                _db.execSQL("CREATE TABLE IF NOT EXISTS `Subject` (`id` INTEGER NOT NULL, `name` TEXT, `gradeId` INTEGER NOT NULL, PRIMARY KEY(`id`))");
            }
            
            @Override
            public void dropAllTables(SupportSQLiteDatabase _db) {
                _db.execSQL("DROP TABLE IF EXISTS `Grade`");
            }
            
            @Override
            protected void validateMigration(SupportSQLiteDatabase _db) {
                //用于数据库升降级后的验证,保证升降级后表结构的正确性,非常非常的冗长
            }
            
            //...
        }, /*代表数据库表结构的hash值*/"fb387e669d2f38d41af37b3394c05f6b");
        
        final SupportSQLiteOpenHelper.Configuration _sqliteConfig = SupportSQLiteOpenHelper.Configuration.builder(configuration.context)
            .name(configuration.name)
            .callback(_openCallback)
            .build();
        //上篇文章讲过,创建SupportSQLiteOpenHelper是要通过工厂方法的,默认情况下返回的就是FrameworkSQLiteOpenHelper
        final SupportSQLiteOpenHelper _helper = configuration.sqliteOpenHelperFactory.create(_sqliteConfig);
        return _helper;
    }
    
    //...
}

AppDatabase_Impl是注解处理器生成的,其中包含了特别冗长的RoomOpenHelper.Delegate类的具体实现,至此,数据库的创建流程就走完了。

总结

Room数据库创建流程

Room抽象的前两层,接口层和接口实现层,把Room这整个抽象系统与原有的被抽象系统SQLite进行了隔离,在此之上,Room的实现层的种种其实就已经与SQLite无关了。我们看到,在Room实现层并没有仅仅“满足于”接口层这种对于SQLite较底层的抽象,而是在此基础之上进行进一步的抽象,最典型的就是RoomOpenHelper.Delegate,通过明确SupportSQLiteOpenHelper.Callback回调的职责,RoomOpenHelper.Delegate抽象出了更加清晰明了的“接口”,对于创建数据库而言,整体的实现结构也变得更加清晰。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容