仿GreenDao构建Android room缓存

  之前将GreenDao数据库替换成Room数据库后,虽然表的构建和数据库的升级比以往简单了许多,但是也出现了,一个问题,那就是数据不同步问题,因为GreenDao 是有缓存,而Room数据库是没有缓存的

1.比如我们同时调用数据查询方法,根据id 获取两个对像,然后,对其中一个bean 进行update操作,那GreenDao和room分别会走一下流程

     

GreenDao
GeenDao数据获取流程

因为GreenDao有缓存操作,所有其实两个Bean所对应的是同一块内存,所以当 其中一个bean更新的时候,另外一个bean也会同步更新。

Room数据库

而Room数据库,每次获取对象都会开辟一个新的内存,所以 两个对象对应的内存块,不一致,因此当数据修改时候,不会同步

 于是就想参照GreenDao对Room最一个数据缓存。

首先我们来看看GreenDao的数据缓存机制

1.首先是一个缓存对象

GreenDao的数据缓存对象

IdentityScope持有一个锁和一个map对象,然后实现,一些列的增加和删除操作,之所以要实现一个带锁和不带锁的方法,是因为之后有一些比较复杂的操作,需要通过,一些列的复合操作的实现,所以我们可以先调用lock 然后在一系列复合操作完成之后去调用unlock释放锁。详细的操作可以参见IdentityScopeObject的实现。所以GreenDao是数据缓存结构其实很简单。

/*
 * Copyright (C) 2011-2016 Markus Junginger, greenrobot (http://greenrobot.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.greenrobot.greendao.identityscope;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 * The context for entity identities. Provides the scope in which entities will be tracked and managed.
 * 
 * @author Markus
 * @param <K>
 * @param <T>
 */
public class IdentityScopeObject<K, T> implements IdentityScope<K, T> {
    private final HashMap<K, Reference<T>> map;
    private final ReentrantLock lock;

    public IdentityScopeObject() {
        map = new HashMap<K, Reference<T>>();
        lock = new ReentrantLock();
    }

    @Override
    public T get(K key) {
        Reference<T> ref;
        lock.lock();
        try {
            ref = map.get(key);
        } finally {
            lock.unlock();
        }
        if (ref != null) {
            return ref.get();
        } else {
            return null;
        }
    }

    @Override
    public T getNoLock(K key) {
        Reference<T> ref = map.get(key);
        if (ref != null) {
            return ref.get();
        } else {
            return null;
        }
    }

