採坑記之greendao緩存

 

採坑記之greendao緩存

項目裏面ORM框架用的greendao.測試中出現一個問題,在一個界面獲取數據庫的一個對象,然後更改對象的屬性值,沒有點擊保存按鈕。再進入這個界面時,從數據庫同樣獲取的這個對象居然改變了。

之前有看到網上說greendao有緩存,所以獲取數據比較快,我猜想這裏碰到的應該也是這個問題。

 

我模擬獲取數據對象的示例代碼,首先拿到對象,然後設置一個屬性後,再去數據庫獲取對象打印這個屬性值

UserInfoDomain userInfoDomain=VeryFitPlusDao.getInstance().getUserInfoDomain();
DebugLog.d(userInfoDomain.getShowName());
userInfoDomain.setShowName("hahahah");
DebugLog.d(VeryFitPlusDao.getInstance().getUserInfoDomain().getShowName());


獲取用戶信息的代碼也很簡單,直接調用greendaoAPI

/**
     * 獲取當前用戶信息
     */
    
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;
        }

    }

結果打印的值

 

 

 

 

到底經歷了啥??我都沒有調用保存數據庫的代碼啊!!!

憋急,先把greendao獲取數據的源碼追蹤看一遍。

首先根據條件獲取對象

HealthSleepDomain sleepDomain=dao.queryBuilder().where(dao.queryBuilder().and(conditionDate,conditionUserId)).unique()

調用的是QueryBuilderunique方法

QueryBuilder

public T unique() {
    return build().unique();
}

繼續查看.調用的是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);
}

繼續查看AbstractDao的loadUniqueAndCloseCursor方法。內部又調用了loadUnique方法

AbstractDao

protected T loadUniqueAndCloseCursor(Cursor cursor) {
    try {
        return loadUnique(cursor);
    } finally {
        cursor.close();
    }
}

 

loadUnique方法,判斷是否有數據,如果沒有數據直接返回null,有數據裏面調用了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方法纔是關鍵,首先判斷了一個identityScopeLong對象是否爲null,如果不爲null,則根據key判斷identityScopeLong裏面是否有該對象,如果有直接返回,沒有就去數據庫查詢,查詢完再放入identityScopeLongidentityScopeLong並不是一個Map集合,但它裏面有個類似Map集合的對象,它實現了類似Map集合存放key-value方法。可以看到,這個identityScopeLong對象應該就是緩存元兇了。

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是怎麼創建的,

首先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);
}

調用了DaoConfiginitIdentityScope方法

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方法根據類型判斷,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變量賦值是在構造方法裏面進行的

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對象初始化的時候,都會傳入對應的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;
}

 

現給解決方法

1 既然有緩存,那麼我們不用緩存就可以了。

在生成DaoSession方法裏面傳入IdentityScopeType.None就行,這樣每次都從數據庫獲取。這樣子所有的獲取數據都會沒有緩存,除非每次使用都重新生成一個DaoSession

public DaoSession newSession(IdentityScopeType type) {
    return new DaoSession(db, type, daoConfigMap);
}

2 使用對象的clone方法。既然我們不想沒保存就修改greendao給我們的緩存對象,那我們就使用clone方法生成一個副本。這樣修改這個對象不影響greendao的緩存對象。

3 利用dao.detachAll方法.可以看到,此方法是清除緩存,這樣我們拿到的是數據庫獲取的數據對象

public void detachAll() {
    if (identityScope != null) {
        identityScope.clear();
    }
}

 

 

使用第2種方式,只在獲取的時候調用clone方法,

當然,這個對象要實現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;
        }

    }

打印結果。正如所料,並沒有改變greendao緩存對象

 

 

使用第三種方法,在獲取前調用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;
    }

}

打印結果也如第二種一樣。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章