采坑记之greendao缓存

采坑记之greendao缓存
项目里面ORM框架用的greendao.测试中出现一个问题,在一个界面获取数据库的一个对象,然后更改对象的属性值,没有点击保存按钮。再进入这个界面时,从数据库同样获取的这个对象居然改变了。
之前有看到网上说greendao有缓存,所以获取数据比较快,我猜想这里碰到的应该也是这个问题。

我模拟获取数据对象的示例代码,首先拿到对象,然后设置一个属性后,再去数据库获取对象打印这个属性值

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

获取用户信息的代码也很简单,直接调用greendao的API

/**
     * 获取当前用户信息
     */
    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;
        }

    }

结果打印的值


图片1.png
wuyu.jpg

到底经历了啥??我都没有调用保存数据库的代码啊!!!
憋急,先把greendao获取数据的源码追踪看一遍。
首先根据条件获取对象

HealthSleepDomain sleepDomain=dao.queryBuilder().where(dao.queryBuilder().and(conditionDate,conditionUserId)).unique()
调用的是QueryBuilder的unique方法
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里面是否有该对象,如果有直接返回,没有就去数据库查询,查询完再放入identityScopeLong。identityScopeLong并不是一个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);
}

调用了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方法根据类型判断,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缓存对象


图片2.png

使用第三种方法,在获取前调用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

打印结果也如第二种一样。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容

  • 前段时间工作中接触到了数据库greendao,将项目中所有原生sqlite替换成为了greendao数据库封装框架...
    ya_nn阅读 14,933评论 6 33
  • (一)GreenDao简介 GreenDao是一个对象关系映射(ORM)的开源框架,目前最主流的安卓数据库操作框架...
    miss2008阅读 5,238评论 4 18
  • GreenDao 介绍:greenDAO是一个对象关系映射(ORM)的框架,能够提供一个接口通过操作对象的方式去操...
    小董666阅读 730评论 0 1
  • greenDAO greenDAO 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。它...
    蕉下孤客阅读 16,091评论 18 104
  • 老石磨 一个老人 蹲在乡村的屋檐下: 半睡半醒 从不怕日晒雨淋 坚强咬牙 磨不灭硬性 乡村汉子
    雪山孟龙阅读 270评论 0 0