Caching
DBFlow provides powerful caching mechanisms to speed up retrieval from the database to enable high performance in our applications.
Caching is not enabled by default, but it is very easy to enable.
Note: caching functions correctly in only when we do CRUD operations on full model queries (i.e. Single table, no projections).
Caching should be used when:
- Loading the same set of
Model
many times with slightly different selection. - Operating on full
Model
objects (containing all@PrimaryKey
)
Caching should be avoided (or clear caches) when:
- Performing selection, complex queries, anything different than starting with
SQLite.select()
- Using the wrapper modifications such as
Insert
,Update
, orDelete
. In this case you should clear the associated cache, post-operation.
Supported Caching Classes
Caching is supported for:
SparseArray
viaSparseArrayBasedCache
(platform SparseArray)Map
viaSimpleMapCache
LruCache
viaModelLruCache
(copy ofLruCache
, so dependency avoided)- Custom Caching classes that implement
ModelCache
Cache sizes are not supported for SimpleMapCache
. This is because Map
can hold
arbitrary size of contents.
Enable Caching
To enable caching on a single-primary key table, simply specify that it is enabled:
@Table(database = AppDatabase.class, cachingEnabled = true)
public class CacheableModel {
@PrimaryKey(autoincrement = true)
long id;
@Column
String name;
}
or in Kotlin:
@Table(database = AppDatabase.class, cachingEnabled = true)
class CacheableModel {
@PrimaryKey(autoincrement = true)
var id: Long = 0L
@Column
var name: String? = null;
}
to use caching on a table that uses multiple primary keys, see.
By default we use a SimpleMapCache
, which loads Model
into a Map
. The key is
either the primary key of the object or a combination of the two, but it should have
an associated HashCode
and equals()
value.
Loading from the DB
When retrieving from the database, we still run a full query that returns a Cursor
.
We skirt the expensive conversion process by checking the combination of Primary key on each row.
If an item exists with the same primary key combination, we return that object out of the cache.
If you operate on a model object, that change gets reflected in the cache. But beware modifying them in a separate thread might result in state state returned if the cache is not synchronized properly.
Any time a field on these objects are modified, you should immediately save those since we have a direct reference to the object from the cache. Otherwise, the DB and cache could get into an inconsistent state.
MyModel model = SQLite.select().from(MyModel.class).where(...).querySingle();
model.setName("Name");
model.save(); // save it to DB post any modifications to this object.
(select from MyModel::class where (...)).result?.let { result ->
result.name = "Name"
result.save()
}
Disable Caching For Some Queries
To disable caching on certain queries as you might want to project on only a few columns,
rather than the full dataset. Just call disableCaching()
:
select(My_Table.column, My_Table.column2)
.from(My.class)
.disableCaching()
.queryList();
select(My_Table.column, My_Table.column2)
.from(My::class)
.disableCaching()
.list;
Advanced
Specifying cache Size
To specify cache size, set @Table(cacheSize = {size})
. Please note that not all
caches support sizing. It's up to each cache.
Custom Caches
To specify a custom cache for a table, please define a public static final
field:
@ModelCacheField
public static ModelCache<CacheableModel3, ?> modelCache = new SimpleMapCache<>(); // replace with any cache you want.
companion object {
@JvmField @ModelCacheField
val modelCache = SimpleMapCache<CacheableModel3, Any>()
}
Multiple Primary Key Caching
This allows for tables that have multiple primary keys be used in caching. To use,
add a @MultiCacheField
public static final
field.
for example we have a Coordinate
class:
@Table(database = AppDatabase.class, cachingEnabled = true)
public class Coordinate {
@MultiCacheField
public static final IMultiKeyCacheConverter<String> multiKeyCacheModel = new IMultiKeyCacheConverter<String>() {
@Override
@NonNull
public String getCachingKey(@NonNull Object[] values) {
return "(" + values[0] + "," + values[1] + ")";
}
};
@PrimaryKey
double latitude;
@PrimaryKey
double longitude;
@Table(database = AppDatabase.class, cachingEnabled = true)
class Coordinate(@PrimaryKey latitude: Double = 0.0,
@PrimaryKey longitude: Double = 0.0) {
companion object {
@JvmField
@MultiCacheField
val cacheConverter = IMultiKeyCacheConverter { values -> "${values[0]},${values[1]}" }
}
}
In this case we use the IMultiKeyCacheConverter
class, which specifies a key type
that the object returns. The getCachingKey
method returns an ordered set of @PrimaryKey
columns in declaration order. Also the value that is returned should have an equals()
or hashcode()
specified
especially when used in the SimpleMapCache
.