    @Override
    public void put(K key, T entity) {
        lock.lock();
        try {
            map.put(key, new WeakReference<T>(entity));
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void putNoLock(K key, T entity) {
        map.put(key, new WeakReference<T>(entity));
    }

    @Override
    public boolean detach(K key, T entity) {
        lock.lock();
        try {
            if (get(key) == entity && entity != null) {
                remove(key);
                return true;
            } else {
                return false;
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void remove(K key) {
        lock.lock();
        try {
            map.remove(key);
        } finally {
            lock.unlock();
        }
    }
    
    @Override
    public void remove(Iterable< K> keys) {
        lock.lock();
        try {
            for (K key : keys) {
                map.remove(key);
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void clear() {
        lock.lock();
        try {
            map.clear();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void lock() {
        lock.lock();
    }

    @Override
    public void unlock() {
        lock.unlock();
    }

    @Override
    public void reserveRoom(int count) {
        // HashMap does not allow
    }

}

2.搞清楚了GreenDao的数据缓存结构,我们接下来就要搞清楚,数据在增删改查的时候怎么样去同步数据库和缓存之间的数据

Daosession
​​​​​​

要从 AbstractDao这个类看起

AbstractDao这个类含有一个DB对象,和一个identity缓存对象。

所以这个Dao其实就是数据操作的入口,当我们设置没有缓存的时候,就将IdentiyScope设置为空,如果是有缓存的时候,就注入相应的数据缓存类型

1.在数据操作的时候,同时同步缓存数据,具体实现如下

  protected final DaoConfig config;
    protected final Database db;
    protected final boolean isStandardSQLite;
    protected final IdentityScope<K, T> identityScope;
    protected final IdentityScopeLong<T> identityScopeLong;
    protected final TableStatements statements;

    protected final AbstractDaoSession session;
    protected final int pkOrdinal;

    private volatile RxDao<T, K> rxDao;
    private volatile RxDao<T, K> rxDaoPlain;

    public AbstractDao(DaoConfig config) {
        this(config, null);
    }

    @SuppressWarnings("unchecked")
    public AbstractDao(DaoConfig config, AbstractDaoSession daoSession) {
        this.config = config;
        this.session = daoSession;
        db = config.db;
        isStandardSQLite = db.getRawDatabase() instanceof SQLiteDatabase;
        identityScope = (IdentityScope<K, T>) config.getIdentityScope();
        if (identityScope instanceof IdentityScopeLong) {
            identityScopeLong = (IdentityScopeLong<T>) identityScope;
        } else {
            identityScopeLong = null;
        }
        statements = config.statements;
        pkOrdinal = config.pkProperty != null ? config.pkProperty.ordinal : -1;
    }

在构造方法中传入Db相关参数 比如 DaoConfig, db,statements等参数,主要是为了实现数据库操作,而具体的数据库实现方式我们这边就不深究了

接下去我们看一个简单的数据操作的实现

   public T load(K key) {
        assertSinglePk();
        if (key == null) {
            return null;
        }
        if (identityScope != null) {
            T entity = identityScope.get(key);
            if (entity != null) {
                return entity;
            }
        }
        String sql = statements.getSelectByKey();
        String[] keyArray = new String[]{key.toString()};
        Cursor cursor = db.rawQuery(sql, keyArray);
        return loadUniqueAndCloseCursor(cursor);
    }

根据Key值,也就是主键,加载数据,先从缓存中获取,没有就从数据库获取,这也是很常规的缓存操作。

最后就是DaoSession的管理

AbstractDaoSession 这是所有DaoSession的入口,通过Bean的class为主键存储一系列的Dao,然后通过主键,获取一系列的Dao

所有比较简单的数据库操作都可以通过这个类直接调用,比如一般的增删查该操作。

好了 接下来我们回到Room数据库,具体Room数据库的时候我们就不在这边讲了

我们知道Room数据库,已经对db进行了,封装,而直接暴露给我们的接口已经是Dao层了,所以我们没办法像GreenDao那样通过封装Db 和IdentityScope来实现缓存,机制,那我们怎么办?

我这边采取的方法是,通过把Identity和Dao进行再一层的封装,封装成DaoWrapper。然后,进行统一的数据操作,要实现这一步

我们首先必须为Dao定义一个统一接口,这样才能实现代码的抽取,接口如下:

每个Room的Dao实现这个接口,然后自定义一系列DB操作。

然后是一个AbsDaoWrapper的简单实现,增删查改操作:代码如下。




public abstract class AbstractDaoWrapper<T, K> {
    /**数据缓存类*/
    private IMTdentityScope<T, K> mIdentityScope;

    /**默认的插入策略是插入更新*/
    private IInsertStrategy<T, K> mInsertStrategy = new ReplaceStrategy<>();

    // 另外一个是数据库操作
    private IDataResource<T, K> mRepository;

    public  AbstractDaoWrapper(@MTIdentityScopeType.IdentityType int sessionType,
        @IInsertStrategy.InsertStrategy int insertStrategy, IDataResource<T, K> repository) {
        // 初始化数据库操作
        this.mRepository = repository;
        // 初始化缓存操作
        initIdentityScope(sessionType);
        // 初始化插入策略
        if (insertStrategy == IInsertStrategy.STRATEGY_INGNORE) {
            mInsertStrategy = new IngoreStrategy<>();
        } else if (insertStrategy == IInsertStrategy.STRATEGY_REPLACE) {
            mInsertStrategy = new ReplaceStrategy<>();
        }

    }

    private void initIdentityScope(@MTIdentityScopeType.IdentityType int type) {
        if (type == MTIdentityScopeType.NONE) {
            mIdentityScope = null;
        } else if (type == MTIdentityScopeType.SESSION) {
            mIdentityScope = new MTIdentityObject<>();
        }
    }

    /**
     * 定义增删查改,和更新操作
     * @param key
     * @return
     */
    public T load(K key) {
        // 先从缓存中取
        if (mIdentityScope != null) {
            T entity = mIdentityScope.get(key);
            if (entity != null) {
                return entity;
            }

        }
        return loadFromDb(key);
    }

    /**
     * 从Db中拿数据
     * @return
     */
    private T loadFromDb(K key) {
        if (mRepository == null) {
            return null;
        }
        return mRepository.loadEntity(key);
    }

    /**
     * 获取全部,GreenDao的做法是根据cursor去遍历主键
     * 如果缓存中存在的话就去缓存中去,如果缓存中没有的话就去数据库去
     * 这也是一级缓存常用的做法,会做两条sql
     *
     *
     *
     * 然后room的cursor没有暴露处理啊,所以采取的做法是
     * 方案1:从数据库查找出所有的数据,然后与本地进行比对
     * 方案2:类似greenDao从数据库先查找主键,然后根据主键更新
     * @return
     */
    public List<T> loadAll() {
        // 先从数据库当中读出所有的key
        List<K> keys = mRepository.loadKeys();
        List<T> list = new ArrayList<>();
        if (keys != null && !keys.isEmpty()) {
            // 加锁避免数据
            lockScope();
            try {
                for (K key : keys) {
                    list.add(loadCurrent(key, false));
                }
            } finally {
                unLockScope();
            }
        }
        return list;
    }

    private T loadCurrent(K key, boolean isLock) {
        // 首先先去缓存中寻找
        if (mIdentityScope != null) {
            T entity = isLock ? mIdentityScope.get(key) : mIdentityScope.getNoLock(key);
            if (entity == null) {
                // 去数据库找,然后更新到缓存中
                entity = loadFromDb(key);
                if (isLock) {
                    mIdentityScope.put(key, entity);
                } else {
                    mIdentityScope.put(key, entity);
                }

            }
            return entity;
        } else {
            // 直接返回数据库中的数据
            return loadFromDb(key);
        }
    }

    /**
     * 插入要解决主键问题,和策略问题,默认策略:重复替换
     * @param entity
     */
    public void insert(T entity) {
        if (entity == null) {
            return;
        }
        insertToDb(entity);
        K key = readKey(entity);
        if (mInsertStrategy != null && key != null) {
            mInsertStrategy.insert(key, entity, mIdentityScope);
        }

    }

    /**
     * 插入到数据库
     */
    private void insertToDb(T entity) {
        if (mRepository != null) {
            mRepository.insert(entity);

        }
    }

    private void attachEntity(T entity, boolean isLock) {
        K key = readKey(entity);
        if (mIdentityScope != null && key != null) {
            if (isLock) {
                mIdentityScope.put(key, entity);
            } else {
                mIdentityScope.putNoLock(key, entity);
            }
        }

    }

    /**
     * 插入相应的队列
     * @param entities
     */
    public void insertEntityList(Iterable<T> entities) {
        if (entities == null) {
            return;
        }
        insertToDb(entities);
        if (mInsertStrategy != null) {
            Map<K, T> maps = new HashMap<>(16);
            for (T entity : entities) {
                maps.put(readKey(entity), entity);
            }
            mInsertStrategy.insertAll(maps, mIdentityScope);
        }

    }

    private void lockScope() {
        if (mIdentityScope != null) {
            mIdentityScope.lock();
        }
    }

    private void unLockScope() {
        if (mIdentityScope != null) {
            mIdentityScope.unLock();
        }
    }

    /**
     * 入库操作
     * @param entities
     */
    private void insertToDb(Iterable<T> entities) {
        // 批量插入数据库
        if (mRepository != null) {
            mRepository.insert(entities);

        }

    }

    /**
     * 删除操作
     * @param entity
     */
    public void delete(T entity) {
        if (entity == null) {
            return;
        }
        deleteFromDb(entity);
        dettachEntity(entity);
    }

    public void delete(Iterable<T> entities) {
        if (entities == null) {
            return;
        }
        deleteFromDb(entities);
        // 从缓存中删除
        List<K> keys = new ArrayList<>();
        for (T entity : entities) {
            K key = readKey(entity);
            if (key != null) {
                keys.add(key);
            }
        }
        dettachEntities(keys);
    }

    private void deleteFromDb(T entity) {
        // 从数据库移除
        if (mRepository != null) {
            mRepository.delete(entity);

        }

    }

    private void deleteFromDb(Iterable<T> entities) {
        if (mRepository != null) {
            mRepository.deleteAll(entities);

        }

    }

    /**
     * 取消内存的关联
     * @param entity
     */
    private void dettachEntity(T entity) {
        if (entity == null) {
            return;
        }
        K key = readKey(entity);
        if (key != null && mIdentityScope != null) {
            // 从缓存中移除
            mIdentityScope.remove(key);
        }

    }

    private void dettachEntities(Iterable<K> entities) {
        if (entities != null && mIdentityScope != null) {
            mIdentityScope.remove(entities);
        }
    }

    /**
     * 更新当前的数据
     * @param entity
     */
    public void updateEntity(T entity) {
        if (mIdentityScope != null) {
            mIdentityScope.put(readKey(entity), entity);
        }
        // 更新数据库数据
        updateToDb(entity);
    }

    /**
     * 批量插入
     * @param entities
     */
    public void updateEntities(Iterable<T> entities) {
        updateToDb(entities);
        lockScope();
        // 同步更新缓存数据
        for (T entity : entities) {
            attachEntity(entity, false);
        }
        unLockScope();

    }

    /**
     * 根据key值来获取值,实现条件获取
     * @param keys
     * @return
     */
    public List<T> loadByKeys(List<K> keys) {
        List<T> list = new ArrayList<>();
        if (keys != null && !keys.isEmpty()) {
            // 加锁避免数据
            lockScope();
            try {
                for (K key : keys) {
                    list.add(loadCurrent(key, false));
                }
            } finally {
                unLockScope();
            }
        }
        return list;

    }

    public void updateToDb(Iterable<T> entities) {
        if (mRepository != null) {
            mRepository.updateAll(entities);

        }

    }

    public void updateToDb(T entity) {
        if (mRepository != null) {
            mRepository.update(entity);

        }

    }

    public abstract K readKey(T entity);

}

a.在代码里面我们也可以通过传入缓存策略来实现,是否需要缓存操作:

b.同时根据策略模式来决定,插入时候冲突的解决办法

忽略策略:

public class IngoreStrategy<T, K> implements IInsertStrategy<T, K> {
    @Override
    public void insert(K key, T entity, IdentityScope<T, K> imTdentityScope) {
        // 首先执行数据库的操作

        // 然后判断是否存在如果不存在当前的key,则不插入
        if (imTdentityScope != null) {
            imTdentityScope.lock();
            try {
                T cacheEntity = imTdentityScope.getNoLock(key);
                if (cacheEntity == null) {
                    imTdentityScope.putNoLock(key, entity);
                }
            } finally {
                imTdentityScope.unLock();
            }

        }

    }

    @Override
    public void insertAll(Map<K, T> entities, IdentityScope<T, K> imTdentityScope) {
        // 数据库操作
        if (imTdentityScope != null) {
            imTdentityScope.lock();
            Iterable<K> keys = entities.keySet();
            try {
                for (K key : keys) {
                    T entity = imTdentityScope.getNoLock(key);
                    if (entity == null) {
                        imTdentityScope.putNoLock(key, entities.get(key));
                    }
                }
            } finally {
                imTdentityScope.unLock();
            }

        }

    }
}

替代策略:

public class ReplaceStrategy<T, K> implements IInsertStrategy<T, K> {
    @Override
    public void insert(K key, T entity, IdentityScope<T, K> imTdentityScope) {
        if (imTdentityScope != null && entity != null && key != null) {
            imTdentityScope.lock();
            try {
                imTdentityScope.putNoLock(key, entity);
            } finally {
                imTdentityScope.unLock();
            }

        }

    }

    @Override
    public void insertAll(Map<K, T> entities, IdentityScope<T, K> imTdentityScope) {
        if (imTdentityScope != null && entities != null) {
            imTdentityScope.lock();
            Iterable<K> keys = entities.keySet();
            try {
                for (K key : keys) {
                    imTdentityScope.putNoLock(key, entities.get(key));
                }
            } finally {
                imTdentityScope.unLock();
            }

        }

    }
}

在实现这个AbsDaoWrapper的时候遇到一个问题,那就是条件查找如何实现,因为GreenDao可以直接进行sql的语句的封装,而我们这边以Dao作为数据库操作的如果,无法统一数据的查询方式,所以我的解决办法是,在AbsDaoWrapper中实现,根据key来查找数据的方法,也就是说,AbsDaoWrapper的是子类,要实现条件查找的过程如下:1.通过条件查找相应的key list,--》根据keyList去缓存中查找,如果缓存中没有,---》则去数据库查找,并添加到缓存中。

List<Key> keys = dao.getKeys();

List<Entity> = dao.loadByKeys(keys)

然后最后仿照GreenDao去构造一个DaoWrapper的管理器:

public class AbsDaoSession {
    public final Map<Class<?>, AbstractDaoWrapper<?, ?>> entityToDao;

    public AbsDaoSession() {
        entityToDao = new HashMap<>(16);
    }

    public <T> void registerEntityDao(Class<T> entity, AbstractDaoWrapper<T, ?> abstractDao) {
        entityToDao.put(entity, abstractDao);
    }

    public AbstractDaoWrapper<?, ?> getDao(Class<?> entityClass) {
        AbstractDaoWrapper abstractDao = entityToDao.get(entityClass);
        return abstractDao;

    }

    /**
     * 查询操作
     * @param entityClass
     * @param key
     * @param <T>
     * @param <K>
     * @return
     */
    public <T, K> T load(Class<T> entityClass, K key) {
        AbstractDaoWrapper<T, K> dao = (AbstractDaoWrapper<T, K>) getDao(entityClass);
        if (dao == null) {
            return null;
        }
        return dao.load(key);

    }

    public <T, K> List<T> loadAll(Class<T> entiytClass) {
        AbstractDaoWrapper<T, K> dao = (AbstractDaoWrapper<T, K>) getDao(entiytClass);
        if (dao == null) {
            return null;
        }
        return dao.loadAll();

    }

    /**
     * 插入操作
     * @param entityClass
     * @param entity
     * @param <T>
     * @param <K>
     */
    public <T, K> void insert(Class<T> entityClass, T entity) {
        AbstractDaoWrapper<T, K> dao = (AbstractDaoWrapper<T, K>) getDao(entityClass);
        if (dao != null) {
            dao.insert(entity);
        }
    }

    public <T, K> void insert(Class<T> entityClass, Iterable<T> list) {
        AbstractDaoWrapper<T, K> dao = (AbstractDaoWrapper<T, K>) getDao(entityClass);
        if (dao != null) {
            dao.insertEntityList(list);
        }
    }

    /**
     * 删除操作
     * @param entityClass
     * @param entity
     * @param <T>
     * @param <K>
     */
    public <T, K> void delete(Class<T> entityClass, T entity) {
        AbstractDaoWrapper<T, K> dao = (AbstractDaoWrapper<T, K>) getDao(entityClass);
        if (dao != null) {
            dao.delete(entity);
        }

    }

    public <T, K> void deleteList(Class<T> entityClass, Iterable<T> list) {
        AbstractDaoWrapper<T, K> dao = (AbstractDaoWrapper<T, K>) getDao(entityClass);
        if (dao != null) {
            dao.delete(list);
        }

    }

    /**
     * 更新操作
     */
    public <T, K> void updateEntity(Class<T> entityClass, T entity) {
        AbstractDaoWrapper<T, K> dao = (AbstractDaoWrapper<T, K>) getDao(entityClass);
        if (dao != null) {
            dao.updateEntity(entity);
        }

    }

    public <T, K> void updateEntities(Class<T> entityClass, Iterable<T> entities) {
        AbstractDaoWrapper<T, K> dao = (AbstractDaoWrapper<T, K>) getDao(entityClass);
        if (dao != null) {
            dao.updateEntities(entities);
        }

    }

}

猜你喜欢

转载自blog.csdn.net/soybeen/article/details/84947854
今日推荐