DBFlow provides seamless caching mechanisms to speed up retrieval from the database to skip querying the DB directly.
Caching is not enabled by default, but it is very easy to enable.
- Retrieve the near-same list of objects from the DB when data does not change frequently.
- Need to load large, full objects from the DB repeatedly in different places in the app.
Do not use caching when:
-
You project a subset of columns from the DB. i.e. (
select(name, age, firstName)
) -
The data is expected to change frequently. Any operation on the DB that is not tied to model instances will invalidate our cache.
-
You load data from
@OneToMany
, or have nested@OneToMany
fields within inner objects.
Note: DBFlow is fast and efficient. Caching may not be required at all, except in very particular use-cases. Do not abuse. You can call disableCaching()
on a query to ensure it's a fresh dataset.
Sometimes the data becomes out of sync, or you perform a vanilla SQLite query, which causes data to get out of sync from the cache. In those cases call:
modelAdapter<MyTable>().cacheAdapter.clearCache()
Caching under the hood is done by storing an instance of each Model
returned from a query on a specific table into memory.
-
Developer enables caching on Table A
-
Query from Table A
-
When receiving the
Cursor
, we read the primary key values from it and look them up fromModelCache
. If theModel
exists, return it from cache; otherwise create new instance, read in values, and store in cache. -
That instance remains in memory such on next query, we return that instance instead of recreating one from a
Cursor
. -
When we call
ModelAdapter.save()
,insert()
,update()
, ordelete()
, we update model in the cache so that on next retrieval, the model with proper values is returned. -
When SQLite wrapper operations are performed on tables with caching, caches are not modified. When doing such a call, please call
TableA_Table.cacheAdapter.clearCache()
Caching is supported under the hood 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.
To enable caching on a single-primary key table, simply specify that it is enabled:
@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.
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.
val result = (select from MyModel::class where (...)).querySingle(db)
result.name = "Name"
modelAdapter<MyModel>().save(result, db)
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(db)
To specify cache size, set @Table(cacheSize = {size})
. Please note that not all caches support sizing. It's up to each cache.
To specify a custom cache for a table, please define a @JvmField
field:
companion object {
@JvmField @ModelCacheField
val modelCache = SimpleMapCache<CacheableModel3, Any>()
}
This allows for tables that have multiple primary keys be used in caching. To use, add a @MultiCacheField
@JvmField
field. for example we have a Coordinate
class:
@Table(database = AppDatabase.class, cachingEnabled = true)
class Coordinate(@PrimaryKey latitude: Double = 0.0,
@PrimaryKey longitude: Double = 0.0) {
companion object {
@JvmField
@MultiCacheField
val cacheConverter = MultiKeyCacheConverter { values -> "${values[0]},${values[1]}" }
}
}
In this case we use the MultiKeyCacheConverter
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 (use a data class
) especially when used in the SimpleMapCache
.