美文网首页
从Room源码看抽象与封装——Dao

从Room源码看抽象与封装——Dao

作者: 珞泽珈群 | 来源:发表于2019-07-06 17:49 被阅读0次

    目录

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

    前言

    这一系列的文章的长度大大超出了我的预期,我本想着大概需要两篇文章来分析Room的源码,结果现在这都第四篇了,居然还没来到Dao,没关系,好消息来了,这篇文章我们终于要来看看Room对于“增删改查”的实现了。

    1. Dao

    Dao是Data Access Objects的缩写,翻译成中文就是“数据访问对象”,这是一个面向对象的数据库接口,再翻译成大白话就是,这是我们访问数据库的接口,能进行对象数据跟数据库数据的双向转换,方便我们使用数据库。

    Room架构

    如上图所示,这是Room的架构图,一般而言App是通过Dao来操作数据库的,包括从数据库中获取数据和保存数据到数据库,显然,我们对于数据库的存和取使用的都是Entity,即Java对象,这就体现出了Dao的职责:对象数据跟数据库数据的双向转换,并且帮我们完成“增删改查”。


    Long long ago,在第一篇文章SQLite的抽象中,讲了Room对“增删改查”的抽象,这种抽象奠定了Dao的基础,其实也体现出了Room实现“增删改查”的一些细节。这里我帮你复习一下。

    SupportSQLiteStatement和SupportSQLiteQuery

    “增删改”是通过SupportSQLiteStatement完成的,查询是通过SupportSQLiteQuery完成的。话虽这么说,但是从我们定义的Dao到最终“增删改查”的实现,这中间经历了漫漫长途,没错,这是辆长途汽车,把车门封死,我要飙车了。

    2. RoomDatabase

    在看“增删改查”之前我们再来重新认识一下RoomDatabase。很“神奇”的一点是,RoomDatabase它并不是个Database。之所以这么说,是因为RoomDatabase并没有实现SupportSQLiteDatabase这个接口。在Room体系当中,SupportSQLiteDatabase接口的唯一实现类是FrameworkSQLiteDatabaseRoomDatabase虽然并不是个Database,但是它却包含了一个SupportSQLiteDatabase,也就是说RoomDatabase采用了组合的模式来代替了继承,之所以这么做是因为,严格来讲RoomDatabase并不是一个Database,但是它也包括了部分Database的职责,除此之外,RoomDatabase还有更多别的职责。RoomDatabase是对SupportSQLiteDatabase的收缩和扩展,使用组合的模式让实现变得更加灵活。

    public abstract class RoomDatabase {
        //包含有 SupportSQLiteDatabase
        protected volatile SupportSQLiteDatabase mDatabase;
        private SupportSQLiteOpenHelper mOpenHelper;
    
        @CallSuper
        public void init(@NonNull DatabaseConfiguration configuration) {
            mOpenHelper = createOpenHelper(configuration);
            //...
        }
    
        @NonNull
        public SupportSQLiteOpenHelper getOpenHelper() {
            return mOpenHelper;
        }
    
        @NonNull
        protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);
    
        // used in generated code
        public void assertNotMainThread() {
            if (mAllowMainThreadQueries) {
                return;
            }
            if (isMainThread()) {
                throw new IllegalStateException("Cannot access database on the main thread since"
                        + " it may potentially lock the UI for a long period of time.");
            }
        }
    
        // 以下方法是对 SupportSQLiteDatabase 方法的包装,只保留了少量用得到的方法
        // 同时也方便我们在单元测试时 mock RoomDatabase
    
        public Cursor query(String query, @Nullable Object[] args) {
            return mOpenHelper.getWritableDatabase().query(new SimpleSQLiteQuery(query, args));
        }
    
        public Cursor query(SupportSQLiteQuery query) {
            //不能在主线程中
            assertNotMainThread();
            return mOpenHelper.getWritableDatabase().query(query);
        }
    
        //把SQL语句(String类型)编译成可以执行的SQL语句(SupportSQLiteStatement类型)
        public SupportSQLiteStatement compileStatement(@NonNull String sql) {
            //不能在主线程中
            assertNotMainThread();
            return mOpenHelper.getWritableDatabase().compileStatement(sql);
        }
    
        public void beginTransaction() {
            //不能在主线程中
            assertNotMainThread();
            SupportSQLiteDatabase database = mOpenHelper.getWritableDatabase();
            database.beginTransaction();
        }
    
        public void endTransaction() {
            mOpenHelper.getWritableDatabase().endTransaction();
        }
        
        public void setTransactionSuccessful() {
            mOpenHelper.getWritableDatabase().setTransactionSuccessful();
        }
        
        public boolean inTransaction() {
            return mOpenHelper.getWritableDatabase().inTransaction();
        }
    }
    

    RoomDatabase源码当中可以看出,虽然RoomDatabase并不是Database,但是它包含了相当Database的职责,RoomDatabaseSupportSQLiteDatabase中的方法进行了大量的“收缩”,只保留了几个“用得着”的方法,此外,RoomDatabase中还包含了除Database之外的其它职责,例如,Database的创建及打开等,这就是我说的,RoomDatabase是对SupportSQLiteDatabase的收缩和扩展。
    注意,RoomDatabase中的query,compileStatementbeginTransaction方法中都加有判断,强制要求不能在主线程中调用,因为Room把“增删改”都放到了Transaction中,也就是说,“增删改查”都不能发生在主线程中,避免我们不小心在主线程操作数据库。

    3. 增删改

    先来回顾一下SupportSQLiteStatement

    public interface SupportSQLiteStatement extends SupportSQLiteProgram {
        /**
         * 执行SQL, 非 SELECT / INSERT / DELETE / UPDATE
         * 例如CREATE / DROP table, view, trigger, index 等
         */
        void execute();
    
        /**
         * 例如 UPDATE / DELETE 这种
         * 返回有多少行受了影响
         */
        int executeUpdateDelete();
    
        /**
         * INSERT这种
         * 返回 row ID,-1代表失败
         */
        long executeInsert();
    
        /**
         * 执行SQL后返回一个数字
         * 例如 SELECT COUNT(*) FROM table;
         */
        long simpleQueryForLong();
        
        /**
         * 执行SQL后返回一个String
         */
        String simpleQueryForString();
    }
    

    可以看出,通过SupportSQLiteStatement我们可以方便地执行增(executeInsert)删改(executeUpdateDelete),这是因为SupportSQLiteStatement本身就是对于SQLiteStatement的映射,要知道在SQLite中,“增删改”原本就是通过SQLiteStatement来执行的,只不过藏的比较深,我们不经常接触到。
    到目前为止,即使我们不去查看Room的源码,也基本上可以推测出Dao中的“增删改”是如何被执行的。首先注解处理器帮我们生成出来了相应的SQL语句,然后这些SQL语句通过RoomDatabasecompileStatement方法被编译成了SupportSQLiteStatement,最后调用SupportSQLiteStatement上的executeInsert或者executeUpdateDelete方法来完成“增删改”。这种推测从流程上讲是对的,要是我们自己设计可能也就这么干了,但是,Google就是Google,总是在抽象层次上能更进一步。下面我就来看看Room是如何实现的。

    3.1 SharedSQLiteStatement

    我们方才的推测最大的问题就在于,没有考虑增删改SupportSQLiteStatement的共性,而共性往往是抽象的基础或者说切入点。增删改SupportSQLiteStatement的共性就在于:

    1. 一般情况下“增删改”SQL语句都会包含有若干占位符(?),而后在执行之前需要绑定具体的参数。
    2. “增删改”SQL语句是可以重用的,因为SQL语句使用了占位符(?),而不是直接把参数硬编码进SQL语句中,只需要在执行之前把最新的参数重新绑定到SQL语句中就可以重用了。
    3. “增删改”重用的几率是很大的,以UPDATE为例,向数据库多次插入数据再正常不过了,只是我们每次插入的数据不一样,如果使用带占位符的SQL语句,那么SQL语句是一模一样的。
    4. 当然,重用是有益的,降低内存占用,提升执行效率。

    基于以上几点,Room抽象出了SharedSQLiteStatement,用于解决增删改SupportSQLiteStatement重用的问题。

    public abstract class SharedSQLiteStatement {
        //多线程重用的标记
        private final AtomicBoolean mLock = new AtomicBoolean(false);
    
        private final RoomDatabase mDatabase;
        private volatile SupportSQLiteStatement mStmt;
    
        /**
         * Creates an SQLite prepared statement that can be re-used across threads. If it is in use,
         * it automatically creates a new one.
         * 如注释所说,statement会跨线程重用,如果statement正在使用就会再新建一个
         */
        public SharedSQLiteStatement(RoomDatabase database) {
            mDatabase = database;
        }
    
        /**
         * SQL语句,注解处理器会帮我们生成
         */
        protected abstract String createQuery();
    
        protected void assertNotMainThread() {
            mDatabase.assertNotMainThread();
        }
    
        private SupportSQLiteStatement createNewStatement() {
            String query = createQuery();
            return mDatabase.compileStatement(query);
        }
    
        //如果 mStmt存在并且没有在使用就重用,不然就新建一个
        private SupportSQLiteStatement getStmt(boolean canUseCached) {
            final SupportSQLiteStatement stmt;
            if (canUseCached) {
                if (mStmt == null) {
                    mStmt = createNewStatement();
                }
                stmt = mStmt;
            } else {
                // it is in use, create a one off statement
                stmt = createNewStatement();
            }
            return stmt;
        }
    
        /**
         * 通过这个方法获取 SupportSQLiteStatement,使用完之后必须调用 release
         */
        public SupportSQLiteStatement acquire() {
            assertNotMainThread();
            return getStmt(mLock.compareAndSet(false, true));
        }
    
        /**
         * 当 SupportSQLiteStatement不再使用时,必须调用这个方法
         */
        public void release(SupportSQLiteStatement statement) {
            if (statement == mStmt) {
                mLock.set(false);
            }
        }
    }
    

    SharedSQLiteStatement的基本使用方式是,通过acquire方法获取一个SupportSQLiteStatement对象,在使用完后通过release方法释放。SharedSQLiteStatement只保留了一个SupportSQLiteStatement,在我们使用完毕调用release后才会被重用。在多线程的情形下,如果保留的SupportSQLiteStatement还未被release,就会新建一个SupportSQLiteStatement(但是这个新建的Statement不会被保留重用),不会因为要重用而影响正常的执行。


    由于增、删改需要调用SupportSQLiteStatement不同的方法,所以增、删改分别各自实现了SharedSQLiteStatement

    //插入Entity
    public abstract class EntityInsertionAdapter<T> extends SharedSQLiteStatement {
        public EntityInsertionAdapter(RoomDatabase database) {
            super(database);
        }
    
        //绑定参数
        protected abstract void bind(SupportSQLiteStatement statement, T entity);
    
        public final void insert(T entity) {
            //获取,可能会重用之前的 SupportSQLiteStatement
            final SupportSQLiteStatement stmt = acquire();
            try {
                //绑定
                bind(stmt, entity);
                //执行 executeInsert
                stmt.executeInsert();
            } finally {
                //释放
                release(stmt);
            }
        }
    
        //其它方法都是类似的
    }
    
    //删除、更新Entity
    public abstract class EntityDeletionOrUpdateAdapter<T> extends SharedSQLiteStatement {
        public EntityDeletionOrUpdateAdapter(RoomDatabase database) {
            super(database);
        }
    
        //绑定参数
        protected abstract void bind(SupportSQLiteStatement statement, T entity);
    
        public final int handle(T entity) {
            //获取,可能会重用之前的 SupportSQLiteStatement
            final SupportSQLiteStatement stmt = acquire();
            try {
                //绑定
                bind(stmt, entity);
                //执行 executeUpdateDelete
                return stmt.executeUpdateDelete();
            } finally {
                //释放
                release(stmt);
            }
        }
    
        //其它方法都是类似的
    }
    

    万事俱备,只欠东风。我们只需要在Dao中实现对应的EntityInsertionAdapter<T>或者EntityDeletionOrUpdateAdapter<T>,就可以执行“增删改”操作了。而这些是注解处理器帮我们实现的,我们留待最后再看。

    4. 查询

    依然是Long long ago,在第一篇文章SQLite的抽象中就介绍了,SupportSQLiteDatabase收紧了查询方法,并且底层使用rawQueryWithFactory方法来真正的执行查询。我们再来复习一下。

    public interface SupportSQLiteQuery {
        String getSql();
        
        //重点在这个方法
        void bindTo(SupportSQLiteProgram statement);
    
        int getArgCount();
    }
    
    class FrameworkSQLiteDatabase implements SupportSQLiteDatabase {
        private final SQLiteDatabase mDelegate;
    
        @Override
        public Cursor query(final SupportSQLiteQuery supportQuery) {
            return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
                @Override
                public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
                        String editTable, SQLiteQuery query) {
                    supportQuery.bindTo(new FrameworkSQLiteProgram(query));
                    return new SQLiteCursor(masterQuery, editTable, query);
                }
            }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null);
        }
        
        //...
    }
    

    查询SQL的执行看上去很简单,但是却暗含着很多并不简单的内容。
    首先,SupportSQLiteQuery的定义就很奇怪,getSql表示SQL语句(包含占位符),getArgCount表示SQL语句中有几个占位符,单凭这两个信息,这个查询SQL根本无法执行,SupportSQLiteQuery中最重要的方法是bindTo,调用这个方法进行参数绑定,在这之后SQL语句才变得完整,才能被执行。按道理来说,SQL语句和绑定参数是配套的,正常流程应该是我们向数据库提供SQL语句及其绑定参数,然后执行查询,但是Room并没有这么做。不这么做居然也能完成查询,真是神奇。
    其次,再来看看真正执行查询的底层方法rawQueryWithFactory,它的完整签名是

    Cursor rawQueryWithFactory(
                CursorFactory cursorFactory, String sql, String[] selectionArgs,
                String editTable)
    

    第一个参数是cursorFactory,即Cursor工厂,这个方法本身我们就不经常使用,即使用第一个参数也往往传null,表示使用默认Cursor工厂;第二个参数是sql,没啥好说的;第三个参数是selectionArgs,即SQL语句的绑定参数;最后一个参数可以忽略。
    Room对于rawQueryWithFactory方法的使用也很奇怪,明明必须要提供SQL语句的绑定参数,但是Room却使用了一个空字符串数组EMPTY_STRING_ARRAY敷衍了过去,这么说来真相就只有一个了,必然是传递的第一个参数cursorFactory有啥魔法,那我们就来仔细看看。

    class FrameworkSQLiteDatabase implements SupportSQLiteDatabase {
        @Override
        public Cursor query(final SupportSQLiteQuery supportQuery) {
            return mDelegate.rawQueryWithFactory(new SQLiteDatabase.CursorFactory() {
                @Override
                public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery,
                        String editTable, SQLiteQuery query) {
                    //调用了SupportSQLiteQuery上的bindTo方法,很可疑
                    supportQuery.bindTo(new FrameworkSQLiteProgram(query));
                    return new SQLiteCursor(masterQuery, editTable, query);
                }
            }, supportQuery.getSql(), EMPTY_STRING_ARRAY, null);
        }
    }
    

    第一个参数是Cursor工厂,要求我们创建一个Cursor出来,按道理来说,如果不是为了创建自定义的Cursor,是没必要传递这个参数的。Room这里还真的没有创建什么特别的、自定义的Cursor出来,还是使用的原来的SQLiteCursor,Room这么做的唯一目的就是为了完成SQL语句的参数绑定,因为我们提供的SQL语句(String类型)会被转化为SQLiteQuery,然后作为参数传递到newCursor方法当中,SQLiteQuerySQLiteProgram的子类,可以被包装成FrameworkSQLiteProgram,正好回调SupportSQLiteQuery上的bindTo方法,进而完成参数的绑定。在newCursor被调用时,查询SQL还未真正执行,此时绑定参数还来得及。
    总结一下,Room使用SQLiteDatabase底层的rawQueryWithFactory方法来执行查询操作,并且在需要提供selectionArgs参数时使用空字符串数组敷衍过去,由于此时查询尚未执行,SQLiteDatabase并不知道我们给的绑定参数是错误的,它只是会默默地帮我们把SQL语句转化为SQLiteQuery,然后Room使用一个并没有实质作用的CursorFactory(仅仅利用了它的调用时机),在查询要执行前的最后一刻,把缺失的绑定参数给补上。真是演了一出偷梁换柱的好戏,妙啊!
    此时,我的内心是忐忑的,生怕有个还没被我绕晕的人,上来怼我一句,有个卵用?!好好地提供selectionArgs参数不就完事了。

    额。。。好,我们进行下一个话题。
    其实,最根本的原因在于“类型安全”,Room是很强调这一点的。这里的类型安全指的是SQL语句在绑定参数时应该依据参数的类型绑定,例如bindStringbindLong等等,而不是什么类型都转换成String再绑定,这么做不安全,也不方便。SQLiteProgram其实是包含有一个叫bindAllArgsAsStrings的方法的,SupportSQLiteProgram在定义时选择将其剔除,不暴露出来。非常遗憾的是,rawQueryWithFactory方法的selectionArgs参数就是一个String[],这并不符合Room的设计理念。

    我们姑且把FrameworkSQLiteDatabase设计出的这套query的方式称之为“延迟绑定”。

    扯了这么多,现在的问题就归结为了如何向SupportSQLiteQuery提供绑定参数,SupportSQLiteQuery只定义了三个方法,并没有定义一个提供绑定参数的方法。

    4.1 SimpleSQLiteQuery和RoomSQLiteQuery

    public interface SupportSQLiteQuery {
        String getSql();
        
        void bindTo(SupportSQLiteProgram statement);
    
        int getArgCount();
    }
    

    如上所述,SupportSQLiteQuery并没有定义一个提供绑定参数的方法。即使FrameworkSQLiteDatabase费那么大劲设计出了“延迟绑定”的方式,在bindTo方法被调用时,我们依然不知道哪里可以提供要绑定的参数。不把绑定参数放到SupportSQLiteQuery中,这似乎非常不合理,毕竟,SQL语句和绑定参数是配套的。其实,Room当中的确包含有这种“正常思路”的SQLiteQuery,这就是SupportSQLiteQuery的实现类SimpleSQLiteQuery

    public final class SimpleSQLiteQuery implements SupportSQLiteQuery {
        private final String mQuery;
        
        //这里保存了要绑定的参数
        @Nullable
        private final Object[] mBindArgs;
    
        public SimpleSQLiteQuery(String query, @Nullable Object[] bindArgs) {
            mQuery = query;
            mBindArgs = bindArgs;
        }
    
        public SimpleSQLiteQuery(String query) {
            this(query, null);
        }
    
        @Override
        public String getSql() {
            return mQuery;
        }
    
        @Override
        public void bindTo(SupportSQLiteProgram statement) {
            bind(statement, mBindArgs);
        }
    
        @Override
        public int getArgCount() {
            return mBindArgs == null ? 0 : mBindArgs.length;
        }
    
        public static void bind(SupportSQLiteProgram statement, Object[] bindArgs) {
            if (bindArgs == null) {
                return;
            }
            final int limit = bindArgs.length;
            for (int i = 0; i < limit; i++) {
                final Object arg = bindArgs[i];
                bind(statement, i + 1, arg);
            }
        }
    
        private static void bind(SupportSQLiteProgram statement, int index, Object arg) {
            // extracted from android.database.sqlite.SQLiteConnection
            if (arg == null) {
                statement.bindNull(index);
            } else if (arg instanceof byte[]) {
                statement.bindBlob(index, (byte[]) arg);
            } else if (arg instanceof Float) {
                statement.bindDouble(index, (Float) arg);
            } else if (arg instanceof Double) {
                statement.bindDouble(index, (Double) arg);
            } else if (arg instanceof Long) {
                statement.bindLong(index, (Long) arg);
            } else if (arg instanceof Integer) {
                statement.bindLong(index, (Integer) arg);
            } else if (arg instanceof Short) {
                statement.bindLong(index, (Short) arg);
            } else if (arg instanceof Byte) {
                statement.bindLong(index, (Byte) arg);
            } else if (arg instanceof String) {
                statement.bindString(index, (String) arg);
            } else if (arg instanceof Boolean) {
                statement.bindLong(index, ((Boolean) arg) ? 1 : 0);
            } else {
                throw new IllegalArgumentException("Cannot bind " + arg + " at index " + index
                        + " Supported types: null, byte[], float, double, long, int, short, byte,"
                        + " string");
            }
        }
    }
    

    可以看出,SimpleSQLiteQuery的确是SupportSQLiteQuery的简单实现类。SimpleSQLiteQuery非常符合“正常思路”下SQLiteQuery该有的样子,有SQL语句,有绑定参数mBindArgs,在bindTo方法被调用时,根据mBindArgs的参数类型绑定一下子就完事了。但是,事实上,Room只在非常特定的条件下使用了SimpleSQLiteQuery,使用条件都是那种,第一,查询语句是固定的;第二,没有绑定参数的。
    Room并没有把SimpleSQLiteQuery作为执行查询操作的通用手段。为什么?主要原因有如下几个方面:

    1. FrameworkSQLiteDatabase费劲巴拉地设计出“延迟绑定”的机制,目的就是为了“类型安全”,如果按照SimpleSQLiteQuery的做法,把所有绑定参数放入Object[]中,那还不如把绑定参数全转换为String类型,然后直接提供给rawQueryWithFactory方法。
    2. 把绑定参数全部放入Object[]中是有性能损耗的,因为需要装箱拆箱操作。
    3. 查询可以说是数据库最重要最常用的操作了,在一个应用中“增删改”操作加在一起可能也没有查询操作多,这么多的查询操作,如果每一个都要生成一个SimpleSQLiteQuery对象,每个SimpleSQLiteQuery对象再包含若干个绑定参数,每个参数的存取又都需要装箱拆箱的话,这对性能的影响是不容忽视的。

    综上所述,比较好的SupportSQLiteQuery的实现应该是:

    1. 绑定参数按照其类型存储,避免不必要的装箱拆箱操作。
    2. 查询操作虽然比较多,但不见得都会用到,SupportSQLiteQuery的对象应该是“懒加载”的,最好SupportSQLiteQuery对象可以复用。

    如你所想,RoomSQLiteQuery就是这么实现的。

    public class RoomSQLiteQuery implements SupportSQLiteQuery, SupportSQLiteProgram {
        
        private volatile String mQuery;
    
        //绑定参数按照其类型存储,避免装箱拆箱
        final long[] mLongBindings;
        final double[] mDoubleBindings;
        final String[] mStringBindings;
        final byte[][] mBlobBindings;
    
        //记录了对应位置的参数类型
        @Binding
        private final int[] mBindingTypes;
        
        final int mCapacity;
        // number of arguments in the query
        int mArgCount;
    
        //回收复用池,按照绑定参数的个数进行分类
        static final TreeMap<Integer, RoomSQLiteQuery> sQueryPool = new TreeMap<>();
    
        /**
         * 获取 RoomSQLiteQuery,按照绑定参数的个数来,可能会复用之前的 RoomSQLiteQuery
         */
        public static RoomSQLiteQuery acquire(String query, int argumentCount) {
            synchronized (sQueryPool) {
                //关键操作,取sQueryPool的ceilingEntry
                //ceiling是天花板的意思,ceilingEntry就是取不小于argumentCount的Entity
                final Map.Entry<Integer, RoomSQLiteQuery> entry =
                        sQueryPool.ceilingEntry(argumentCount);
                if (entry != null) {
                    //如果存在会把它从sQueryPool,这样就不会被别的查询用到了
                    sQueryPool.remove(entry.getKey());
                    final RoomSQLiteQuery sqliteQuery = entry.getValue();
                    //对它进行初始化
                    sqliteQuery.init(query, argumentCount);
                    return sqliteQuery;
                }
            }
            //没有可复用的,则新建一个
            RoomSQLiteQuery sqLiteQuery = new RoomSQLiteQuery(argumentCount);
            sqLiteQuery.init(query, argumentCount);
            return sqLiteQuery;
        }
    
        private RoomSQLiteQuery(int capacity) {
            mCapacity = capacity;
            // SQL的参数绑定,位置是从1开始计数的,不是从0
            int limit = capacity + 1;
            //noinspection WrongConstant
            mBindingTypes = new int[limit];
            mLongBindings = new long[limit];
            mDoubleBindings = new double[limit];
            mStringBindings = new String[limit];
            mBlobBindings = new byte[limit][];
        }
    
        void init(String query, int argCount) {
            mQuery = query;
            mArgCount = argCount;
        }
    
        /**
         * 释放掉 query,让其回到sQueryPool,以便复用
         */
        public void release() {
            synchronized (sQueryPool) {
                sQueryPool.put(mCapacity, this);
                prunePoolLocked();
            }
        }
    
        //sQueryPool如果超过15个,会被修剪到10个
        private static void prunePoolLocked() {
            if (sQueryPool.size() > POOL_LIMIT) {
                int toBeRemoved = sQueryPool.size() - DESIRED_POOL_SIZE;
                final Iterator<Integer> iterator = sQueryPool.descendingKeySet().iterator();
                while (toBeRemoved-- > 0) {
                    iterator.next();
                    iterator.remove();
                }
            }
        }
    
        @Override
        public String getSql() {
            return mQuery;
        }
    
        @Override
        public int getArgCount() {
            return mArgCount;
        }
    
        //根据 mBindingTypes中记录的类型,进行相应的绑定
        @Override
        public void bindTo(SupportSQLiteProgram program) {
            for (int index = 1; index <= mArgCount; index++) {
                switch (mBindingTypes[index]) {
                    case NULL:
                        program.bindNull(index);
                        break;
                    case LONG:
                        program.bindLong(index, mLongBindings[index]);
                        break;
                    case DOUBLE:
                        program.bindDouble(index, mDoubleBindings[index]);
                        break;
                    case STRING:
                        program.bindString(index, mStringBindings[index]);
                        break;
                    case BLOB:
                        program.bindBlob(index, mBlobBindings[index]);
                        break;
                }
            }
        }
    
        @Override
        public void bindNull(int index) {
            mBindingTypes[index] = NULL;
        }
    
        //记录对应位置的参数类型,并且保存相应参数
        @Override
        public void bindLong(int index, long value) {
            mBindingTypes[index] = LONG;
            mLongBindings[index] = value;
        }
    
        @Override
        public void bindDouble(int index, double value) {
            mBindingTypes[index] = DOUBLE;
            mDoubleBindings[index] = value;
        }
    
        @Override
        public void bindString(int index, String value) {
            mBindingTypes[index] = STRING;
            mStringBindings[index] = value;
        }
    
        @Override
        public void bindBlob(int index, byte[] value) {
            mBindingTypes[index] = BLOB;
            mBlobBindings[index] = value;
        }
    
        private static final int NULL = 1;
        private static final int LONG = 2;
        private static final int DOUBLE = 3;
        private static final int STRING = 4;
        private static final int BLOB = 5;
    
        //参数类型标记
        @Retention(RetentionPolicy.SOURCE)
        @IntDef({NULL, LONG, DOUBLE, STRING, BLOB})
        @interface Binding {
        }
    }
    

    RoomSQLiteQuerySupportSQLiteQuery的实现类,是Room进行数据库查询真正用到的类。它的实现主要包含如下内容:

    1. 按照绑定参数的个数分配相应存储空间,以便按照参数的类型保存参数。
    2. RoomSQLiteQuery中包含有回收复用池,我们通过acquire方法获取一个RoomSQLiteQuery对象,再使用完毕后使用release方法释放,该对象就会回到回收复用池。在这两个方法之间,RoomSQLiteQuery对象已经脱离了回收复用池,不会被“打扰”。
    3. 回收复用池是按照RoomSQLiteQuery的绑定参数个数来分类的(TreeMap),从回收复用池中拿对象,会通过ceilingEntity方法去取,保证给我们一个不小于绑定参数个数argumentCount的对象(如果有的话),这样既保证了RoomSQLiteQuery对象的正常使用,又提高了复用的效率。
    4. 诸如bindLong等方法,会在获取到RoomSQLiteQuery对象后被调用(生成的代码),这些方法会记录对应位置的参数类型并保存相应参数,在真正需要绑定参数(bindTo方法被调用)时,根据这些记录的信息进行绑定。

    查询SQL之所以可以被复用的原因在于,对于RoomSQLiteQuery而言,SQL语句是什么并不重要,重要的是绑定参数要有足够的空间去存储(getArgCount决定需要多大空间),getSql返回的SQL语句和bindTo时绑定参数是对应的,就可以了。

    5. Dao实现

    以上所说都是Dao实现“增删改查”的基础,有了这些基础,再来看Dao的实现是很简单的。假设我们的Dao定义如下:

    @Entity
    data class User(
        @PrimaryKey val uid: Int,
        @ColumnInfo(name = "first_name") val firstName: String?,
        @ColumnInfo(name = "last_name") val lastName: String?
    )
    
    @Dao
    interface UserDao {
        @Query("SELECT * FROM user WHERE uid = :userId")
        fun getUserById(userId: Int): User?
    
        @Insert
        fun insert(user: User)
    
        @Delete
        fun delete(user: User)
    
        @Update
        fun update(user: User)
    }
    
    abstract class AppDatabase : RoomDatabase() {
        abstract fun userDao(): UserDao
    }
    

    那么生成的代码如下:

    public final class AppDatabase_Impl extends AppDatabase {
        private volatile UserDao _userDao;
        
        //...
        
        @Override
        public UserDao userDao() {
            if (_userDao != null) {
                return _userDao;
            } else {
                synchronized(this) {
                    if(_userDao == null) {
                        _userDao = new UserDao_Impl(this);
                    }
                    return _userDao;
                }
            }
        }
    }
    
    public final class UserDao_Impl implements UserDao {
      private final RoomDatabase __db;
    
      private final EntityInsertionAdapter __insertionAdapterOfUser;
    
      private final EntityDeletionOrUpdateAdapter __deletionAdapterOfUser;
    
      private final EntityDeletionOrUpdateAdapter __updateAdapterOfUser;
    
      public UserDao_Impl(RoomDatabase __db) {
        this.__db = __db;
        this.__insertionAdapterOfUser = new EntityInsertionAdapter<User>(__db) {
          @Override
          public String createQuery() {
            //SQL语句帮我们生成
            return "INSERT OR ABORT INTO `User`(`uid`,`first_name`,`last_name`) VALUES (?,?,?)";
          }
    
          @Override
          public void bind(SupportSQLiteStatement stmt, User value) {
            //绑定帮我们做好
            stmt.bindLong(1, value.getUid());
            if (value.getFirstName() == null) {
              stmt.bindNull(2);
            } else {
              stmt.bindString(2, value.getFirstName());
            }
            if (value.getLastName() == null) {
              stmt.bindNull(3);
            } else {
              stmt.bindString(3, value.getLastName());
            }
          }
        };
        
        //__deletionAdapterOfUser, __updateAdapterOfUser是类似的
      }
    
      @Override
      public void insert(final User user) {
        __db.assertNotSuspendingTransaction();
        __db.beginTransaction();
        try {
          //插入调用EntityInsertionAdapter上的insert方法
          __insertionAdapterOfUser.insert(user);
          __db.setTransactionSuccessful();
        } finally {
          __db.endTransaction();
        }
      }
    
      @Override
      public void delete(final User user) {
        __db.assertNotSuspendingTransaction();
        __db.beginTransaction();
        try {
          //删除和更新调用EntityDeletionOrUpdateAdapter上handle方法
          __deletionAdapterOfUser.handle(user);
          __db.setTransactionSuccessful();
        } finally {
          __db.endTransaction();
        }
      }
    
      @Override
      public void update(final User user) {
        __db.assertNotSuspendingTransaction();
        __db.beginTransaction();
        try {
          //删除和更新调用EntityDeletionOrUpdateAdapter上handle方法
          __updateAdapterOfUser.handle(user);
          __db.setTransactionSuccessful();
        } finally {
          __db.endTransaction();
        }
      }
    
      @Override
      public User getUserById(final int userId) {
        final String _sql = "SELECT * FROM user WHERE uid = ?";
        //获取RoomSQLiteQuery对象
        final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
        int _argIndex = 1;
        //绑定参数,这里不是最终的参数绑定,只是要记录对应位置的参数类型及参数本身,方便之后真正的参数绑定
        _statement.bindLong(_argIndex, userId);
        __db.assertNotSuspendingTransaction();
        //执行查询拿到Cursor
        final Cursor _cursor = DBUtil.query(__db, _statement, false);
        //把Cursor数据转换成Entity对象
        try {
          final int _cursorIndexOfUid = CursorUtil.getColumnIndexOrThrow(_cursor, "uid");
          final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "first_name");
          final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "last_name");
          final User _result;
          if(_cursor.moveToFirst()) {
            final int _tmpUid;
            _tmpUid = _cursor.getInt(_cursorIndexOfUid);
            final String _tmpFirstName;
            _tmpFirstName = _cursor.getString(_cursorIndexOfFirstName);
            final String _tmpLastName;
            _tmpLastName = _cursor.getString(_cursorIndexOfLastName);
            _result = new User(_tmpUid,_tmpFirstName,_tmpLastName);
          } else {
            _result = null;
          }
          return _result;
        } finally {
          _cursor.close();
          //释放RoomSQLiteQuery对象(回到回收复用池)
          _statement.release();
        }
      }
    }
    

    虽然生成的代码很长,但是都是些套路,也说明这些代码确实适合用注解处理器生成。Dao的实现只是对之前分析内容的运用,没有什么实质性的内容。

    6. 总结

    Room对于“增删改查”实现的关键在于“效率”,针对增删改查不同的执行方式,使用了不同的复用机制。“增删改”复用的核心在于,针对特定的Entity(例如User),“增删改”各自的SQL语句是固定的,绑定参数是固定的,使用SharedSQLiteStatement的方式,只保留一个Statement进行复用是很合理的;查询复用的核心在于,“延迟绑定”+绑定参数存储空间的复用,Room中所有的查询共享一个查询复用池,只在查询真正发生时才从复用池中取,因为查询操作是“最多”的,使用查询复用池大大提升了查询效率。

    相关文章

      网友评论

          本文标题:从Room源码看抽象与封装——Dao

          本文链接:https://www.haomeiwen.com/subject/iyqmhctx.html