采坑记之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缓存_第1张图片

 

到底经历了啥??我都没有调用保存数据库的代码啊!!!

憋急,先把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, Mapextends 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, Classextends 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 pkColumnList = new ArrayList();
        List nonPkColumnList = new ArrayList();
        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;
    }

}

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

 

 

 

你可能感兴趣的:(android,源码解读)