Basic Concepts
Concepts
Up to now, the caches we have introduced are all single-view caches, where all clients see the same cached data.
However, often times, different clients will see different persistent data, which is usually caused by permission systems.
Since different clients will see different persistent data, we naturally also expect different clients to see different cache data, that is, the cache provides different views for different clients.
Multi-view cache solves this problem very well by allowing different clients to see different caches.
Unlike previous discussions where cache can be categorized into object cache, association cache and calculated cache, multi-view cache is not adding a new category to the existing taxonomy, but rather another dimension of characteristics.
Except for object cache, both association cache and calculated cache can be combined with multi-view cache.
Single-view Cache | Multi-view Cache | |
---|---|---|
Object Cache | Object Cache | NA |
Association Cache | Single-view Association Cache | Multi-view Association Cache |
Calculated Cache | Single-view Calculated Cache | Multi-view Calculated Cache |
We can refer to association cache and calculated cache together as property cache.
Therefore, this table can also be interpreted as property cache can be multi-viewed.
Scenarios
Multi-view cache is caused by user defined global filters.
As long as an entity is applied with a user defined global filter,
-
all association caches targeting it
-
calculated caches relying on these association caches
need to be multi-viewed.
For example, if an user defined global filter is added to Book
, then properties like:
-
Association properties targeting
Book
, such asBookStore.books
,Author.books
-
Calculated properties relying on the above association properties, such as
BookStore.avgPrice
,BookStore.newestBooks
will be sensitive to the filter, that is, different filtering conditions will result in different data seen by different clients.
These filter-sensitive properties either do not support cache or support multi-view cache. Configuring single-view cache for them will be considered invalid and the cache configuration will be ignored.
Don't worry. Jimmer will tell developers why cache is abandoned.
SubKey
SubKey
is an important concept for multi-view cache and a prerequisite for subsequent documentation.
Let's first take a look at the structure of single-view cache
Key | Value |
---|---|
Book-10 | {"id":10,"name":"GraphQL in Action",...} |
Book.authors-1 | [1,2] |
BookStore.avgPrice-2 | 80.333333 |
Here we list examples of object cache, association cache and calculated cache. Although the cache types are different, the cache structures are unified simple KV structures.
Now let's take a look at an example of multi-view cache:
Key | SubKey | Value |
---|---|---|
BookStore.books-1 | {} | [6,5,4,3,2,1,9,8,7] |
{"tenant":"a"} | [5,3,1,9,7] | |
{"tenant":"b"} | [6,4,2,8] | |
{"module":"x"} | [6,5,3,2,9,8] | |
{"module":"x","tenant":"a"} | [5,3,9] | |
{"module":"x","tenant":"b"} | [6,2,8] | |
{"module":"y"} | [4,1,7] | |
{"module":"y","tenant":"a"} | [1,7] | |
{"module":"y","tenant":"b"} | [4] | |
BookStore.books-2 | {} | [12,11,10] |
{"tenant":"a"} | [11] | |
{"tenant":"b"} | [12,10] | |
{"module":"x"} | [12,11] | |
{"module":"x","tenant":"a"} | [11] | |
{"module":"x","tenant":"b"} | [12] | |
{} | [10] | |
{"tenant":"a"} | [] | |
{"tenant":"b"} | [10] |
Multi-view cache is no longer a simple KV structure, but a nested two-level KV structure.
For Redis, this structure is Redis Hashes.
In this case, the SubKey
in the table is the Hash Key
in Redis.
Obviously, compared to single-view cache, multi-view cache splits the data into finer granularity by sub key, allowing different clients to see different data.
-
Key
The Key in multi-view cache is no different from the key in single-view cache, representing a specific entity property, decided by Jimmer.
-
SubKey
The key characteristic of multi-view cache. The permission system allows different clients to have different sub keys, eventually extracting different data from the cache.
infoIn Jimmer, SubKey must be the JSON-serialized string of
java.util.SortedMap<String, Object>
.This
SortedMap
must use the default sorting rule without customComparator
.This is very important. For example, SubKey can only be
{"module":"x","tenant":"a"}
, not{tenant:"a", "module":"x"}
which is different but equivalent.
This ensures there is no redundant information internally in multi-view cache.
Multi-view cache is only for property cache, i.e. association cache and calculated cache. So association properties and complex calculated properties must specify sub key for their multi-view caches.
SubKey of Association Properties
For association properties, the necessity of multi-view cache must be caused by the associated entity being affected by user defined global filters.
Supporting multi-view cache by merely implementing the Filter/KFilter
interface is not enough. The CacheableFilter/KCacheableFilter
interface must be implemented.
- Java
- Kotlin
package org.babyfish.jimmer.sql.filter;
public interface CacheableFilter<P extends Props> extends Filter<P> {
SortedMap<String, Object> getParameters();
...other code omitted...
}
package org.babyfish.jimmer.sql.kt.filter
interface KCacheableFilter<E: Any> : KFilter<E> {
fun getParameters(): SortedMap<String, Any>?
...other code omitted...
}
Users need to implement the getParameters()
method to contribute part of the information to sub key.
When multiple CacheableFilter/KCacheableFilter
affect the associated entity, the data returned by the getParameters()
method of each filter object is merged together as the SubKey.
SubKey of Complex Calculated Properties
Implementing complex calculated properties requires implementing the TransientResolver/KTransientResolver
interface.
- Java
- Kotlin
package org.babyfish.jimmer.sql;
import org.babyfish.jimmer.lang.Ref;
import java.util.SortedMap;
public interface TransientResolver<ID, V> {
default Ref<SortedMap<String, Object>> getParameterMapRef() {
return Ref.empty();
}
...other code omitted...
}
package org.babyfish.jimmer.sql.kt
import org.babyfish.jimmer.lang.Ref
interface KTransientResolver<ID: Any, V> : TransientResolver<ID, V> {
override fun getParameterMapRef(): Ref<SortedMap<String, Any>?>? =
Ref.empty()
...other code omitted...
}
This method returns the Ref
which is the wrapper of SortedMap
.
-
If
Ref
itself is null, it means getting sub key is difficult and multi-view cache cannot be applied to the calculated property.infoIn this case, Jimmer will tell developers why cache is abandoned.
-
Otherwise, the internal value of
Ref
represents the sub key of the current calculated property.
When invalidating cache, Jimmer will automatically delete invalid cached by key.
Cached items of multi-view cache are always deleted as a whole based on Key
rather than partially based on Key
+ SubKey
, to maximize the relative simplicity of multi-view cache.