美文网首页第三方程序员Android知识
GreenDAO 3.2 源码分析(2):获得查询结果

GreenDAO 3.2 源码分析(2):获得查询结果

作者: 登高且赋 | 来源:发表于2017-03-20 18:47 被阅读344次

    在前文GreenDAO 3.2 源码分析(1):查询语句与Query实例的构造中,我们分析了greenDAO是如何构建SQL语句并维护Query对象的。为了提高在多线程的操作中的效率,greenDAO并没有采用上锁的机制,而是对每一个线程都单独分配一个Query对象来执行查询操作,那Query对象是获得查询结果的呢?让我们在本文中一探究竟吧。

    2. 1查询的种类

    Query类中一共定义了四种获得查询结果的方法:

    • list() : 将全部结果都放在内存中,以list形式返回;
    • listLazy() : 只用真正使用到数据时, 才回去访问数据库获得数据。注意需要主动关闭游标;
    • listLazyUncached(): 和listLazy()类似,但是没有使用cache,所以数据不能复用,每次都要去数据库中读取;
    • listIterator(): 以List迭代体的形式返回结果,当迭代体遍历会后,游标关闭。

    看到这么多方法供选择,是不是满脑子都是问号:每种方法在实现上又有什么不同呢?在设计上有什么考虑呢?应该在哪种场景中使用呢?有问题就有意义,让我们从每个方法的源码上开始分析吧。

    2.2 list()

    简单粗暴,先来源码。

    /** Executes the query 
     * and returns the result as a list 
     * containing all entities loaded into memory. */
    public List<T> list() {
        //1. 检查当前线程和Query是否匹配;
        checkThread();
        //2. 获得游标;
        Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
        //3. 通过该游标将所有的数据都遍历读取存入内存中
        return daoAccess.loadAllAndCloseCursor(cursor);
    }
    

    正如源码中注释所说的,list()是将所有的查询结果都读入内存中,然后以List的形式返回查询结果,List中的每一个元素都是一行查询结果。代码的逻辑也很清晰,一共三行:

    • 首先检查当前线程和当前的Query实例是否匹配前文中已经提过了,greenDAO框架中,执行查询之前,会为不同线程分配不同Query, 以避免多线程查询的冲突,所以在创建Query实例时,Query实例就会把创建它的线程对象保存起来。当要执行查询时,首先要判断当前调用它的线程是否就是创建它的线程,如果不是,就会抛出异常,其代码如下:
        protected void checkThread() {
            if (Thread.currentThread() != ownerThread) {
                throw new DaoException(
                        "Method may be called only in owner thread, use forCurrentThread to get an instance for this thread");
            }
        }
    
    • 获得游标。这是数据库查询的标准动作,有了游标才能一行一行的处理查询结果,而重点就是如何处理;
    • 使用游标获得所有的查询结果,并把它放到LIst中list()通过游标遍历所欲的查询结果,把获取到的所有数据都存在内存中。执行的过程中,最终会调用* loadAllFromCursor*方法,其代码如下:
    /** Reads all available rows from the given cursor and returns a list of entities. */
    List<T> loadAllFromCursor(Cursor cursor){
    .....
      // 1. 确保游标在开始处
      if (cursor.moveToFirst()) {
         if (identityScope != null) {
            identityScope.lock();
            identityScope.reserveRoom(count);
        }
        try {
            if (!useFastCursor && window != null && identityScope != null) {
                loadAllUnlockOnWindowBounds(cursor, window, list);
            } else {
               //2. 循环语句开始
                do {
                    //3. 将查询结果放入list中
                    list.add(loadCurrent(cursor, 0, false));
                } while (cursor.moveToNext());  //4. 通过循环遍历每一行数据,将其保存在list中;
            }
        } finally {
            if (identityScope != null) {
                identityScope.unlock();
            }
        }
    }
    

    该函数中有大量的维护查询操作的代码,以上只是节选的部分,请注意注释2,3,4所标识的do-while循环,正是这里把所有的查询结果都放入了List中。

    3. listLazy() & listLazyUncached()

    这两个方法关系紧密,所以放在一起说。通过方法名字可能有些读者已经猜到了:既然list方法是把所有结果都取出来放在内存中,那带有Lazy的方法就是比较“懒”的,并不一次性把结果取出来。事实上的确如此,这两个方法获得游标后,并不着急把全部结果都取出来,而是等到真正要使用某个结果时,再去数据库中读出数据。两种方法的差别在于,listLazy()使用缓存机制,一个结果被使用过后会被保留下来,下次再使用该结果时,就不用再去数据库中读取,而 listLazyUncached方法不使用缓存机制,所有结果在使用后不保存,每一次都需要去数据中读取。

    /**
    * Executes the query and returns the result as a list that lazy loads the entities
    * on first access. Entities are cached, so accessing the same entity more than 
    * once will not result in loading an entity from the underlying cursor again.
    * Make sure to close it to close the underlying cursor.
    */
    public LazyList<T> listLazy() {
        checkThread();
        Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
        return new LazyList<T>(daoAccess, cursor, true);
    }
    
    public LazyList<T> listLazyUncached() {
        checkThread();
        Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
        return new LazyList<T>(daoAccess, cursor, false);
    }
    

    二者的源码和list方法很像,只不过是最后一部返回的是自定义类型* LazyList,而listLazy* 和 listLazyUncached的区别就体现在如何构建LazyList。下面是其构造函数

    * A thread-safe, unmodifiable list that reads entities 
    * once they are accessed from an underlying database cursor.
    LazyList(InternalQueryDaoAccess<E> daoAccess, Cursor cursor, boolean cacheEntities) {
        this.cursor = cursor;
        this.daoAccess = daoAccess;
        size = cursor.getCount();
        if (cacheEntities) { //1. 如果使用Cache
            //2. 虽然创建了大小和游标查询数量相同的list,但是没有添加对象
            entities = new ArrayList<E>(size);
            for (int i = 0; i < size; i++) {
                entities.add(null);//3. 都是null
            }
        } else {
            entities = null; //不适用cache
        }
        if (size == 0) {
            cursor.close();
        }
    
        lock = new ReentrantLock();
    }
    

    LazyList是一个不可修改的多线程安全的List。

    由源码可以发现,当使用缓存时,LazyList构建了一个和查询结果个数相同大小的List entities,但是并没有将查询结果取出,而是把这个List里所有的元素都设为空引用;当不适用cache的模式下,entities直接为空。那何时真正获得要查询的结果呢?别忘记,LazyList也是当做List使用的,只有当真正去get(int position)时,entities才去加载内容。这是有些类似于代理模式,表面上是使用LazyList,实际上还是由List entities来完成操作的。

    @Override
    public E get(int location) {
        if (entities != null) {
            E entity = entities.get(location); 
            //1. 第一次获得对象
            if (entity == null) {
                lock.lock(); //上锁,在释放锁之前都要考虑多线程操作的问题
                try {
                    //2. 第二次获得对象,因为在因上锁而等待期间,
                    // 可能有别的进程已经获得了该对象,所以需要再判断一遍
                    entity = entities.get(location); 
                    if (entity == null) {
                        //3. 正真从数据库中获得对象
                        entity = loadEntity(location);
                        entities.set(location, entity);//将该对象添加到entities中
                        // Ignore FindBugs: increment of volatile is fine here because we use a lock
                         //4. 标记已经获得对象的个数,如果对象已经全部获得,则可以关闭游标
                        loadedCount++;
                       
                        if (loadedCount == size) {
                            cursor.close();
                        }
                    }
                } finally {
                    lock.unlock();//释放锁
                }
            }
            return entity;
        } else { //5. 不用cache
            lock.lock();
            try {
                return loadEntity(location);//6. 直接获取对象,并不使用缓存
            } finally {
                lock.unlock();
            }
        }
    }
    

    get方法正是体现出LazyList多线程安全的地方,它考虑到多个线程可能对其内部List所带来的影响:

    1. 首先判断要获取的对象是否有缓存;
    2. 如果没有则上锁,然后第二次查看该对象是否有缓存,以防止在上锁之前有线程已经获查询了该对象,从而避免重复获取;
    3. 两次判断都没有获得对象缓存之后,才正在从数据库中获得该对象,并将其放入缓存;
    4. 对象加入缓存后,记录当前已经获得对象的个数,如果entities已经被填充完毕之后,则关闭游标;
    5. 如果不使用缓存,每次都需要从数据库中获取数据。

    在上锁之前之后都查询对象缓存是否存在,保证避免多线程操作中冲突和重复,这种设计通用而有效的。

    也许有的读者会有疑问,greenDAO是不会通过给每个进程都分配Query对象来避免上锁吗,这里为什么还是加锁? 其实这个不矛盾,greanDAO避免的是数据库操作的上锁,而这里是对查询结果List上锁,因为懒加载的原因,listLazy的结果可能已经被使用者获得,而这个结果集合LazyList可能是被多个进程使用的,所以要在获得查询结果上加锁。

    值得注意的是,LazyList是不可修改的,所以其覆盖了list中add,set以及remore方法,如果要修改list中的元素就会报错,比如:

    @Override
    public boolean add(E object) {
        throw new UnsupportedOperationException();
    }
    

    使用完毕之后如果需要关闭游标,需要自己手动调用LazyList对象的close方法

    4. listIterator()

    有了List,自然就有其迭代器。listIterator()就是返回listLazy中的迭代器。

        public CloseableListIterator<T> listIterator() {
            return listLazyUncached().listIteratorAutoClose();
        }
    
        public CloseableListIterator<E> listIteratorAutoClose() {
            return new LazyIterator(0, true);
        }
    

    LazyIterator是LazyList中的内置迭代器,其源码如下:

    protected class LazyIterator implements CloseableListIterator<E> {
            private int index;
            private final boolean closeWhenDone;
    
            public LazyIterator(int startLocation, boolean closeWhenDone) {
                index = startLocation;
                this.closeWhenDone = closeWhenDone;
            }
    
            @Override
            public void add(E object) {
                throw new UnsupportedOperationException();
            }
    
            @Override
            /** FIXME: before hasPrevious(), next() must be called. */
            public boolean hasPrevious() {
                return index > 0;
            }
    
            @Override
            public int nextIndex() {
                return index;
            }
    
            @Override
            /** FIXME: before previous(), next() must be called. */
            public E previous() {
                if (index <= 0) {
                    throw new NoSuchElementException();
                }
                index--;
                E entity = get(index);
                // if (index == size && closeWhenDone) {
                // close();
                // }
                return entity;
            }
    
            @Override
            public int previousIndex() {
                return index - 1;
            }
    
            @Override
            public void set(E object) {
                throw new UnsupportedOperationException();
            }
    
            @Override
            public boolean hasNext() {
                return index < size;
            }
    
            @Override
            public E next() {
                if (index >= size) {
                    throw new NoSuchElementException();
                }
                E entity = get(index);
                index++;
                if (index == size && closeWhenDone) {
                    close();
                }
                return entity;
            }
    
            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
    
            @Override
            public void close() {
                LazyList.this.close();
            }
    
        }
    

    这里的代码就很直接明白了,在next方法中调用listLazyget方法,其他要改变list元素的方法都被定义成抛出异常。另外如参数closeWhenDone参数被设置为true的话,当迭代器遍历全部内容之后,就会自动关闭游标。

    2.4 使用场景

    • list() : 将全部结果都放在内存中,这样最为直接和常见,但是如果结果集特别大的话,这样做对于内存的压力比较大;此外,如果只是使用结果集中的一小部分,内存就会很是浪费;
    • listLazy() : 只用真正使用到数据时, 才回去访问数据库获得数据,数据加载有延迟,但是比较节省内存,如果不是立刻使用数据结果集,可以考虑;
    • listLazyUncached(): 和listLazy()类似,但是没有使用cache,所以数据不能复用,每次都要去数据库中读取,如果结果集的数据不是被反复使用的话,这样做是最为节省内存的;
    • listIterator(): 以迭代体的形式返回结果,这样的做法更为自由, 如果需要自定义处理过程的话,可以考虑该方法遍历所有结果。

    相关文章

      网友评论

        本文标题:GreenDAO 3.2 源码分析(2):获得查询结果

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