强大的模型缓存机制
在DBFlow库中,模型缓存被设计得非常简单,同时又具有高度可扩展性、可访问性和可用性。
ModelCache
是一个接口,可被用于SQLite检索、FlowQueryList
、FlowCursorList
或者任何需要缓存的地方。
在数据表中开启缓存
通过在@Table
注解中设置cachingEnabled = true
我们就可以很简单地开启缓存。在复合主键@PrimaryKey
的表中,我们还需要定义一个@MultiCacheField
对象(之后会进行讲解)
当我们检索数据库,模型数据对象将被缓存下来,DBFlow会高效地管理这些缓存。
这些是被支持的常见的缓存管理方式:
ModelLruCache
-> 使用LruCache算法,会按照既定的规则添加和释放缓存。SimpleMapCache
-> 会被缓存到一个预定义大小的Map
(默认为HashMap
)SparseArrayBasedCache
-> 底层是一个int->object的SparseArray
稀疏数组。它用于缓存原生数字类型及其包装类(比如Integer、Double、Long等)、或者返回数字类型的MulitCacheConverter
转换器。
NOTE: 如果你执行了SELECT
操作,它将会在生成全部副本之前缓存部分数据,在这里推荐载入全部数据,因为缓存能解决大部分的性能问题。
(if you run a SELECT
with columns specified, it may cache partial Model
classes if they're loaded before the full counterparts. It is highly recommended to just load the full models in this case, since the caching mechanism will make up most efficiency problems.)
默认情况下,缓存使用的是SimpleMapCache
,你可以在@Table
注解中通过指定cacheSize()
参数来设定默认的缓存大小。注意,当我们特别指定了自定义缓存后,这个参数将变得无效。
使用一个自定义缓存,只需要在我们的Model
类中添加@ModelCacheField
属性:
@ModelCacheField
public static ModelCache<CacheableModel3, ?> modelCache = new SimpleMapCache<>();
@ModelCacheField
属性必须是public static
在3.0版本之后,BDFlow对于从缓存中载入的@ForeignKey
会进行更加智能的处理。
缓存如何工作
- 每个
@Tabel
/Model
类都有自己的缓存,表与表直接、子类与父类直接并不会共享缓存。- 它们会拦截对数据库的检索操作,并将其中的数据缓存下来(后面会详解)
- 使用任何的
Insert
、Update
或Delete
方法时,其中的对象都会被直接缓存而不会更新(考虑到性能),。如果你想要强制刷新缓存则可以使用FlowManager.getModelAdapter(MyTable.class).getModelCache().clear()
方法使缓存失效。 - 直接修改从缓存中得到的对象会导致缓存本身被直接修改,由于在使用
save()
、insert()
、update()
等方法之后对象数据才会被写入数据库,所以这可能会导致数据的不一致。
当我们执行一个检索操作,DBFlow将会:
- 运行检索语句,并从数据库得到一个
Cursor
的结果 - 从
Cursor
中取出主键的值。 - 如果这个键值在缓存中,则:
- 刷新关系数据(如
@ForeignKey
)。TIP:将关系所对应的表启动缓存能使这个操作变得更快。 - 返回缓存的对象
- 刷新关系数据(如
- 如果这个键值不存在,则说明此对象没有被缓存,这时我们会从数据库继续载入此对象的其他数据并将之缓存下来(直到该缓存失效)
复合主键的缓存处理
在3.0版本开始,DBFlow支持对复合主键的缓存。这需要Model
类显式定义至少一个@MultiCacheField
属性:
@Table(database = TestDatabase.class, cachingEnabled = true)
public class MultipleCacheableModel extends BaseModel {
@MultiCacheField
public static IMultiKeyCacheConverter<String> multiKeyCacheModel = new IMultiKeyCacheConverter<String>() {
@Override
@NonNull
public String getCachingKey(@NonNull Object[] values) { // in order of the primary keys defined
return "(" + values[0] + "," + values[1] + ")";
}
};
@PrimaryKey
double latitude;
@PrimaryKey
double longitude;
@ForeignKey(references = {@ForeignKeyReference(columnName = "associatedModel",
columnType = String.class, foreignKeyColumnName = "name", referencedFieldIsPackagePrivate = true)})
TestModel1 associatedModel;
}
MultiCacheField
里的返回值可以是任意类型,只要与你所定义ModelCache
的Key类型一致即可。
FlowCursorList + FlowQueryList
FlowCursorList
与FlowQueryList
使用独立的ModelCache
缓存,并不与@Tabel
/Model
类的缓存通用。可以通过重写方法自定义它们的缓存方式:
@Override
protected ModelCache<? extends BaseCacheableModel, ?> getBackingCache() {
return new MyCustomCache<>();
}
自定义缓存
当然,你甚至可以完全自定义自己的缓存算法。
比如,这是内置的LruCache
缓存算法的代码:
public class ModelLruCache<ModelClass extends Model> extends ModelCache<ModelClass, LruCache<Long, ModelClass>>{
public ModelLruCache(int size) {
super(new LruCache<Long, ModelClass>(size));
}
@Override
public void addModel(Object id, ModelClass model) {
if(id instanceof Number) {
synchronized (getCache()) {
Number number = ((Number) id);
getCache().put(number.longValue(), model);
}
} else {
throw new IllegalArgumentException("A ModelLruCache must use an id that can cast to" +
"a Number to convert it into a long");
}
}
@Override
public ModelClass removeModel(Object id) {
ModelClass model;
if(id instanceof Number) {
synchronized (getCache()) {
model = getCache().remove(((Number) id).longValue());
}
} else {
throw new IllegalArgumentException("A ModelLruCache uses an id that can cast to" +
"a Number to convert it into a long");
}
return model;
}
@Override
public void clear() {
synchronized (getCache()) {
getCache().evictAll();
}
}
@Override
public void setCacheSize(int size) {
getCache().resize(size);
}
@Override
public ModelClass get(Object id) {
if(id instanceof Number) {
return getCache().get(((Number) id).longValue());
} else {
throw new IllegalArgumentException("A ModelLruCache must use an id that can cast to" +
"a Number to convert it into a long");
}
}
}