之前将GreenDao数据库替换成Room数据库后,虽然表的构建和数据库的升级比以往简单了许多,但是也出现了,一个问题,那就是数据不同步问题,因为GreenDao 是有缓存,而Room数据库是没有缓存的
1.比如我们同时调用数据查询方法,根据id 获取两个对像,然后,对其中一个bean 进行update操作,那GreenDao和room分别会走一下流程
因为GreenDao有缓存操作,所有其实两个Bean所对应的是同一块内存,所以当 其中一个bean更新的时候,另外一个bean也会同步更新。
而Room数据库,每次获取对象都会开辟一个新的内存,所以 两个对象对应的内存块,不一致,因此当数据修改时候,不会同步
。
于是就想参照GreenDao对Room最一个数据缓存。
首先我们来看看GreenDao的数据缓存机制
1.首先是一个缓存对象
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的数据缓存结构,我们接下来就要搞清楚,数据在增删改查的时候怎么样去同步数据库和缓存之间的数据
要从 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);
}
}
}