采坑記之greendao緩存

采坑記之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é)果打印的值


圖片1.png
wuyu.jpg

到底經(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緩存對象


圖片2.png

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

}
圖片3.png

打印結(jié)果也如第二種一樣蔚约。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涂籽,隨后出現(xiàn)的幾起案子苹祟,更是在濱河造成了極大的恐慌,老刑警劉巖评雌,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件树枫,死亡現(xiàn)場離奇詭異,居然都是意外死亡柳骄,警方通過查閱死者的電腦和手機(jī)团赏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耐薯,“玉大人舔清,你說我怎么就攤上這事∏酰” “怎么了体谒?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長臼婆。 經(jīng)常有香客問我抒痒,道長,這世上最難降的妖魔是什么颁褂? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任故响,我火速辦了婚禮,結(jié)果婚禮上颁独,老公的妹妹穿的比我還像新娘彩届。我一直安慰自己,他們只是感情好誓酒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布樟蠕。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寨辩。 梳的紋絲不亂的頭發(fā)上吓懈,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天,我揣著相機(jī)與錄音靡狞,去河邊找鬼耻警。 笑死,一個胖子當(dāng)著我的面吹牛耍攘,可吹牛的內(nèi)容都是我干的榕栏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼蕾各,長吁一口氣:“原來是場噩夢啊……” “哼扒磁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起式曲,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤妨托,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后吝羞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兰伤,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年钧排,在試婚紗的時候發(fā)現(xiàn)自己被綠了敦腔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡恨溜,死狀恐怖符衔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糟袁,我是刑警寧澤判族,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站项戴,受9級特大地震影響形帮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜周叮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一辩撑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仿耽,春花似錦槐臀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至敬扛,卻和暖如春晰洒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背啥箭。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工谍珊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人急侥。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓砌滞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坏怪。 傳聞我的和親對象是個殘疾皇子贝润,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前段時間工作中接觸到了數(shù)據(jù)庫greendao,將項目中所有原生sqlite替換成為了greendao數(shù)據(jù)庫封裝框架...
    ya_nn閱讀 14,926評論 6 33
  • (一)GreenDao簡介 GreenDao是一個對象關(guān)系映射(ORM)的開源框架铝宵,目前最主流的安卓數(shù)據(jù)庫操作框架...
    miss2008閱讀 5,234評論 4 18
  • GreenDao 介紹:greenDAO是一個對象關(guān)系映射(ORM)的框架打掘,能夠提供一個接口通過操作對象的方式去操...
    小董666閱讀 728評論 0 1
  • greenDAO greenDAO 是一個將對象映射到 SQLite 數(shù)據(jù)庫中的輕量且快速的 ORM 解決方案。它...
    蕉下孤客閱讀 16,090評論 18 104
  • 老石磨 一個老人 蹲在鄉(xiāng)村的屋檐下: 半睡半醒 從不怕日曬雨淋 堅強(qiáng)咬牙 磨不滅硬性 鄉(xiāng)村漢子
    雪山孟龍閱讀 269評論 0 0