采坑記之greendao緩存
項目里面ORM框架用的greendao.測試中出現(xiàn)一個問題,在一個界面獲取數(shù)據(jù)庫的一個對象躬它,然后更改對象的屬性值腾啥,沒有點(diǎn)擊保存按鈕。再進(jìn)入這個界面時,從數(shù)據(jù)庫同樣獲取的這個對象居然改變了碑宴。
之前有看到網(wǎng)上說greendao有緩存软啼,所以獲取數(shù)據(jù)比較快,我猜想這里碰到的應(yīng)該也是這個問題延柠。
我模擬獲取數(shù)據(jù)對象的示例代碼,首先拿到對象祸挪,然后設(shè)置一個屬性后,再去數(shù)據(jù)庫獲取對象打印這個屬性值
UserInfoDomain userInfoDomain=VeryFitPlusDao.getInstance().getUserInfoDomain();
DebugLog.d(userInfoDomain.getShowName());
userInfoDomain.setShowName("hahahah");
DebugLog.d(VeryFitPlusDao.getInstance().getUserInfoDomain().getShowName());
獲取用戶信息的代碼也很簡單贞间,直接調(diào)用greendao的API
/**
* 獲取當(dāng)前用戶信息
*/
public UserInfoDomain getUserInfoDomain(){
UserInfoDomainDao dao= daoSession.getUserInfoDomainDao();
UserInfoDomain userInfoDomain=dao.queryBuilder().where(UserInfoDomainDao.Properties.UserId.eq(getUserId())).unique();
if (userInfoDomain!=null){
DebugLog.d("獲取的用戶信息:"+userInfoDomain.toString());
return userInfoDomain;
}else{
userInfoDomain=new UserInfoDomain();
return userInfoDomain;
}
}
結(jié)果打印的值
到底經(jīng)歷了啥贿条??我都沒有調(diào)用保存數(shù)據(jù)庫的代碼霸鋈取U浴!峻仇!
憋急公黑,先把greendao獲取數(shù)據(jù)的源碼追蹤看一遍。
首先根據(jù)條件獲取對象
HealthSleepDomain sleepDomain=dao.queryBuilder().where(dao.queryBuilder().and(conditionDate,conditionUserId)).unique()
調(diào)用的是QueryBuilder的unique方法
QueryBuilder
public T unique() {
return build().unique();
}
繼續(xù)查看.調(diào)用的是daoAccess對象的loadUniqueAndCloseCursor方法
QueryBuilder
public T unique() {
checkThread();
Cursor cursor = dao.getDatabase().rawQuery(sql, parameters);
return daoAccess.loadUniqueAndCloseCursor(cursor);
}
daoAccess是一個InternalQueryDaoAccess對象摄咆。里面的方法去交給dao去處理了,而dao就算一個繼承了AbstractDao的類
InternalQueryDaoAccess
public T loadUniqueAndCloseCursor(Cursor cursor) {
return dao.loadUniqueAndCloseCursor(cursor);
}
繼續(xù)查看AbstractDao的loadUniqueAndCloseCursor方法凡蚜。內(nèi)部又調(diào)用了loadUnique方法
AbstractDao
protected T loadUniqueAndCloseCursor(Cursor cursor) {
try {
return loadUnique(cursor);
} finally {
cursor.close();
}
}
loadUnique方法,判斷是否有數(shù)據(jù)吭从,如果沒有數(shù)據(jù)直接返回null,有數(shù)據(jù)里面調(diào)用了loadCurrent方法
protected T loadUnique(Cursor cursor) {
boolean available = cursor.moveToFirst();
if (!available) {
return null;
} else if (!cursor.isLast()) {
throw new DaoException("Expected unique result, but count was " + cursor.getCount());
}
return loadCurrent(cursor, 0, true);
}
loadCurrent方法才是關(guān)鍵,首先判斷了一個identityScopeLong對象是否為null,如果不為null,則根據(jù)key判斷identityScopeLong里面是否有該對象朝蜘,如果有直接返回,沒有就去數(shù)據(jù)庫查詢涩金,查詢完再放入identityScopeLong谱醇。identityScopeLong并不是一個Map集合,但它里面有個類似Map集合的對象步做,它實(shí)現(xiàn)了類似Map集合存放key-value方法副渴。可以看到辆床,這個identityScopeLong對象應(yīng)該就是緩存元兇了佳晶。
final protected T loadCurrent(Cursor cursor, int offset, boolean lock) {
if (identityScopeLong != null) {
if (offset != 0) {
// Occurs with deep loads (left outer joins)
if (cursor.isNull(pkOrdinal + offset)) {
return null;
}
}
long key = cursor.getLong(pkOrdinal + offset);
T entity = lock ? identityScopeLong.get2(key) : identityScopeLong.get2NoLock(key);
if (entity != null) {
return entity;
} else {
entity = readEntity(cursor, offset);
attachEntity(entity);
if (lock) {
identityScopeLong.put2(key, entity);
} else {
identityScopeLong.put2NoLock(key, entity);
}
return entity;
}
} else if (identityScope != null) {
K key = readKey(cursor, offset);
if (offset != 0 && key == null) {
// Occurs with deep loads (left outer joins)
return null;
}
T entity = lock ? identityScope.get(key) : identityScope.getNoLock(key);
if (entity != null) {
return entity;
} else {
entity = readEntity(cursor, offset);
attachEntity(key, entity, lock);
return entity;
}
} else {
// Check offset, assume a value !=0 indicating a potential outer join, so check PK
if (offset != 0) {
K key = readKey(cursor, offset);
if (key == null) {
// Occurs with deep loads (left outer joins)
return null;
}
}
T entity = readEntity(cursor, offset);
attachEntity(entity);
return entity;
}
}
從源頭看看這個identityScopeLong是怎么創(chuàng)建的,
首先daoSession初始化的地方
DaoMaster.OpenHelper daoMaster = new DaoMaster.DevOpenHelper(context.getApplicationContext(), Constant.DONGHA_DB_NAME, null);
daoSession = new DaoMaster(daoMaster.getWritableDb()).newSession();
DaoSession 直接是new了一個對象讼载,傳入了IdentityScopeType.Session
public DaoSession newSession() {
return new DaoSession(db, IdentityScopeType.Session, daoConfigMap);
}
調(diào)用了DaoConfig的initIdentityScope方法
public DaoSession(Database db, IdentityScopeType type, Map<Class<? extends AbstractDao<?, ?>>, DaoConfig>
daoConfigMap) {
super(db);
aDLatLngDomainDaoConfig = daoConfigMap.get(ADLatLngDomainDao.class).clone();
aDLatLngDomainDaoConfig.initIdentityScope(type);
DaoConfig 的initIdentityScope方法根據(jù)類型判斷,keyIsNumeric變量判斷表里面是否有主鍵
public void initIdentityScope(IdentityScopeType type) {
if (type == IdentityScopeType.None) {
identityScope = null;
} else if (type == IdentityScopeType.Session) {
if (keyIsNumeric) {
identityScope = new IdentityScopeLong();
} else {
identityScope = new IdentityScopeObject();
}
} else {
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
keyIsNumeric變量賦值是在構(gòu)造方法里面進(jìn)行的
public DaoConfig(Database db, Class<? extends AbstractDao<?, ?>> daoClass) {
this.db = db;
try {
this.tablename = (String) daoClass.getField("TABLENAME").get(null);
Property[] properties = reflectProperties(daoClass);
this.properties = properties;
allColumns = new String[properties.length];
List<String> pkColumnList = new ArrayList<String>();
List<String> nonPkColumnList = new ArrayList<String>();
Property lastPkProperty = null;
for (int i = 0; i < properties.length; i++) {
Property property = properties[i];
String name = property.columnName;
allColumns[i] = name;
//判斷是否有主鍵
if (property.primaryKey) {
pkColumnList.add(name);
lastPkProperty = property;
} else {
nonPkColumnList.add(name);
}
}
String[] nonPkColumnsArray = new String[nonPkColumnList.size()];
nonPkColumns = nonPkColumnList.toArray(nonPkColumnsArray);
String[] pkColumnsArray = new String[pkColumnList.size()];
pkColumns = pkColumnList.toArray(pkColumnsArray);
pkProperty = pkColumns.length == 1 ? lastPkProperty : null;
statements = new TableStatements(db, tablename, allColumns, pkColumns);
//如果有主鍵
if (pkProperty != null) {
Class<?> type = pkProperty.type;
keyIsNumeric = type.equals(long.class) || type.equals(Long.class) || type.equals(int.class)
|| type.equals(Integer.class) || type.equals(short.class) || type.equals(Short.class)
|| type.equals(byte.class) || type.equals(Byte.class);
} else {
keyIsNumeric = false;
}
} catch (Exception e) {
throw new DaoException("Could not init DAOConfig", e);
}
}
每個Dao對象初始化的時候,都會傳入對應(yīng)的config中跌。而identityScopeLong就算從config里面獲取的
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;
}
現(xiàn)給解決方法
1 既然有緩存咨堤,那么我們不用緩存就可以了。
在生成DaoSession方法里面?zhèn)魅隝dentityScopeType.None就行漩符,這樣每次都從數(shù)據(jù)庫獲取一喘。這樣子所有的獲取數(shù)據(jù)都會沒有緩存,除非每次使用都重新生成一個DaoSession。
public DaoSession newSession(IdentityScopeType type) {
return new DaoSession(db, type, daoConfigMap);
}
2 使用對象的clone方法。既然我們不想沒保存就修改greendao給我們的緩存對象,那我們就使用clone方法生成一個副本凸克。這樣修改這個對象不影響greendao的緩存對象议蟆。
3 利用dao.detachAll方法.可以看到,此方法是清除緩存萎战,這樣我們拿到的是數(shù)據(jù)庫獲取的數(shù)據(jù)對象
public void detachAll() {
if (identityScope != null) {
identityScope.clear();
}
}
使用第2種方式咐容,只在獲取的時候調(diào)用clone方法,
當(dāng)然蚂维,這個對象要實(shí)現(xiàn)cloneable接口
public UserInfoDomain getUserInfoDomain(){
UserInfoDomainDao dao= daoSession.getUserInfoDomainDao();
UserInfoDomain userInfoDomain=dao.queryBuilder().where(UserInfoDomainDao.Properties.UserId.eq(getUserId())).unique();
if (userInfoDomain!=null){
DebugLog.d("獲取的用戶信息:"+userInfoDomain.toString());
return userInfoDomain.clone();
}else{
userInfoDomain=new UserInfoDomain();
return userInfoDomain;
}
}
打印結(jié)果戳粒。正如所料,并沒有改變greendao緩存對象
使用第三種方法虫啥,在獲取前調(diào)用detachAll方法
public UserInfoDomain getUserInfoDomain(){
UserInfoDomainDao dao= daoSession.getUserInfoDomainDao();
dao.detachAll();
UserInfoDomain userInfoDomain=dao.queryBuilder().where(UserInfoDomainDao.Properties.UserId.eq(getUserId())).unique();
if (userInfoDomain!=null){
DebugLog.d("獲取的用戶信息:"+userInfoDomain.toString());
return userInfoDomain;
}else{
userInfoDomain=new UserInfoDomain();
return userInfoDomain;
}
}
打印結(jié)果也如第二種一樣蔚约。